[asterisk-commits] dlee: trunk r393529 - in /trunk: ./ doc/rest-api/ include/asterisk/ main/ res...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Wed Jul 3 11:32:44 CDT 2013


Author: dlee
Date: Wed Jul  3 11:32:41 2013
New Revision: 393529

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=393529
Log:
Update events to use Swagger 1.3 subtyping, and related aftermath

This patch started with the simple idea of changing the /events data
model to be more sane. The original model would send out events like:

    { "stasis_start": { "args": [], "channel": { ... } } }

The event discriminator was the field name instead of being a value in
the object, due to limitations in how Swagger 1.1 could model objects.
While technically sufficient in communicating event information, it was
really difficult to deal with in terms of client side JSON handling.

This patch takes advantage of a proposed extension[1] to Swagger which
allows type variance through the use of a discriminator field. This had
a domino effect that made this a surprisingly large patch.

 [1]: https://groups.google.com/d/msg/wordnik-api/EC3rGajE0os/ey_5dBI_jWcJ

In changing the models, I also had to change the swagger_model.py
processor so it can handle the type discriminator and subtyping. I took
that a big step forward, and using that information to generate an
ari_model module, which can validate a JSON object against the Swagger
model.

The REST and WebSocket generators were changed to take advantage of the
validators. If compiled with AST_DEVMODE enabled, JSON objects that
don't match their corresponding models will not be sent out. For REST
API calls, a 500 Internal Server response is sent. For WebSockets, the
invalid JSON message is replaced with an error message.

Since this took over about half of the job of the existing JSON
generators, and the .to_json virtual function on messages took over the
other half, I reluctantly removed the generators.

The validators turned up all sorts of errors and inconsistencies in our
data models, and the code. These were cleaned up, with checks in the
code generator avoid some of the consistency problems in the future.

 * The model for a channel snapshot was trimmed down to match the
   information sent via AMI. Many of the field being sent were not
   useful in the general case.
 * The model for a bridge snapshot was updated to be more consistent
   with the other ARI models.

Another impact of introducing subtyping was that the swagger-codegen
documentation generator was insufficient (at least until it catches up
with Swagger 1.2). I wanted it to be easier to generate docs for the API
anyways, so I ported the wiki pages to use the Asterisk Swagger
generator. In the process, I was able to clean up many of the model
links, which would occasionally give inconsistent results on the wiki. I
also added error responses to the wiki docs, making the wiki
documentation more complete.

Finally, since Stasis-HTTP will now be named Asterisk REST Interface
(ARI), any new functions and files I created carry the ari_ prefix. I
changed a few stasis_http references to ari where it was non-intrusive
and made sense.

(closes issue ASTERISK-21885)
Review: https://reviewboard.asterisk.org/r/2639/


Added:
    trunk/doc/rest-api/
      - copied from r393525, team/dlee/private/merges/doc/rest-api/
    trunk/res/res_ari_model.c
      - copied, made public unchanged from r393525, team/dlee/private/merges/res/res_ari_model.c
    trunk/res/res_ari_model.exports.in
      - copied, made public unchanged from r393525, team/dlee/private/merges/res/res_ari_model.exports.in
    trunk/res/stasis_http/ari_model_validators.c
      - copied, made public unchanged from r393525, team/dlee/private/merges/res/stasis_http/ari_model_validators.c
    trunk/res/stasis_http/ari_model_validators.h
      - copied, made public unchanged from r393525, team/dlee/private/merges/res/stasis_http/ari_model_validators.h
    trunk/rest-api-templates/api.wiki.mustache
      - copied, made public unchanged from r393525, team/dlee/private/merges/rest-api-templates/api.wiki.mustache
    trunk/rest-api-templates/ari_model_validators.c.mustache
      - copied, made public unchanged from r393525, team/dlee/private/merges/rest-api-templates/ari_model_validators.c.mustache
    trunk/rest-api-templates/ari_model_validators.h.mustache
      - copied, made public unchanged from r393525, team/dlee/private/merges/rest-api-templates/ari_model_validators.h.mustache
    trunk/rest-api-templates/make_ari_stubs.py
      - copied, made public unchanged from r393525, team/dlee/private/merges/rest-api-templates/make_ari_stubs.py
    trunk/rest-api-templates/models.wiki.mustache
      - copied, made public unchanged from r393525, team/dlee/private/merges/rest-api-templates/models.wiki.mustache
    trunk/tests/test_ari_model.c
      - copied, made public unchanged from r393525, team/dlee/private/merges/tests/test_ari_model.c
Removed:
    trunk/res/res_stasis_json_asterisk.c
    trunk/res/res_stasis_json_asterisk.exports.in
    trunk/res/res_stasis_json_bridges.c
    trunk/res/res_stasis_json_bridges.exports.in
    trunk/res/res_stasis_json_channels.c
    trunk/res/res_stasis_json_channels.exports.in
    trunk/res/res_stasis_json_endpoints.c
    trunk/res/res_stasis_json_endpoints.exports.in
    trunk/res/res_stasis_json_events.c
    trunk/res/res_stasis_json_events.exports.in
    trunk/res/res_stasis_json_playback.c
    trunk/res/res_stasis_json_playback.exports.in
    trunk/res/res_stasis_json_recordings.c
    trunk/res/res_stasis_json_recordings.exports.in
    trunk/res/res_stasis_json_sounds.c
    trunk/res/res_stasis_json_sounds.exports.in
    trunk/res/stasis_json/
    trunk/rest-api-templates/event_function_decl.mustache
    trunk/rest-api-templates/make_stasis_http_stubs.py
    trunk/rest-api-templates/res_stasis_json_resource.c.mustache
    trunk/rest-api-templates/res_stasis_json_resource.exports.mustache
    trunk/rest-api-templates/stasis_json_resource.h.mustache
Modified:
    trunk/Makefile
    trunk/include/asterisk/json.h
    trunk/include/asterisk/stasis_http.h
    trunk/main/json.c
    trunk/main/stasis_bridging.c
    trunk/main/stasis_channels.c
    trunk/main/stasis_endpoints.c
    trunk/res/Makefile
    trunk/res/res_stasis.c
    trunk/res/res_stasis_http.c
    trunk/res/res_stasis_http_asterisk.c
    trunk/res/res_stasis_http_bridges.c
    trunk/res/res_stasis_http_channels.c
    trunk/res/res_stasis_http_endpoints.c
    trunk/res/res_stasis_http_events.c
    trunk/res/res_stasis_http_playback.c
    trunk/res/res_stasis_http_recordings.c
    trunk/res/res_stasis_http_sounds.c
    trunk/res/stasis_http/ari_websockets.c
    trunk/res/stasis_http/resource_recordings.c
    trunk/res/stasis_http/resource_recordings.h
    trunk/rest-api-templates/asterisk_processor.py
    trunk/rest-api-templates/res_stasis_http_resource.c.mustache
    trunk/rest-api-templates/swagger_model.py
    trunk/rest-api-templates/transform.py
    trunk/rest-api/api-docs/asterisk.json
    trunk/rest-api/api-docs/bridges.json
    trunk/rest-api/api-docs/channels.json
    trunk/rest-api/api-docs/endpoints.json
    trunk/rest-api/api-docs/events.json
    trunk/rest-api/api-docs/playback.json
    trunk/rest-api/api-docs/recordings.json
    trunk/rest-api/api-docs/sounds.json
    trunk/tests/test_res_stasis.c
    trunk/tests/test_stasis_channels.c

Modified: trunk/Makefile
URL: http://svnview.digium.com/svn/asterisk/trunk/Makefile?view=diff&rev=393529&r1=393528&r2=393529
==============================================================================
--- trunk/Makefile (original)
+++ trunk/Makefile Wed Jul  3 11:32:41 2013
@@ -416,6 +416,7 @@
 	rm -f main/version.c
 	rm -f doc/core-en_US.xml
 	rm -f doc/full-en_US.xml
+	rm -f docs/rest-api/*.wiki
 	@$(MAKE) -C menuselect clean
 	cp -f .cleancount .lastclean
 
@@ -963,15 +964,15 @@
 
 # We don't want to require Python or Pystache for every build, so this is its
 # own target.
-stasis-stubs:
+ari-stubs:
 ifeq ($(PYTHON),:)
 	@echo "--------------------------------------------------------------------------"
-	@echo "---        Please install python to build Stasis HTTP stubs            ---"
+	@echo "---        Please install python to build ARI stubs            ---"
 	@echo "--------------------------------------------------------------------------"
 	@false
 else
-	$(PYTHON) rest-api-templates/make_stasis_http_stubs.py \
-		rest-api/resources.json res/
+	$(PYTHON) rest-api-templates/make_ari_stubs.py \
+		rest-api/resources.json .
 endif
 
 .PHONY: menuselect
@@ -993,7 +994,7 @@
 .PHONY: installdirs
 .PHONY: validate-docs
 .PHONY: _clean
-.PHONY: stasis-stubs
+.PHONY: ari-stubs
 .PHONY: $(SUBDIRS_INSTALL)
 .PHONY: $(SUBDIRS_DIST_CLEAN)
 .PHONY: $(SUBDIRS_CLEAN)

Modified: trunk/include/asterisk/json.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/json.h?view=diff&rev=393529&r1=393528&r2=393529
==============================================================================
--- trunk/include/asterisk/json.h (original)
+++ trunk/include/asterisk/json.h Wed Jul  3 11:32:41 2013
@@ -157,6 +157,15 @@
  * \return Type of \a value.
  */
 enum ast_json_type ast_json_typeof(const struct ast_json *value);
+
+/*!
+ * \brief Get the string name for the given type.
+ * \since 12.0.0
+ * \param type Type to convert to string.
+ * \return Simple string for the type name (object, array, string, etc.)
+ * \return \c "?" for invalid types.
+ */
+const char *ast_json_typename(enum ast_json_type type);
 
 /*!@}*/
 

Modified: trunk/include/asterisk/stasis_http.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/stasis_http.h?view=diff&rev=393529&r1=393528&r2=393529
==============================================================================
--- trunk/include/asterisk/stasis_http.h (original)
+++ trunk/include/asterisk/stasis_http.h Wed Jul  3 11:32:41 2013
@@ -32,6 +32,12 @@
 #include "asterisk/http.h"
 #include "asterisk/json.h"
 #include "asterisk/http_websocket.h"
+
+/*!
+ * \brief Configured encoding format for JSON output.
+ * \return JSON output encoding (compact, pretty, etc.)
+ */
+enum ast_json_encoding_format stasis_http_json_format(void);
 
 struct stasis_http_response;
 
@@ -141,12 +147,16 @@
 /*!
  * \brief Create an ARI WebSocket session.
  *
+ * If \c NULL is given for the validator function, no validation will be
+ * performed.
+ *
  * \param ws_session Underlying WebSocket session.
+ * \param validator Function to validate outgoing messages.
  * \return New ARI WebSocket session.
  * \return \c NULL on error.
  */
 struct ari_websocket_session *ari_websocket_session_create(
-	struct ast_websocket *ws_session);
+	struct ast_websocket *ws_session, int (*validator)(struct ast_json *));
 
 /*!
  * \brief Read a message from an ARI WebSocket.

Modified: trunk/main/json.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/json.c?view=diff&rev=393529&r1=393528&r2=393529
==============================================================================
--- trunk/main/json.c (original)
+++ trunk/main/json.c Wed Jul  3 11:32:41 2013
@@ -102,6 +102,23 @@
 	ast_assert(0); /* Unexpect return from json_typeof */
 	return r;
 }
+
+const char *ast_json_typename(enum ast_json_type type)
+{
+	switch (type) {
+	case AST_JSON_OBJECT: return "object";
+	case AST_JSON_ARRAY: return "array";
+	case AST_JSON_STRING: return "string";
+	case AST_JSON_INTEGER: return "integer";
+	case AST_JSON_REAL: return "real";
+	case AST_JSON_TRUE: return "boolean";
+	case AST_JSON_FALSE: return "boolean";
+	case AST_JSON_NULL: return "null";
+	}
+	ast_assert(0);
+	return "?";
+}
+
 
 struct ast_json *ast_json_true(void)
 {

Modified: trunk/main/stasis_bridging.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/stasis_bridging.c?view=diff&rev=393529&r1=393528&r2=393529
==============================================================================
--- trunk/main/stasis_bridging.c (original)
+++ trunk/main/stasis_bridging.c Wed Jul  3 11:32:41 2013
@@ -657,10 +657,10 @@
 	}
 
 	json_bridge = ast_json_pack("{s: s, s: s, s: s, s: s, s: o}",
-		"bridgeUniqueid", snapshot->uniqueid,
-		"bridgeTechnology", snapshot->technology,
-		"bridgeType", capability2str(snapshot->capabilities),
-		"bridgeClass", snapshot->subclass,
+		"id", snapshot->uniqueid,
+		"technology", snapshot->technology,
+		"bridge_type", capability2str(snapshot->capabilities),
+		"bridge_class", snapshot->subclass,
 		"channels", json_channels);
 	if (!json_bridge) {
 		return NULL;

Modified: trunk/main/stasis_channels.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/stasis_channels.c?view=diff&rev=393529&r1=393528&r2=393529
==============================================================================
--- trunk/main/stasis_channels.c (original)
+++ trunk/main/stasis_channels.c Wed Jul  3 11:32:41 2013
@@ -32,10 +32,11 @@
 
 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
+#include "asterisk/astobj2.h"
+#include "asterisk/json.h"
+#include "asterisk/pbx.h"
 #include "asterisk/stasis.h"
-#include "asterisk/astobj2.h"
 #include "asterisk/stasis_channels.h"
-#include "asterisk/pbx.h"
 
 /*** DOCUMENTATION
 	<managerEvent language="en_US" name="VarSet">
@@ -621,25 +622,25 @@
 		return NULL;
 	}
 
-	json_chan = ast_json_pack("{ s: s, s: s, s: s, s: s, s: s, s: s, s: s,"
-				  "  s: s, s: s, s: s, s: s, s: o, s: o, s: o,"
-				  "  s: o"
-				  "}",
-				  "name", snapshot->name,
-				  "state", ast_state2str(snapshot->state),
-				  "accountcode", snapshot->accountcode,
-				  "peeraccount", snapshot->peeraccount,
-				  "userfield", snapshot->userfield,
-				  "uniqueid", snapshot->uniqueid,
-				  "linkedid", snapshot->linkedid,
-				  "parkinglot", snapshot->parkinglot,
-				  "hangupsource", snapshot->hangupsource,
-				  "appl", snapshot->appl,
-				  "data", snapshot->data,
-				  "dialplan", ast_json_dialplan_cep(snapshot->context, snapshot->exten, snapshot->priority),
-				  "caller", ast_json_name_number(snapshot->caller_name, snapshot->caller_number),
-				  "connected", ast_json_name_number(snapshot->connected_name, snapshot->connected_number),
-				  "creationtime", ast_json_timeval(snapshot->creationtime, NULL));
+	json_chan = ast_json_pack(
+		/* Broken up into groups of three for readability */
+		"{ s: s, s: s, s: s,"
+		"  s: o, s: o, s: s,"
+		"  s: o, s: o }",
+		/* First line */
+		"id", snapshot->uniqueid,
+		"name", snapshot->name,
+		"state", ast_state2str(snapshot->state),
+		/* Second line */
+		"caller", ast_json_name_number(
+			snapshot->caller_name, snapshot->caller_number),
+		"connected", ast_json_name_number(
+			snapshot->connected_name, snapshot->connected_number),
+		"accountcode", snapshot->accountcode,
+		/* Third line */
+		"dialplan", ast_json_dialplan_cep(
+			snapshot->context, snapshot->exten, snapshot->priority),
+		"creationtime", ast_json_timeval(snapshot->creationtime, NULL));
 
 	return ast_json_ref(json_chan);
 }
@@ -675,6 +676,91 @@
 		strcmp(old_snapshot->caller_name, new_snapshot->caller_name) == 0;
 }
 
+static struct ast_json *channel_blob_to_json(struct stasis_message *message,
+	const char *type)
+{
+	RAII_VAR(struct ast_json *, out, NULL, ast_json_unref);
+	struct ast_channel_blob *channel_blob = stasis_message_data(message);
+	struct ast_json *blob = channel_blob->blob;
+	struct ast_channel_snapshot *snapshot = channel_blob->snapshot;
+	const struct timeval *tv = stasis_message_timestamp(message);
+	int res = 0;
+
+	if (blob == NULL || ast_json_is_null(blob)) {
+		out = ast_json_object_create();
+	} else {
+		/* blobs are immutable, so shallow copies are fine */
+		out = ast_json_copy(blob);
+	}
+
+	if (!out) {
+		return NULL;
+	}
+
+	res |= ast_json_object_set(out, "type", ast_json_string_create(type));
+	res |= ast_json_object_set(out, "timestamp",
+		ast_json_timeval(*tv, NULL));
+
+	/* For global channel messages, the snapshot is optional */
+	if (snapshot) {
+		res |= ast_json_object_set(out, "channel",
+			ast_channel_snapshot_to_json(snapshot));
+	}
+
+	if (res != 0) {
+		return NULL;
+	}
+
+	return ast_json_ref(out);
+}
+
+static struct ast_json *dtmf_end_to_json(struct stasis_message *message)
+{
+	struct ast_channel_blob *channel_blob = stasis_message_data(message);
+	struct ast_json *blob = channel_blob->blob;
+	struct ast_channel_snapshot *snapshot = channel_blob->snapshot;
+	const char *direction =
+		ast_json_string_get(ast_json_object_get(blob, "direction"));
+	const struct timeval *tv = stasis_message_timestamp(message);
+
+	/* Only present received DTMF end events as JSON */
+	if (strcasecmp("Received", direction) != 0) {
+		return NULL;
+	}
+
+	return ast_json_pack("{s: s, s: o, s: O, s: O, s: o}",
+		"type", "ChannelDtmfReceived",
+		"timestamp", ast_json_timeval(*tv, NULL),
+		"digit", ast_json_object_get(blob, "digit"),
+		"duration_ms", ast_json_object_get(blob, "duration_ms"),
+		"channel", ast_channel_snapshot_to_json(snapshot));
+}
+
+static struct ast_json *user_event_to_json(struct stasis_message *message)
+{
+	struct ast_channel_blob *channel_blob = stasis_message_data(message);
+	struct ast_json *blob = channel_blob->blob;
+	struct ast_channel_snapshot *snapshot = channel_blob->snapshot;
+	const struct timeval *tv = stasis_message_timestamp(message);
+
+	return ast_json_pack("{s: s, s: o, s: O, s: O, s: o}",
+		"type", "ChannelUserevent",
+		"timestamp", ast_json_timeval(*tv, NULL),
+		"eventname", ast_json_object_get(blob, "eventname"),
+		"userevent", blob,
+		"channel", ast_channel_snapshot_to_json(snapshot));
+}
+
+static struct ast_json *varset_to_json(struct stasis_message *message)
+{
+	return channel_blob_to_json(message, "ChannelVarset");
+}
+
+static struct ast_json *hangup_request_to_json(struct stasis_message *message)
+{
+	return channel_blob_to_json(message, "ChannelHangupRequest");
+}
+
 /*!
  * @{ \brief Define channel message types.
  */
@@ -682,11 +768,18 @@
 STASIS_MESSAGE_TYPE_DEFN(ast_channel_dial_type);
 STASIS_MESSAGE_TYPE_DEFN(ast_channel_varset_type,
 	.to_ami = varset_to_ami,
+	.to_json = varset_to_json,
 	);
-STASIS_MESSAGE_TYPE_DEFN(ast_channel_user_event_type);
-STASIS_MESSAGE_TYPE_DEFN(ast_channel_hangup_request_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_user_event_type,
+	.to_json = user_event_to_json,
+	);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_hangup_request_type,
+	.to_json = hangup_request_to_json,
+	);
 STASIS_MESSAGE_TYPE_DEFN(ast_channel_dtmf_begin_type);
-STASIS_MESSAGE_TYPE_DEFN(ast_channel_dtmf_end_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_dtmf_end_type,
+	.to_json = dtmf_end_to_json,
+	);
 STASIS_MESSAGE_TYPE_DEFN(ast_channel_hold_type);
 STASIS_MESSAGE_TYPE_DEFN(ast_channel_unhold_type);
 STASIS_MESSAGE_TYPE_DEFN(ast_channel_chanspy_start_type);

Modified: trunk/main/stasis_endpoints.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/stasis_endpoints.c?view=diff&rev=393529&r1=393528&r2=393529
==============================================================================
--- trunk/main/stasis_endpoints.c (original)
+++ trunk/main/stasis_endpoints.c Wed Jul  3 11:32:41 2013
@@ -239,7 +239,7 @@
 		"technology", snapshot->tech,
 		"resource", snapshot->resource,
 		"state", ast_endpoint_state_to_string(snapshot->state),
-		"channels");
+		"channel_ids");
 
 	if (json == NULL) {
 		return NULL;
@@ -253,7 +253,7 @@
 		}
 	}
 
-	channel_array = ast_json_object_get(json, "channels");
+	channel_array = ast_json_object_get(json, "channel_ids");
 	ast_assert(channel_array != NULL);
 	for (i = 0; i < snapshot->num_channels; ++i) {
 		int res = ast_json_array_append(channel_array,

Modified: trunk/res/Makefile
URL: http://svnview.digium.com/svn/asterisk/trunk/res/Makefile?view=diff&rev=393529&r1=393528&r2=393529
==============================================================================
--- trunk/res/Makefile (original)
+++ trunk/res/Makefile Wed Jul  3 11:32:41 2013
@@ -83,5 +83,8 @@
 res_stasis_http.so: stasis_http/ari_websockets.o
 stasis_http/ari_websockets.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_stasis_http_asterisk)
 
+res_ari_model.so: stasis_http/ari_model_validators.o
+stasis_http/ari_model_validators.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_ari_model)
+
 # Dependencies for res_stasis_http_*.so are generated, so they're in this file
 include stasis_http.make

Copied: trunk/res/res_ari_model.c (from r393525, team/dlee/private/merges/res/res_ari_model.c)
URL: http://svnview.digium.com/svn/asterisk/trunk/res/res_ari_model.c?view=diff&rev=393529&p1=team/dlee/private/merges/res/res_ari_model.c&r1=393525&p2=trunk/res/res_ari_model.c&r2=393529
==============================================================================
--- team/dlee/private/merges/res/res_ari_model.c (original)
+++ trunk/res/res_ari_model.c Wed Jul  3 11:32:41 2013
@@ -1,0 +1,207 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee at digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Implementation Swagger validators.
+ *
+ * \author David M. Lee, II <dlee at digium.com>
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "stasis_http/ari_model_validators.h"
+#include "asterisk/logger.h"
+#include "asterisk/module.h"
+#include "asterisk/utils.h"
+
+#include <regex.h>
+
+/* Regex to match date strings */
+static regex_t date_regex;
+
+/* Regex for YYYY-MM-DD */
+#define REGEX_YMD "[0-9]{4}-[01][0-9]-[0-3][0-9]"
+
+/* Regex for hh:mm(:ss(.s)); seconds and subseconds optional
+ * Handles the probably impossible case of a leap second, too */
+#define REGEX_HMS "[0-2][0-9]:[0-5][0-9](:[0-6][0-9](.[0-9]+)?)?"
+
+/* Regex for timezone: (+|-)hh(:mm), with optional colon. */
+#define REGEX_TZ "(Z|[-+][0-2][0-9](:?[0-5][0-9])?)"
+
+/* REGEX for ISO 8601, the time specifier optional */
+#define ISO8601_PATTERN "^" REGEX_YMD "(T" REGEX_HMS REGEX_TZ ")?$"
+
+static int check_type(struct ast_json *json, enum ast_json_type expected)
+{
+	enum ast_json_type actual;
+
+	if (!json) {
+		ast_log(LOG_ERROR, "Expected type %s, was NULL\n",
+			ast_json_typename(expected));
+		return 0;
+	}
+
+	actual = ast_json_typeof(json);
+	if (expected != actual) {
+		ast_log(LOG_ERROR, "Expected type %s, was %s\n",
+			ast_json_typename(expected), ast_json_typename(actual));
+		return 0;
+	}
+	return 1;
+}
+
+static int check_range(intmax_t minval, intmax_t maxval, struct ast_json *json)
+{
+	intmax_t v;
+
+	if (!check_type(json, AST_JSON_INTEGER)) {
+		return 0;
+	}
+
+	v = ast_json_integer_get(json);
+
+	if (v < minval || maxval < v) {
+		ast_log(LOG_ERROR, "Value out of range. Expected %jd <= %jd <= %jd\n", minval, v, maxval);
+		return 0;
+	}
+	return 1;
+}
+
+int ari_validate_void(struct ast_json *json)
+{
+	return check_type(json, AST_JSON_NULL);
+}
+
+int ari_validate_byte(struct ast_json *json)
+{
+	/* Java bytes are signed, which accounts for great fun for all */
+	return check_range(-128, 255, json);
+}
+
+int ari_validate_boolean(struct ast_json *json)
+{
+	enum ast_json_type actual = ast_json_typeof(json);
+	switch (actual) {
+	case AST_JSON_TRUE:
+	case AST_JSON_FALSE:
+		return 1;
+	default:
+		ast_log(LOG_ERROR, "Expected type boolean, was %s\n",
+			ast_json_typename(actual));
+		return 0;
+	}
+}
+
+int ari_validate_int(struct ast_json *json)
+{
+	/* Swagger int's are 32-bit */
+	return check_range(-2147483648, 2147483647, json);
+}
+
+int ari_validate_long(struct ast_json *json)
+{
+	/* All integral values are valid longs. No need for range check. */
+	return check_type(json, AST_JSON_INTEGER);
+}
+
+int ari_validate_float(struct ast_json *json)
+{
+	return check_type(json, AST_JSON_REAL);
+}
+
+int ari_validate_double(struct ast_json *json)
+{
+	return check_type(json, AST_JSON_REAL);
+}
+
+int ari_validate_string(struct ast_json *json)
+{
+	return check_type(json, AST_JSON_STRING);
+}
+
+int ari_validate_date(struct ast_json *json)
+{
+	/* Dates are ISO-8601 strings */
+	const char *str;
+	if (!check_type(json, AST_JSON_STRING)) {
+		return 0;
+	}
+	str = ast_json_string_get(json);
+	ast_assert(str != NULL);
+	if (regexec(&date_regex, str, 0, NULL, 0) != 0) {
+		ast_log(LOG_ERROR, "Date field is malformed: '%s'\n", str);
+		return 0;
+	}
+	return 1;
+}
+
+int ari_validate_list(struct ast_json *json, int (*fn)(struct ast_json *))
+{
+	int res = 1;
+	size_t i;
+
+	if (!check_type(json, AST_JSON_ARRAY)) {
+		return 0;
+	}
+
+	for (i = 0; i < ast_json_array_size(json); ++i) {
+		int member_res;
+		member_res = fn(ast_json_array_get(json, i));
+		if (!member_res) {
+			ast_log(LOG_ERROR,
+				"Array member %zd failed validation\n", i);
+			res = 0;
+		}
+	}
+
+	return res;
+}
+
+static int load_module(void)
+{
+	int res;
+	res = regcomp(&date_regex, ISO8601_PATTERN,
+		REG_EXTENDED | REG_ICASE | REG_NOSUB);
+
+	if (res != 0) {
+		return AST_MODULE_LOAD_FAILURE;
+	}
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	regfree(&date_regex);
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY,
+	AST_MODFLAG_LOAD_ORDER | AST_MODFLAG_GLOBAL_SYMBOLS,
+	"ARI Model validators",
+	.load = load_module,
+	.unload = unload_module,
+	.load_pri = AST_MODPRI_APP_DEPEND,
+        );

Copied: trunk/res/res_ari_model.exports.in (from r393525, team/dlee/private/merges/res/res_ari_model.exports.in)
URL: http://svnview.digium.com/svn/asterisk/trunk/res/res_ari_model.exports.in?view=diff&rev=393529&p1=team/dlee/private/merges/res/res_ari_model.exports.in&r1=393525&p2=trunk/res/res_ari_model.exports.in&r2=393529
==============================================================================
--- team/dlee/private/merges/res/res_ari_model.exports.in (original)
+++ trunk/res/res_ari_model.exports.in Wed Jul  3 11:32:41 2013
@@ -1,0 +1,6 @@
+{
+	global:
+		LINKER_SYMBOL_PREFIXari_*;
+	local:
+		*;
+};

Modified: trunk/res/res_stasis.c
URL: http://svnview.digium.com/svn/asterisk/trunk/res/res_stasis.c?view=diff&rev=393529&r1=393528&r2=393529
==============================================================================
--- trunk/res/res_stasis.c (original)
+++ trunk/res/res_stasis.c Wed Jul  3 11:32:41 2013
@@ -48,7 +48,6 @@
  */
 
 /*** MODULEINFO
-	<depend>res_stasis_json_events</depend>
 	<support_level>core</support_level>
  ***/
 
@@ -66,7 +65,6 @@
 #include "asterisk/strings.h"
 #include "stasis/app.h"
 #include "stasis/control.h"
-#include "stasis_json/resource_events.h"
 
 /*! Time to wait for a frame in the application */
 #define MAX_WAIT_MS 200
@@ -233,28 +231,60 @@
 /*! \brief Typedef for callbacks that get called on channel snapshot updates */
 typedef struct ast_json *(*channel_snapshot_monitor)(
 	struct ast_channel_snapshot *old_snapshot,
-	struct ast_channel_snapshot *new_snapshot);
+	struct ast_channel_snapshot *new_snapshot,
+	const struct timeval *tv);
+
+static struct ast_json *simple_channel_event(
+	const char *type,
+	struct ast_channel_snapshot *snapshot,
+	const struct timeval *tv)
+{
+	return ast_json_pack("{s: s, s: o, s: o}",
+		"type", type,
+		"timestamp", ast_json_timeval(*tv, NULL),
+		"channel", ast_channel_snapshot_to_json(snapshot));
+}
+
+static struct ast_json *channel_created_event(
+	struct ast_channel_snapshot *snapshot,
+	const struct timeval *tv)
+{
+	return simple_channel_event("ChannelCreated", snapshot, tv);
+}
+
+static struct ast_json *channel_destroyed_event(
+	struct ast_channel_snapshot *snapshot,
+	const struct timeval *tv)
+{
+	return ast_json_pack("{s: s, s: o, s: i, s: s, s: o}",
+		"type", "ChannelDestroyed",
+		"timestamp", ast_json_timeval(*tv, NULL),
+		"cause", snapshot->hangupcause,
+		"cause_txt", ast_cause2str(snapshot->hangupcause),
+		"channel", ast_channel_snapshot_to_json(snapshot));
+}
+
+static struct ast_json *channel_state_change_event(
+	struct ast_channel_snapshot *snapshot,
+	const struct timeval *tv)
+{
+	return simple_channel_event("ChannelStateChange", snapshot, tv);
+}
 
 /*! \brief Handle channel state changes */
 static struct ast_json *channel_state(
 	struct ast_channel_snapshot *old_snapshot,
-	struct ast_channel_snapshot *new_snapshot)
-{
-	RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
+	struct ast_channel_snapshot *new_snapshot,
+	const struct timeval *tv)
+{
 	struct ast_channel_snapshot *snapshot = new_snapshot ? new_snapshot : old_snapshot;
 
 	if (!old_snapshot) {
-		return stasis_json_event_channel_created_create(snapshot);
+		return channel_created_event(snapshot, tv);
 	} else if (!new_snapshot) {
-		json = ast_json_pack("{s: i, s: s}",
-			"cause", snapshot->hangupcause,
-			"cause_txt", ast_cause2str(snapshot->hangupcause));
-		if (!json) {
-			return NULL;
-		}
-		return stasis_json_event_channel_destroyed_create(snapshot, json);
+		return channel_destroyed_event(snapshot, tv);
 	} else if (old_snapshot->state != new_snapshot->state) {
-		return stasis_json_event_channel_state_change_create(snapshot);
+		return channel_state_change_event(snapshot, tv);
 	}
 
 	return NULL;
@@ -262,7 +292,8 @@
 
 static struct ast_json *channel_dialplan(
 	struct ast_channel_snapshot *old_snapshot,
-	struct ast_channel_snapshot *new_snapshot)
+	struct ast_channel_snapshot *new_snapshot,
+	const struct timeval *tv)
 {
 	RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
 
@@ -280,19 +311,18 @@
 		return NULL;
 	}
 
-	json = ast_json_pack("{s: s, s: s}",
-		"application", new_snapshot->appl,
-		"application_data", new_snapshot->data);
-	if (!json) {
-		return NULL;
-	}
-
-	return stasis_json_event_channel_dialplan_create(new_snapshot, json);
+	return ast_json_pack("{s: s, s: o, s: s, s: s, s: o}",
+		"type", "ChannelDialplan",
+		"timestamp", ast_json_timeval(*tv, NULL),
+		"dialplan_app", new_snapshot->appl,
+		"dialplan_app_data", new_snapshot->data,
+		"channel", ast_channel_snapshot_to_json(new_snapshot));
 }
 
 static struct ast_json *channel_callerid(
 	struct ast_channel_snapshot *old_snapshot,
-	struct ast_channel_snapshot *new_snapshot)
+	struct ast_channel_snapshot *new_snapshot,
+	const struct timeval *tv)
 {
 	RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
 
@@ -305,29 +335,16 @@
 		return NULL;
 	}
 
-	json = ast_json_pack("{s: i, s: s}",
+	return ast_json_pack("{s: s, s: o, s: i, s: s, s: o}",
+		"type", "ChannelCallerId",
+		"timestamp", ast_json_timeval(*tv, NULL),
 		"caller_presentation", new_snapshot->caller_pres,
-		"caller_presentation_txt", ast_describe_caller_presentation(new_snapshot->caller_pres));
-	if (!json) {
-		return NULL;
-	}
-
-	return stasis_json_event_channel_caller_id_create(new_snapshot, json);
-}
-
-static struct ast_json *channel_snapshot(
-	struct ast_channel_snapshot *old_snapshot,
-	struct ast_channel_snapshot *new_snapshot)
-{
-	if (!new_snapshot) {
-		return NULL;
-	}
-
-	return stasis_json_event_channel_snapshot_create(new_snapshot);
+		"caller_presentation_txt", ast_describe_caller_presentation(
+			new_snapshot->caller_pres),
+		"channel", ast_channel_snapshot_to_json(new_snapshot));
 }
 
 channel_snapshot_monitor channel_monitors[] = {
-	channel_snapshot,
 	channel_state,
 	channel_dialplan,
 	channel_callerid
@@ -351,6 +368,9 @@
 	struct stasis_cache_update *update = stasis_message_data(message);
 	struct ast_channel_snapshot *new_snapshot = stasis_message_data(update->new_snapshot);
 	struct ast_channel_snapshot *old_snapshot = stasis_message_data(update->old_snapshot);
+	/* Pull timestamp from the new snapshot, or from the update message
+	 * when there isn't one. */
+	const struct timeval *tv = update->new_snapshot ? stasis_message_timestamp(update->new_snapshot) : stasis_message_timestamp(message);
 	int i;
 
 	watching_apps = get_apps_watching_channel(new_snapshot ? new_snapshot->uniqueid : old_snapshot->uniqueid);
@@ -361,7 +381,7 @@
 	for (i = 0; i < ARRAY_LEN(channel_monitors); ++i) {
 		RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
 
-		msg = channel_monitors[i](old_snapshot, new_snapshot);
+		msg = channel_monitors[i](old_snapshot, new_snapshot, tv);
 		if (msg) {
 			ao2_callback(watching_apps, OBJ_NODATA, app_send_cb, msg);
 		}
@@ -373,22 +393,26 @@
 	ao2_callback(apps, OBJ_NODATA, app_send_cb, msg);
 }
 
-static void generic_blob_handler(struct ast_channel_blob *obj, channel_blob_handler_cb handler_cb)
+static void sub_channel_blob_handler(void *data,
+		struct stasis_subscription *sub,
+		struct stasis_topic *topic,
+		struct stasis_message *message)
 {
 	RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
 	RAII_VAR(struct ao2_container *, watching_apps, NULL, ao2_cleanup);
+	struct ast_channel_blob *obj = stasis_message_data(message);
 
 	if (!obj->snapshot) {
+		return;
+	}
+
+	msg = stasis_message_to_json(message);
+	if (!msg) {
 		return;
 	}
 
 	watching_apps = get_apps_watching_channel(obj->snapshot->uniqueid);
 	if (!watching_apps) {
-		return;
-	}
-
-	msg = handler_cb(obj);
-	if (!msg) {
 		return;
 	}
 
@@ -446,7 +470,6 @@
 	int argc, char *argv[])
 {
 	RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
 	RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
 
 	struct ast_json *json_args;
@@ -460,13 +483,16 @@
 		return -1;
 	}
 
-	blob = ast_json_pack("{s: []}", "args");
-	if (!blob) {
+	msg = ast_json_pack("{s: s, s: [], s: o}",
+		"type", "StasisStart",
+		"args",
+		"channel", ast_channel_snapshot_to_json(snapshot));
+	if (!msg) {
 		return -1;
 	}
 
 	/* Append arguments to args array */
-	json_args = ast_json_object_get(blob, "args");
+	json_args = ast_json_object_get(msg, "args");
 	ast_assert(json_args != NULL);
 	for (i = 0; i < argc; ++i) {
 		int r = ast_json_array_append(json_args,
@@ -477,11 +503,6 @@
 		}
 	}
 
-	msg = stasis_json_event_stasis_start_create(snapshot, blob);
-	if (!msg) {
-		return -1;
-	}
-
 	app_send(app, msg);
 	return 0;
 }
@@ -499,7 +520,9 @@
 		return -1;
 	}
 
-	msg = stasis_json_event_stasis_end_create(snapshot);
+	msg = ast_json_pack("{s: s, s: o}",
+		"type", "StasisEnd",
+		"channel", ast_channel_snapshot_to_json(snapshot));
 	if (!msg) {
 		return -1;
 	}
@@ -633,15 +656,13 @@
 	app = ao2_find(apps_registry, app_name, OBJ_KEY | OBJ_NOLOCK);
 
 	if (app) {
-		RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
 		RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
 
-		blob = ast_json_pack("{s: s}", "application", app_name);
-		if (blob) {
-			msg = stasis_json_event_application_replaced_create(blob);
-			if (msg) {
-				app_send(app, msg);
-			}
+		msg = ast_json_pack("{s: s, s: s}",
+			"type", "ApplicationReplaced",
+			"application", app_name);
+		if (msg) {
+			app_send(app, msg);
 		}
 
 		app_update(app, handler, data);
@@ -665,82 +686,6 @@
 	}
 }
 
-static struct ast_json *handle_blob_dtmf(struct ast_channel_blob *obj)
-{
-	RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
-	const char *direction;
-
-	/* To simplify events, we'll only generate on receive */
-	direction = ast_json_string_get(
-		ast_json_object_get(obj->blob, "direction"));
-
-	if (strcmp("Received", direction) != 0) {
-		return NULL;
-	}
-
-	extra = ast_json_pack(
-		"{s: o}",
-		"digit", ast_json_ref(ast_json_object_get(obj->blob, "digit")));
-	if (!extra) {
-		return NULL;
-	}
-
-	return stasis_json_event_channel_dtmf_received_create(obj->snapshot, extra);
-}
-
-/* To simplify events, we'll only generate on DTMF end (dtmf_end type) */
-static void sub_dtmf_handler(void *data,
-		struct stasis_subscription *sub,
-		struct stasis_topic *topic,
-		struct stasis_message *message)
-{
-	struct ast_channel_blob *obj = stasis_message_data(message);
-	generic_blob_handler(obj, handle_blob_dtmf);
-}
-
-static struct ast_json *handle_blob_userevent(struct ast_channel_blob *obj)
-{
-	return stasis_json_event_channel_userevent_create(obj->snapshot, obj->blob);
-}
-
-static void sub_userevent_handler(void *data,
-		struct stasis_subscription *sub,
-		struct stasis_topic *topic,
-		struct stasis_message *message)
-{
-	struct ast_channel_blob *obj = stasis_message_data(message);
-	generic_blob_handler(obj, handle_blob_userevent);
-}
-
-static struct ast_json *handle_blob_hangup_request(struct ast_channel_blob *obj)
-{
-	return stasis_json_event_channel_hangup_request_create(obj->snapshot, obj->blob);
-}
-
-static void sub_hangup_request_handler(void *data,
-		struct stasis_subscription *sub,
-		struct stasis_topic *topic,
-		struct stasis_message *message)
-{
-	struct ast_channel_blob *obj = stasis_message_data(message);
-	generic_blob_handler(obj, handle_blob_hangup_request);
-}
-
-static struct ast_json *handle_blob_varset(struct ast_channel_blob *obj)
-{
-	return stasis_json_event_channel_varset_create(obj->snapshot, obj->blob);
-}
-
-static void sub_varset_handler(void *data,
-		struct stasis_subscription *sub,
-		struct stasis_topic *topic,
-		struct stasis_message *message)
-{
-	struct ast_channel_blob *obj = stasis_message_data(message);
-	generic_blob_handler(obj, handle_blob_varset);
-}
-
 void stasis_app_ref(void)
 {
 	ast_module_ref(ast_module_info->self);
@@ -788,6 +733,30 @@
 	return 0;
 }
 
+static struct ast_json *simple_bridge_event(
+	const char *type,
+	struct ast_bridge_snapshot *snapshot,
+	const struct timeval *tv)
+{
+	return ast_json_pack("{s: s, s: o, s: o}",
+		"type", type,
+		"timestamp", ast_json_timeval(*tv, NULL),
+		"bridge", ast_bridge_snapshot_to_json(snapshot));
+}
+
+static struct ast_json *simple_bridge_channel_event(
+	const char *type,
+	struct ast_bridge_snapshot *bridge_snapshot,
+	struct ast_channel_snapshot *channel_snapshot,
+	const struct timeval *tv)
+{
+	return ast_json_pack("{s: s, s: o, s: o}",
+		"type", type,
+		"timestamp", ast_json_timeval(*tv, NULL),
+		"bridge", ast_bridge_snapshot_to_json(bridge_snapshot),
+		"channel", ast_channel_snapshot_to_json(channel_snapshot));
+}
+
 static void sub_bridge_snapshot_handler(void *data,
 		struct stasis_subscription *sub,
 		struct stasis_topic *topic,
@@ -797,6 +766,8 @@
 	struct stasis_cache_update *update = stasis_message_data(message);
 	struct ast_bridge_snapshot *new_snapshot = stasis_message_data(update->new_snapshot);
 	struct ast_bridge_snapshot *old_snapshot = stasis_message_data(update->old_snapshot);
+	const struct timeval *tv = update->new_snapshot ? stasis_message_timestamp(update->new_snapshot) : stasis_message_timestamp(message);
+
 	RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
 
 	watching_apps = get_apps_watching_bridge(new_snapshot ? new_snapshot->uniqueid : old_snapshot->uniqueid);
@@ -810,11 +781,11 @@
 		/* The bridge has gone away. Create the message, make sure no apps are
 		 * watching this bridge anymore, and destroy the bridge's control
 		 * structure */
-		msg = stasis_json_event_bridge_destroyed_create(old_snapshot);
+		msg = simple_bridge_event("BridgeDestroyed", old_snapshot, tv);
 		ao2_callback(watching_apps, OBJ_NODATA, remove_bridge_cb, bridge_id);
 		stasis_app_bridge_destroy(old_snapshot->uniqueid);
 	} else if (!old_snapshot) {
-		msg = stasis_json_event_bridge_created_create(old_snapshot);
+		msg = simple_bridge_event("BridgeCreated", old_snapshot, tv);
 	}
 
 	if (!msg) {
@@ -865,6 +836,7 @@
 	struct ast_bridge_merge_message *merge = stasis_message_data(message);
 	RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
 	RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
+	const struct timeval *tv = stasis_message_timestamp(message);
 
 	watching_apps_to = get_apps_watching_bridge(merge->to->uniqueid);
 	if (watching_apps_to) {
@@ -881,15 +853,15 @@
 		return;
 	}
 
-	/* The secondary bridge has to be packed into JSON by hand because the auto-generated

[... 8155 lines stripped ...]



More information about the asterisk-commits mailing list