This patch adds a RESTful HTTP interface to Asterisk.
authorDavid M. Lee <dlee@digium.com>
Mon, 22 Apr 2013 14:58:53 +0000 (14:58 +0000)
committerDavid M. Lee <dlee@digium.com>
Mon, 22 Apr 2013 14:58:53 +0000 (14:58 +0000)
The API itself is documented using Swagger, a lightweight mechanism for
documenting RESTful API's using JSON. This allows us to use swagger-ui
to provide executable documentation for the API, generate client
bindings in different languages, and generate a lot of the boilerplate
code for implementing the RESTful bindings. The API docs live in the
rest-api/ directory.

The RESTful bindings are generated from the Swagger API docs using a set
of Mustache templates.  The code generator is written in Python, and
uses Pystache. Pystache has no dependencies, and be installed easily
using pip. Code generation code lives in rest-api-templates/.

The generated code reduces a lot of boilerplate when it comes to
handling HTTP requests. It also helps us have greater consistency in the
REST API.

(closes issue ASTERISK-20891)
Review: https://reviewboard.asterisk.org/r/2376/

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@386232 65c4cc65-6c06-0410-ace0-fbb531ad65f3

63 files changed:
Makefile
configs/stasis_http.conf.sample [new file with mode: 0644]
include/asterisk/http.h
include/asterisk/json.h
include/asterisk/stasis_app.h
include/asterisk/stasis_http.h [new file with mode: 0644]
include/asterisk/strings.h
main/http.c
main/json.c
res/Makefile
res/res_stasis.c
res/res_stasis_http.c [new file with mode: 0644]
res/res_stasis_http.exports.in [new file with mode: 0644]
res/res_stasis_http_asterisk.c [new file with mode: 0644]
res/res_stasis_http_bridges.c [new file with mode: 0644]
res/res_stasis_http_channels.c [new file with mode: 0644]
res/res_stasis_http_endpoints.c [new file with mode: 0644]
res/res_stasis_http_events.c [new file with mode: 0644]
res/res_stasis_http_playback.c [new file with mode: 0644]
res/res_stasis_http_recordings.c [new file with mode: 0644]
res/res_stasis_http_sounds.c [new file with mode: 0644]
res/stasis_http.make [new file with mode: 0644]
res/stasis_http/resource_asterisk.c [new file with mode: 0644]
res/stasis_http/resource_asterisk.h [new file with mode: 0644]
res/stasis_http/resource_bridges.c [new file with mode: 0644]
res/stasis_http/resource_bridges.h [new file with mode: 0644]
res/stasis_http/resource_channels.c [new file with mode: 0644]
res/stasis_http/resource_channels.h [new file with mode: 0644]
res/stasis_http/resource_endpoints.c [new file with mode: 0644]
res/stasis_http/resource_endpoints.h [new file with mode: 0644]
res/stasis_http/resource_events.c [new file with mode: 0644]
res/stasis_http/resource_events.h [new file with mode: 0644]
res/stasis_http/resource_playback.c [new file with mode: 0644]
res/stasis_http/resource_playback.h [new file with mode: 0644]
res/stasis_http/resource_recordings.c [new file with mode: 0644]
res/stasis_http/resource_recordings.h [new file with mode: 0644]
res/stasis_http/resource_sounds.c [new file with mode: 0644]
res/stasis_http/resource_sounds.h [new file with mode: 0644]
rest-api-templates/README.txt [new file with mode: 0644]
rest-api-templates/asterisk_processor.py [new file with mode: 0644]
rest-api-templates/do-not-edit.mustache [new file with mode: 0644]
rest-api-templates/make_stasis_http_stubs.py [new file with mode: 0755]
rest-api-templates/odict.py [new file with mode: 0644]
rest-api-templates/res_stasis_http_resource.c.mustache [new file with mode: 0644]
rest-api-templates/rest_handler.mustache [new file with mode: 0644]
rest-api-templates/stasis_http.make.mustache [new file with mode: 0644]
rest-api-templates/stasis_http_resource.c.mustache [new file with mode: 0644]
rest-api-templates/stasis_http_resource.h.mustache [new file with mode: 0644]
rest-api-templates/swagger_model.py [new file with mode: 0644]
rest-api-templates/transform.py [new file with mode: 0644]
rest-api/README.txt [new file with mode: 0644]
rest-api/api-docs/asterisk.json [new file with mode: 0644]
rest-api/api-docs/bridges.json [new file with mode: 0644]
rest-api/api-docs/channels.json [new file with mode: 0644]
rest-api/api-docs/endpoints.json [new file with mode: 0644]
rest-api/api-docs/events.json [new file with mode: 0644]
rest-api/api-docs/playback.json [new file with mode: 0644]
rest-api/api-docs/recordings.json [new file with mode: 0644]
rest-api/api-docs/sounds.json [new file with mode: 0644]
rest-api/resources.json [new file with mode: 0644]
tests/test_stasis.c
tests/test_stasis_http.c [new file with mode: 0644]
tests/test_strings.c

index ff329d9..2d4c021 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -453,6 +453,9 @@ datafiles: _all doc/core-en_US.xml
                $(INSTALL) -m 644 $$x "$(DESTDIR)$(ASTDATADIR)/images" ; \
        done
        $(MAKE) -C sounds install
+       find rest-api -name "*.json" | while read x; do \
+               $(INSTALL) -m 644 $$x "$(DESTDIR)$(ASTDATADIR)/rest-api" ; \
+       done
 
 ifneq ($(GREP),)
   XML_core_en_US = $(foreach dir,$(MOD_SUBDIRS),$(shell $(GREP) -l "language=\"en_US\"" $(dir)/*.c $(dir)/*.cc 2>/dev/null))
@@ -537,8 +540,8 @@ INSTALLDIRS="$(ASTLIBDIR)" "$(ASTMODDIR)" "$(ASTSBINDIR)" "$(ASTETCDIR)" "$(ASTV
        "$(ASTLOGDIR)/cel-custom" "$(ASTDATADIR)" "$(ASTDATADIR)/documentation" \
        "$(ASTDATADIR)/documentation/thirdparty" "$(ASTDATADIR)/firmware" \
        "$(ASTDATADIR)/firmware/iax" "$(ASTDATADIR)/images" "$(ASTDATADIR)/keys" \
-       "$(ASTDATADIR)/phoneprov" "$(ASTDATADIR)/static-http" "$(ASTDATADIR)/sounds" \
-       "$(ASTDATADIR)/moh" "$(ASTMANDIR)/man8" "$(AGI_DIR)" "$(ASTDBDIR)"
+       "$(ASTDATADIR)/phoneprov" "$(ASTDATADIR)/rest-api" "$(ASTDATADIR)/static-http" \
+       "$(ASTDATADIR)/sounds" "$(ASTDATADIR)/moh" "$(ASTMANDIR)/man8" "$(AGI_DIR)" "$(ASTDBDIR)"
 
 installdirs:
        @for i in $(INSTALLDIRS); do \
@@ -958,6 +961,19 @@ menuselect-tree: $(foreach dir,$(filter-out main,$(MOD_SUBDIRS)),$(wildcard $(di
        @cat sounds/sounds.xml >> $@
        @echo "</menu>" >> $@
 
+# We don't want to require Python or Pystache for every build, so this is its
+# own target.
+stasis-stubs:
+ifeq ($(PYTHON),:)
+       @echo "--------------------------------------------------------------------------"
+       @echo "---        Please install python to build Stasis HTTP stubs            ---"
+       @echo "--------------------------------------------------------------------------"
+       @false
+else
+       $(PYTHON) rest-api-templates/make_stasis_http_stubs.py \
+               rest-api/resources.json res/
+endif
+
 .PHONY: menuselect
 .PHONY: main
 .PHONY: sounds
@@ -977,6 +993,7 @@ menuselect-tree: $(foreach dir,$(filter-out main,$(MOD_SUBDIRS)),$(wildcard $(di
 .PHONY: installdirs
 .PHONY: validate-docs
 .PHONY: _clean
+.PHONY: stasis-stubs
 .PHONY: $(SUBDIRS_INSTALL)
 .PHONY: $(SUBDIRS_DIST_CLEAN)
 .PHONY: $(SUBDIRS_CLEAN)
diff --git a/configs/stasis_http.conf.sample b/configs/stasis_http.conf.sample
new file mode 100644 (file)
index 0000000..1527a32
--- /dev/null
@@ -0,0 +1,25 @@
+[general]
+enabled = yes          ; When set to no, stasis-http support is disabled
+;pretty = no           ; When set to yes, responses from stasis-http are
+;                      ; formatted to be human readable
+;allowed_origins =     ; Comma separated list of allowed origins, for
+;                      ; Cross-Origin Resource Sharing. May be set to * to allow
+;                      ; all origins.
+
+;[user-username]
+;read_only = no                ; When set to yes, user is only authorized for
+;                      ; read-only requests
+;
+; If a password is specified, user must authenticate using HTTP Basic
+; authentication. If no password is specified, then the user may authenticate
+; simply by adding ?api_key=username to their requests.
+;
+;password =            ; Crypted or plaintext password (see crypt_password)
+;
+; crypt_password may be set to crypt (the default) or plain. When set to crypt,
+; crypt(3) is used to encrypt the password. A crypted password can be generated
+; using mkpasswd -m sha-512.
+;
+; When set to plain, the password is in plaintext
+;
+;crypt_password = plain
index 3400240..db424d3 100644 (file)
@@ -58,7 +58,10 @@ enum ast_http_method {
        AST_HTTP_GET = 0,
        AST_HTTP_POST,
        AST_HTTP_HEAD,
-       AST_HTTP_PUT,            /*!< Not supported in Asterisk */
+       AST_HTTP_PUT,
+       AST_HTTP_DELETE,
+       AST_HTTP_OPTIONS,
+       AST_HTTP_MAX_METHOD, /*!< Last entry in ast_http_method enum */
 };
 
 struct ast_http_uri;
index 62e2129..d06416f 100644 (file)
@@ -586,16 +586,33 @@ int ast_json_object_iter_set(struct ast_json *object, struct ast_json_iter *iter
 /*!@{*/
 
 /*!
+ * \brief Encoding format type.
+ * \since 12.0.0
+ */
+enum ast_json_encoding_format
+{
+       /*! Compact format, low human readability */
+       AST_JSON_COMPACT,
+       /*! Formatted for human readability */
+       AST_JSON_PRETTY,
+};
+
+#define ast_json_dump_string(root) ast_json_dump_string_format(root, AST_JSON_COMPACT)
+
+/*!
  * \brief Encode a JSON value to a string.
  * \since 12.0.0
  *
  * Returned string must be freed by calling ast_free().
  *
- * \param JSON value.
+ * \param root JSON value.
+ * \param format encoding format type.
  * \return String encoding of \a root.
  * \return \c NULL on error.
  */
-char *ast_json_dump_string(struct ast_json *root);
+char *ast_json_dump_string_format(struct ast_json *root, enum ast_json_encoding_format format);
+
+#define ast_json_dump_str(root, dst) ast_json_dump_str_format(root, dst, AST_JSON_COMPACT)
 
 /*!
  * \brief Encode a JSON value to an \ref ast_str.
@@ -605,10 +622,13 @@ char *ast_json_dump_string(struct ast_json *root);
  *
  * \param root JSON value.
  * \param dst \ref ast_str to store JSON encoding.
+ * \param format encoding format type.
  * \return 0 on success.
  * \return -1 on error. The contents of \a dst are undefined.
  */
-int ast_json_dump_str(struct ast_json *root, struct ast_str **dst);
+int ast_json_dump_str_format(struct ast_json *root, struct ast_str **dst, enum ast_json_encoding_format format);
+
+#define ast_json_dump_file(root, output) ast_json_dump_file_format(root, output, AST_JSON_COMPACT)
 
 /*!
  * \brief Encode a JSON value to a \c FILE.
@@ -616,10 +636,13 @@ int ast_json_dump_str(struct ast_json *root, struct ast_str **dst);
  *
  * \param root JSON value.
  * \param output File to write JSON encoding to.
+ * \param format encoding format type.
  * \return 0 on success.
  * \return -1 on error. The contents of \a output are undefined.
  */
-int ast_json_dump_file(struct ast_json *root, FILE *output);
+int ast_json_dump_file_format(struct ast_json *root, FILE *output, enum ast_json_encoding_format format);
+
+#define ast_json_dump_new_file(root, path) ast_json_dump_new_file_format(root, path, AST_JSON_COMPACT)
 
 /*!
  * \brief Encode a JSON value to a file at the given location.
@@ -627,10 +650,11 @@ int ast_json_dump_file(struct ast_json *root, FILE *output);
  *
  * \param root JSON value.
  * \param path Path to file to write JSON encoding to.
+ * \param format encoding format type.
  * \return 0 on success.
  * \return -1 on error. The contents of \a output are undefined.
  */
-int ast_json_dump_new_file(struct ast_json *root, const char *path);
+int ast_json_dump_new_file_format(struct ast_json *root, const char *path, enum ast_json_encoding_format format);
 
 #define AST_JSON_ERROR_TEXT_LENGTH    160
 #define AST_JSON_ERROR_SOURCE_LENGTH   80
index caec19b..a789e40 100644 (file)
@@ -66,7 +66,7 @@ struct ast_channel_snapshot;
  * \param argv Arguments for the application.
  */
 int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
-                   char *argv[]);
+       char *argv[]);
 
 /*! @} */
 
@@ -126,22 +126,50 @@ int stasis_app_send(const char *app_name, struct ast_json *message);
 struct stasis_app_control;
 
 /*!
- * \brief Returns the handler for the given channel
+ * \brief Returns the handler for the given channel.
  * \param chan Channel to handle.
- * \return NULL channel not in Stasis application
- * \return Pointer to stasis handler.
+ * \return NULL channel not in Stasis application.
+ * \return Pointer to \c res_stasis handler.
  */
 struct stasis_app_control *stasis_app_control_find_by_channel(
        const struct ast_channel *chan);
 
 /*!
- * \brief Exit \c app_stasis and continue execution in the dialplan.
+ * \brief Returns the handler for the channel with the given id.
+ * \param channel_id Uniqueid of the channel.
+ * \return NULL channel not in Stasis application, or channel does not exist.
+ * \return Pointer to \c res_stasis handler.
+ */
+struct stasis_app_control *stasis_app_control_find_by_channel_id(
+       const char *channel_id);
+
+/*!
+ * \brief Exit \c res_stasis and continue execution in the dialplan.
  *
- * If the channel is no longer in \c app_stasis, this function does nothing.
+ * If the channel is no longer in \c res_stasis, this function does nothing.
  *
- * \param handler Handler for \c app_stasis
+ * \param control Control for \c res_stasis
+ */
+void stasis_app_control_continue(struct stasis_app_control *control);
+
+/*!
+ * \brief Answer the channel associated with this control.
+ * \param control Control for \c res_stasis.
+ * \return 0 for success.
+ * \return -1 for error.
+ */
+int stasis_app_control_answer(struct stasis_app_control *control);
+
+/*! @} */
+
+/*! @{ */
+
+/*!
+ * \brief Build a JSON object from a \ref ast_channel_snapshot.
+ * \return JSON object representing channel snapshot.
+ * \return \c NULL on error
  */
-void stasis_app_control_continue(struct stasis_app_control *handler);
+struct ast_json *ast_channel_snapshot_to_json(const struct ast_channel_snapshot *snapshot);
 
 /*! @} */
 
diff --git a/include/asterisk/stasis_http.h b/include/asterisk/stasis_http.h
new file mode 100644 (file)
index 0000000..cc0ceee
--- /dev/null
@@ -0,0 +1,171 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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.
+ */
+
+#ifndef _ASTERISK_STASIS_HTTP_H
+#define _ASTERISK_STASIS_HTTP_H
+
+/*! \file
+ *
+ * \brief Stasis RESTful API hooks.
+ *
+ * This header file is used mostly as glue code between generated declarations
+ * and res_stasis_http.c.
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+#include "asterisk/http.h"
+#include "asterisk/json.h"
+#include "asterisk/http_websocket.h"
+
+struct stasis_http_response;
+
+/*!
+ * \brief Callback type for RESTful method handlers.
+ * \param get_params GET parameters from the HTTP request.
+ * \param path_vars Path variables from any wildcard path segments.
+ * \param headers HTTP headers from the HTTP requiest.
+ * \param[out] response The RESTful response.
+ */
+typedef void (*stasis_rest_callback)(struct ast_variable *get_params,
+                                    struct ast_variable *path_vars,
+                                    struct ast_variable *headers,
+                                    struct stasis_http_response *response);
+
+/*!
+ * \brief Handler for a single RESTful path segment.
+ */
+struct stasis_rest_handlers {
+       /*! Path segement to handle */
+       const char *path_segment;
+       /*! If true (non-zero), path_segment is a wildcard, and will match all values.
+        *
+        * Value of the segement will be passed into the \a path_vars parameter of the callback.
+        */
+       int is_wildcard;
+       /*! Callbacks for all handled HTTP methods. */
+       stasis_rest_callback callbacks[AST_HTTP_MAX_METHOD];
+       /*! Number of children in the children array */
+       size_t num_children;
+       /*! Handlers for sub-paths */
+       struct stasis_rest_handlers *children[];
+};
+
+/*!
+ * Response type for RESTful requests
+ */
+struct stasis_http_response {
+       /*! Response message */
+       struct ast_json *message;
+       /*! \r\n seperated response headers */
+       struct ast_str *headers;
+       /*! HTTP response code.
+        * See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html */
+       int response_code;
+       /*! Corresponding text for the response code */
+       const char *response_text; // Shouldn't http.c handle this?
+};
+
+/*!
+ * Add a resource for REST handling.
+ * \param handler Handler to add.
+ * \return 0 on success.
+ * \return non-zero on failure.
+ */
+int stasis_http_add_handler(struct stasis_rest_handlers *handler);
+
+/*!
+ * Remove a resource for REST handling.
+ * \param handler Handler to add.
+ * \return 0 on success.
+ * \return non-zero on failure.
+ */
+int stasis_http_remove_handler(struct stasis_rest_handlers *handler);
+
+/*!
+ * \internal
+ * \brief Stasis RESTful invocation handler.
+ *
+ * Only call from res_stasis_http and test_stasis_http. Only public to allow
+ * for unit testing.
+ *
+ * \param uri HTTP URI, relative to the API path.
+ * \param method HTTP method.
+ * \param get_params HTTP \c GET parameters.
+ * \param headers HTTP headers.
+ * \param[out] response RESTful HTTP response.
+ */
+void stasis_http_invoke(const char *uri, enum ast_http_method method, struct ast_variable *get_params,
+                       struct ast_variable *headers, struct stasis_http_response *response);
+
+/*!
+ * \internal
+ * \brief Service function for API declarations.
+ *
+ * Only call from res_stasis_http and test_stasis_http. Only public to allow
+ * for unit testing.
+ *
+ * \param uri Requested URI, relative to the docs path.
+ * \param headers HTTP headers.
+ * \param[out] response RESTful HTTP response.
+ */
+void stasis_http_get_docs(const char *uri, struct ast_variable *headers, struct stasis_http_response *response);
+
+/*!
+ * \internal
+ * \brief Stasis WebSocket connection handler
+ * \param session WebSocket session.
+ * \param parameters HTTP \c GET parameters.
+ * \param headers HTTP headers.
+ */
+void stasis_websocket_callback(struct ast_websocket *session, struct ast_variable *parameters, struct ast_variable *headers);
+
+/*!
+ * \brief Fill in an error \a stasis_http_response.
+ * \param response Response to fill in.
+ * \param response_code HTTP response code.
+ * \param response_text Text corresponding to the HTTP response code.
+ * \param message_fmt Error message format string.
+ */
+void stasis_http_response_error(struct stasis_http_response *response,
+                               int response_code,
+                               const char *response_text,
+                               const char *message_fmt, ...)
+__attribute__((format(printf, 4, 5)));
+
+/*!
+ * \brief Fill in an \c OK (200) \a stasis_http_response.
+ * \param response Response to fill in.
+ * \param message JSON response.  This reference is stolen, so just \ref
+ *                ast_json_incref if you need to keep a reference to it.
+ */
+void stasis_http_response_ok(struct stasis_http_response *response,
+                            struct ast_json *message);
+
+/*!
+ * \brief Fill in a <tt>No Content</tt> (204) \a stasis_http_response.
+ */
+void stasis_http_response_no_content(struct stasis_http_response *response);
+
+/*!
+ * \brief Fill in \a response with a 500 message for allocation failures.
+ * \param response Response to fill in.
+ */
+void stasis_http_response_alloc_failed(struct stasis_http_response *response);
+
+#endif /* _ASTERISK_STASIS_HTTP_H */
index d16e9f7..967eb82 100644 (file)
@@ -82,6 +82,48 @@ static force_inline int attribute_pure ast_strlen_zero(const char *s)
  */
 #define S_COR(a, b, c) ({typeof(&((b)[0])) __x = (b); (a) && !ast_strlen_zero(__x) ? (__x) : (c);})
 
+/*
+  \brief Checks whether a string begins with another.
+  \since 12.0.0
+  \param str String to check.
+  \param prefix Prefix to look for.
+  \param 1 if \a str begins with \a prefix, 0 otherwise.
+ */
+static int force_inline attribute_pure ast_begins_with(const char *str, const char *prefix)
+{
+       ast_assert(str != NULL);
+       ast_assert(prefix != NULL);
+       while (*str == *prefix && *prefix != '\0') {
+               ++str;
+               ++prefix;
+       }
+       return *prefix == '\0';
+}
+
+/*
+  \brief Checks whether a string ends with another.
+  \since 12.0.0
+  \param str String to check.
+  \param suffix Suffix to look for.
+  \param 1 if \a str ends with \a suffix, 0 otherwise.
+ */
+static int force_inline attribute_pure ast_ends_with(const char *str, const char *suffix)
+{
+       size_t str_len;
+       size_t suffix_len;
+
+       ast_assert(str != NULL);
+       ast_assert(suffix != NULL);
+       str_len = strlen(str);
+       suffix_len = strlen(suffix);
+
+       if (suffix_len > str_len) {
+               return 0;
+       }
+
+       return strcmp(str + str_len - suffix_len, suffix) == 0;
+}
+
 /*!
  * \brief return Yes or No depending on the argument.
  *
index aff38c3..ec9517f 100644 (file)
@@ -153,6 +153,8 @@ static const struct ast_cfhttp_methods_text {
        { AST_HTTP_POST,        "POST" },
        { AST_HTTP_HEAD,        "HEAD" },
        { AST_HTTP_PUT,         "PUT" },
+       { AST_HTTP_DELETE,      "DELETE" },
+       { AST_HTTP_OPTIONS,     "OPTIONS" },
 };
 
 const char *ast_get_http_method(enum ast_http_method method)
@@ -897,6 +899,10 @@ static void *httpd_helper_thread(void *data)
                http_method = AST_HTTP_HEAD;
        } else if (!strcasecmp(method,"PUT")) {
                http_method = AST_HTTP_PUT;
+       } else if (!strcasecmp(method,"DELETE")) {
+               http_method = AST_HTTP_DELETE;
+       } else if (!strcasecmp(method,"OPTIONS")) {
+               http_method = AST_HTTP_OPTIONS;
        }
 
        uri = ast_skip_blanks(uri);     /* Skip white space */
index 2aa5387..1ff5638 100644 (file)
@@ -338,20 +338,15 @@ int ast_json_object_iter_set(struct ast_json *object, struct ast_json_iter *iter
 /*!
  * \brief Default flags for JSON encoding.
  */
-static size_t dump_flags(void)
+static size_t dump_flags(enum ast_json_encoding_format format)
 {
-       /* There's a chance this could become a runtime flag */
-       int flags = JSON_COMPACT;
-#ifdef AST_DEVMODE
-       /* In dev mode, write readable JSON */
-       flags = JSON_INDENT(2) | JSON_PRESERVE_ORDER;
-#endif
-       return flags;
+       return format == AST_JSON_PRETTY ?
+               JSON_INDENT(2) | JSON_PRESERVE_ORDER : JSON_COMPACT;
 }
 
-char *ast_json_dump_string(struct ast_json *root)
+char *ast_json_dump_string_format(struct ast_json *root, enum ast_json_encoding_format format)
 {
-       return json_dumps((json_t *)root, dump_flags());
+       return json_dumps((json_t *)root, dump_flags(format));
 }
 
 static int write_to_ast_str(const char *buffer, size_t size, void *data)
@@ -385,25 +380,25 @@ static int write_to_ast_str(const char *buffer, size_t size, void *data)
        return 0;
 }
 
-int ast_json_dump_str(struct ast_json *root, struct ast_str **dst)
+int ast_json_dump_str_format(struct ast_json *root, struct ast_str **dst, enum ast_json_encoding_format format)
 {
-       return json_dump_callback((json_t *)root, write_to_ast_str, dst, dump_flags());
+       return json_dump_callback((json_t *)root, write_to_ast_str, dst, dump_flags(format));
 }
 
 
-int ast_json_dump_file(struct ast_json *root, FILE *output)
+int ast_json_dump_file_format(struct ast_json *root, FILE *output, enum ast_json_encoding_format format)
 {
        if (!root || !output) {
                return -1;
        }
-       return json_dumpf((json_t *)root, output, dump_flags());
+       return json_dumpf((json_t *)root, output, dump_flags(format));
 }
-int ast_json_dump_new_file(struct ast_json *root, const char *path)
+int ast_json_dump_new_file_format(struct ast_json *root, const char *path, enum ast_json_encoding_format format)
 {
        if (!root || !path) {
                return -1;
        }
-       return json_dump_file((json_t *)root, path, dump_flags());
+       return json_dump_file((json_t *)root, path, dump_flags(format));
 }
 
 /*!
index 53aeeaf..fec20a2 100644 (file)
@@ -67,4 +67,7 @@ endif
 ael/pval.o: ael/pval.c
 
 clean::
-       rm -f snmp/*.o snmp/*.i ael/*.o ael/*.i ais/*.o ais/*.i
+       rm -f snmp/*.[oi] ael/*.[oi] ais/*.[oi] stasis_http/*.[oi]
+
+# Dependencies for res_stasis_http_*.so are generated, so they're in this file
+include stasis_http.make
index 3527ada..0453625 100644 (file)
@@ -40,6 +40,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/stasis_channels.h"
 #include "asterisk/strings.h"
 
+/*! Time to wait for a frame in the application */
+#define MAX_WAIT_MS 200
+
 /*!
  * \brief Number of buckets for the Stasis application hash table.  Remember to
  * keep it a prime number!
@@ -147,7 +150,67 @@ static void app_send(struct app *app, struct ast_json *message)
        app->handler(app->data, app->name, message);
 }
 
+typedef void* (*stasis_app_command_cb)(struct stasis_app_control *control,
+                                      struct ast_channel *chan,
+                                      void *data);
+
+struct stasis_app_command {
+       ast_mutex_t lock;
+       ast_cond_t condition;
+       stasis_app_command_cb callback;
+       void *data;
+       void *retval;
+       int is_done:1;
+};
+
+static void command_dtor(void *obj)
+{
+       struct stasis_app_command *command = obj;
+       ast_mutex_destroy(&command->lock);
+       ast_cond_destroy(&command->condition);
+}
+
+static struct stasis_app_command *command_create(stasis_app_command_cb callback,
+                                                void *data)
+{
+       RAII_VAR(struct stasis_app_command *, command, NULL, ao2_cleanup);
+
+       command = ao2_alloc(sizeof(*command), command_dtor);
+       if (!command) {
+               return NULL;
+       }
+
+       ast_mutex_init(&command->lock);
+       ast_cond_init(&command->condition, 0);
+       command->callback = callback;
+       command->data = data;
+
+       ao2_ref(command, +1);
+       return command;
+}
+
+static void command_complete(struct stasis_app_command *command, void *retval)
+{
+       SCOPED_MUTEX(lock, &command->lock);
+
+       command->is_done = 1;
+       command->retval = retval;
+       ast_cond_signal(&command->condition);
+}
+
+static void *wait_for_command(struct stasis_app_command *command)
+{
+       SCOPED_MUTEX(lock, &command->lock);
+       while (!command->is_done) {
+               ast_cond_wait(&command->condition, &command->lock);
+       }
+
+       return command->retval;
+}
+
 struct stasis_app_control {
+       /*! Queue of commands to dispatch on the channel */
+       struct ao2_container *command_queue;
        /*!
         * When set, /c app_stasis should exit and continue in the dialplan.
         */
@@ -167,11 +230,24 @@ static struct stasis_app_control *control_create(const char *uniqueid)
                return NULL;
        }
 
+       control->command_queue = ao2_container_alloc_list(0, 0, NULL, NULL);
+
        strncpy(control->channel_id, uniqueid, size - sizeof(*control));
 
        return control;
 }
 
+static void *exec_command(struct stasis_app_control *control,
+                         struct stasis_app_command *command)
+{
+        ao2_lock(control);
+       ao2_ref(command, +1);
+       ao2_link(control->command_queue, command);
+        ao2_unlock(control);
+
+       return wait_for_command(command);
+}
+
 /*! AO2 hash function for \ref stasis_app_control */
 static int control_hash(const void *obj, const int flags)
 {
@@ -199,13 +275,20 @@ static int control_compare(void *lhs, void *rhs, int flags)
 struct stasis_app_control *stasis_app_control_find_by_channel(
        const struct ast_channel *chan)
 {
-       RAII_VAR(struct ao2_container *, controls, NULL, ao2_cleanup);
        if (chan == NULL) {
                return NULL;
        }
 
+       return stasis_app_control_find_by_channel_id(
+               ast_channel_uniqueid(chan));
+}
+
+struct stasis_app_control *stasis_app_control_find_by_channel_id(
+       const char *channel_id)
+{
+       RAII_VAR(struct ao2_container *, controls, NULL, ao2_cleanup);
        controls = app_controls();
-       return ao2_find(controls, ast_channel_uniqueid(chan), OBJ_KEY);
+       return ao2_find(controls, channel_id, OBJ_KEY);
 }
 
 /*!
@@ -233,6 +316,33 @@ void stasis_app_control_continue(struct stasis_app_control *control)
        control->continue_to_dialplan = 1;
 }
 
+static int OK = 0;
+static int FAIL = -1;
+
+static void *__app_control_answer(struct stasis_app_control *control,
+                                 struct ast_channel *chan, void *data)
+{
+       ast_debug(3, "%s: Answering", control->channel_id);
+       return __ast_answer(chan, 0, 1) == 0 ? &OK : &FAIL;
+}
+
+int stasis_app_control_answer(struct stasis_app_control *control)
+{
+       RAII_VAR(struct stasis_app_command *, command, NULL, ao2_cleanup);
+       int *retval;
+
+       ast_debug(3, "%s: Sending answer command\n", control->channel_id);
+
+       command = command_create(__app_control_answer, NULL);
+       retval = exec_command(control, command);
+
+       if (*retval != 0) {
+               ast_log(LOG_WARNING, "Failed to answer channel");
+       }
+
+       return *retval;
+}
+
 static struct ast_json *app_event_create(
        const char *event_name,
        const struct ast_channel_snapshot *snapshot,
@@ -410,6 +520,26 @@ static void control_unlink(struct stasis_app_control *control)
        ao2_cleanup(control);
 }
 
+static void dispatch_commands(struct stasis_app_control *control,
+                             struct ast_channel *chan)
+{
+       struct ao2_iterator i;
+       void *obj;
+
+        SCOPED_AO2LOCK(lock, control);
+
+       i = ao2_iterator_init(control->command_queue, AO2_ITERATOR_UNLINK);
+
+       while ((obj = ao2_iterator_next(&i))) {
+               RAII_VAR(struct stasis_app_command *, command, obj, ao2_cleanup);
+               void *retval = command->callback(control, chan, command->data);
+               command_complete(command, retval);
+       }
+
+       ao2_iterator_destroy(&i);
+}
+
+
 /*! /brief Stasis dialplan application callback */
 int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
                    char *argv[])
@@ -458,8 +588,38 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
                return res;
        }
 
-       while (!hungup && !control_continue_test_and_reset(control) && ast_waitfor(chan, -1) > -1) {
-               RAII_VAR(struct ast_frame *, f, ast_read(chan), ast_frame_dtor);
+       while (1) {
+               RAII_VAR(struct ast_frame *, f, NULL, ast_frame_dtor);
+               int r;
+
+               if (hungup) {
+                       ast_debug(3, "%s: Hangup\n",
+                                 ast_channel_uniqueid(chan));
+                       break;
+               }
+
+               if (control_continue_test_and_reset(control)) {
+                       ast_debug(3, "%s: Continue\n",
+                                 ast_channel_uniqueid(chan));
+                       break;
+               }
+
+               r = ast_waitfor(chan, MAX_WAIT_MS);
+
+               if (r < 0) {
+                       ast_debug(3, "%s: Poll error\n",
+                                 ast_channel_uniqueid(chan));
+                       break;
+               }
+
+               dispatch_commands(control, chan);
+
+               if (r == 0) {
+                       /* Timeout */
+                       continue;
+               }
+
+               f = ast_read(chan);
                if (!f) {
                        ast_debug(3, "%s: No more frames. Must be done, I guess.\n", ast_channel_uniqueid(chan));
                        break;
@@ -468,8 +628,6 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
                switch (f->frametype) {
                case AST_FRAME_CONTROL:
                        if (f->subclass.integer == AST_CONTROL_HANGUP) {
-                               ast_debug(3, "%s: Received hangup\n",
-                                         ast_channel_uniqueid(chan));
                                hungup = 1;
                        }
                        break;
diff --git a/res/res_stasis_http.c b/res/res_stasis_http.c
new file mode 100644 (file)
index 0000000..aa9c8a8
--- /dev/null
@@ -0,0 +1,932 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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 HTTP binding for the Stasis API
+ * \author David M. Lee, II <dlee@digium.com>
+ *
+ * The API itself is documented using <a
+ * href="https://developers.helloreverb.com/swagger/">Swagger</a>, a lightweight
+ * mechanism for documenting RESTful API's using JSON. This allows us to use <a
+ * href="https://github.com/wordnik/swagger-ui">swagger-ui</a> to provide
+ * executable documentation for the API, generate client bindings in different
+ * <a href="https://github.com/asterisk/asterisk_rest_libraries">languages</a>,
+ * and generate a lot of the boilerplate code for implementing the RESTful
+ * bindings. The API docs live in the \c rest-api/ directory.
+ *
+ * The RESTful bindings are generated from the Swagger API docs using a set of
+ * <a href="http://mustache.github.io/mustache.5.html">Mustache</a> templates.
+ * The code generator is written in Python, and uses the Python implementation
+ * <a href="https://github.com/defunkt/pystache">pystache</a>. Pystache has no
+ * dependencies, and be installed easily using \c pip. Code generation code
+ * lives in \c rest-api-templates/.
+ *
+ * The generated code reduces a lot of boilerplate when it comes to handling
+ * HTTP requests. It also helps us have greater consistency in the REST API.
+ *
+ * The structure of the generated code is:
+ *
+ *  - res/stasis_http/resource_{resource}.h
+ *    - For each operation in the resouce, a generated argument structure
+ *      (holding the parsed arguments from the request) and function
+ *      declarations (to implement in res/stasis_http/resource_{resource}.c)
+ *  - res_stasis_http_{resource}.c
+ *    - A set of \ref stasis_rest_callback functions, which glue the two
+ *      together. They parse out path variables and request parameters to
+ *      populate a specific \c *_args which is passed to the specific request
+ *      handler (in res/stasis_http/resource_{resource}.c)
+ *    - A tree of \ref stasis_rest_handlers for routing requests to its
+ *      \ref stasis_rest_callback
+ *
+ * The basic flow of an HTTP request is:
+ *
+ *  - stasis_http_callback()
+ *    1. Initial request validation
+ *    2. Routes as either a doc request (stasis_http_get_docs) or API
+ *       request (stasis_http_invoke)
+ *       - stasis_http_invoke()
+ *         1. Further request validation
+ *         2. Routes the request through the tree of generated
+ *            \ref stasis_rest_handlers.
+ *         3. Dispatch to the generated callback
+ *            - \c stasis_http_*_cb
+ *              1. Populate \c *_args struct with path and get params
+ *              2. Invoke the request handler
+ *    3. Validates and sends response
+ */
+
+/*** MODULEINFO
+       <depend type="module">app_stasis</depend>
+       <support_level>core</support_level>
+ ***/
+
+/*** DOCUMENTATION
+       <configInfo name="res_stasis_http" language="en_US">
+               <synopsis>HTTP binding for the Stasis API</synopsis>
+               <configFile name="stasis_http.conf">
+                       <configObject name="global">
+                               <synopsis>Global configuration settings</synopsis>
+                               <configOption name="enabled">
+                                       <synopsis>Enable/disable the stasis-http module</synopsis>
+                               </configOption>
+                               <configOption name="pretty">
+                                       <synopsis>Responses from stasis-http are formatted to be human readable</synopsis>
+                               </configOption>
+                       </configObject>
+               </configFile>
+       </configInfo>
+***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/module.h"
+#include "asterisk/paths.h"
+#include "asterisk/stasis_http.h"
+#include "asterisk/config_options.h"
+
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+/*! \brief Global configuration options for stasis http. */
+struct conf_global_options {
+       /*! Enabled by default, disabled if false. */
+       int enabled:1;
+       /*! Encoding format used during output (default compact). */
+       enum ast_json_encoding_format format;
+};
+
+/*! \brief All configuration options for stasis http. */
+struct conf {
+       /*! The general section configuration options. */
+       struct conf_global_options *global;
+};
+
+/*! \brief Locking container for safe configuration access. */
+static AO2_GLOBAL_OBJ_STATIC(confs);
+
+/*! \brief Mapping of the stasis http conf struct's globals to the
+ *         general context in the config file. */
+static struct aco_type global_option = {
+       .type = ACO_GLOBAL,
+       .name = "global",
+       .item_offset = offsetof(struct conf, global),
+       .category = "^general$",
+       .category_match = ACO_WHITELIST
+};
+
+static struct aco_type *global_options[] = ACO_TYPES(&global_option);
+
+/*! \brief Disposes of the stasis http conf object */
+static void conf_destructor(void *obj)
+{
+    struct conf *cfg = obj;
+    ao2_cleanup(cfg->global);
+}
+
+/*! \brief Creates the statis http conf object. */
+static void *conf_alloc(void)
+{
+    struct conf *cfg;
+
+    if (!(cfg = ao2_alloc(sizeof(*cfg), conf_destructor))) {
+        return NULL;
+    }
+
+    if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), NULL))) {
+        ao2_ref(cfg, -1);
+        return NULL;
+    }
+    return cfg;
+}
+
+/*! \brief The conf file that's processed for the module. */
+static struct aco_file conf_file = {
+       /*! The config file name. */
+       .filename = "stasis_http.conf",
+       /*! The mapping object types to be processed. */
+       .types = ACO_TYPES(&global_option),
+};
+
+CONFIG_INFO_STANDARD(cfg_info, confs, conf_alloc,
+                    .files = ACO_FILES(&conf_file));
+
+/*! \brief Bitfield handler since it is not possible to take address. */
+static int conf_bitfield_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+       struct conf_global_options *global = obj;
+
+       if (!strcasecmp(var->name, "enabled")) {
+               global->enabled = ast_true(var->value);
+       } else {
+               return -1;
+       }
+
+       return 0;
+}
+
+/*! \brief Encoding format handler converts from boolean to enum. */
+static int encoding_format_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+       struct conf_global_options *global = obj;
+
+       if (!strcasecmp(var->name, "pretty")) {
+               global->format = ast_true(var->value) ? AST_JSON_PRETTY : AST_JSON_COMPACT;
+       } else {
+               return -1;
+       }
+
+       return 0;
+}
+
+/*! \brief Helper function to check if module is enabled. */
+static char is_enabled(void)
+{
+       RAII_VAR(struct conf *, cfg, ao2_global_obj_ref(confs), ao2_cleanup);
+
+       return cfg->global->enabled;
+}
+
+/*! Lock for \ref root_handler */
+static ast_mutex_t root_handler_lock;
+
+/*! Handler for root RESTful resource. */
+static struct stasis_rest_handlers *root_handler;
+
+/*! Pre-defined message for allocation failures. */
+static struct ast_json *alloc_failed_message;
+
+int stasis_http_add_handler(struct stasis_rest_handlers *handler)
+{
+       RAII_VAR(struct stasis_rest_handlers *, new_handler, NULL, ao2_cleanup);
+       size_t old_size, new_size;
+
+       SCOPED_MUTEX(lock, &root_handler_lock);
+
+       old_size = sizeof(*new_handler) +
+               root_handler->num_children * sizeof(handler);
+       new_size = old_size + sizeof(handler);
+
+       new_handler = ao2_alloc(new_size, NULL);
+       if (!new_handler) {
+               return -1;
+       }
+       memcpy(new_handler, root_handler, old_size);
+       new_handler->children[new_handler->num_children++] = handler;
+
+       ao2_cleanup(root_handler);
+       ao2_ref(new_handler, +1);
+       root_handler = new_handler;
+       return 0;
+}
+
+int stasis_http_remove_handler(struct stasis_rest_handlers *handler)
+{
+       RAII_VAR(struct stasis_rest_handlers *, new_handler, NULL, ao2_cleanup);
+       size_t old_size, new_size, i, j;
+
+       SCOPED_MUTEX(lock, &root_handler_lock);
+       old_size = sizeof(*new_handler) +
+               root_handler->num_children * sizeof(handler);
+       new_size = old_size - sizeof(handler);
+
+       new_handler = ao2_alloc(new_size, NULL);
+       if (!new_handler) {
+               return -1;
+       }
+       memcpy(new_handler, root_handler, sizeof(*new_handler));
+
+       for (i = 0, j = 0; i < root_handler->num_children; ++i) {
+               if (root_handler->children[i] == handler) {
+                       continue;
+               }
+               new_handler->children[j++] = root_handler->children[i];
+       }
+       new_handler->num_children = j;
+
+       ao2_cleanup(root_handler);
+       ao2_ref(new_handler, +1);
+       root_handler = new_handler;
+       return 0;
+}
+
+static struct stasis_rest_handlers *get_root_handler(void)
+{
+       SCOPED_MUTEX(lock, &root_handler_lock);
+       ao2_ref(root_handler, +1);
+       return root_handler;
+}
+
+static struct stasis_rest_handlers *root_handler_create(void)
+{
+       RAII_VAR(struct stasis_rest_handlers *, handler, NULL, ao2_cleanup);
+
+       handler = ao2_alloc(sizeof(*handler), NULL);
+       if (!handler) {
+               return NULL;
+       }
+       handler->path_segment = "stasis";
+
+       ao2_ref(handler, +1);
+       return handler;
+}
+
+void stasis_http_response_error(struct stasis_http_response *response,
+                               int response_code,
+                               const char *response_text,
+                               const char *message_fmt, ...)
+{
+       RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
+       va_list ap;
+
+       va_start(ap, message_fmt);
+       message = ast_json_vstringf(message_fmt, ap);
+       response->message = ast_json_pack("{s: o}",
+                                         "message", ast_json_ref(message));
+       response->response_code = response_code;
+       response->response_text = response_text;
+}
+
+void stasis_http_response_ok(struct stasis_http_response *response,
+                            struct ast_json *message)
+{
+       response->message = message;
+       response->response_code = 200;
+       response->response_text = "OK";
+}
+
+void stasis_http_response_no_content(struct stasis_http_response *response)
+{
+       response->message = NULL;
+       response->response_code = 204;
+       response->response_text = "No Content";
+}
+
+void stasis_http_response_alloc_failed(struct stasis_http_response *response)
+{
+       response->message = ast_json_ref(alloc_failed_message);
+       response->response_code = 500;
+       response->response_text = "Internal Server Error";
+}
+
+static void add_allow_header(struct stasis_rest_handlers *handler,
+                            struct stasis_http_response *response)
+{
+       enum ast_http_method m;
+       ast_str_append(&response->headers, 0,
+                      "Allow: OPTIONS");
+       for (m = 0; m < AST_HTTP_MAX_METHOD; ++m) {
+               if (handler->callbacks[m] != NULL) {
+                       ast_str_append(&response->headers, 0,
+                                      ",%s", ast_get_http_method(m));
+               }
+       }
+       ast_str_append(&response->headers, 0, "\r\n");
+}
+
+#define ACR_METHOD "Access-Control-Request-Method"
+#define ACR_HEADERS "Access-Control-Request-Headers"
+#define ACA_METHODS "Access-Control-Allow-Methods"
+#define ACA_HEADERS "Access-Control-Allow-Headers"
+
+/*!
+ * \brief Handle OPTIONS request, mainly for CORS preflight requests.
+ *
+ * Some browsers will send this prior to non-simple methods (i.e. DELETE).
+ * See http://www.w3.org/TR/cors/ for the spec. Especially section 6.2.
+ */
+static void handle_options(struct stasis_rest_handlers *handler,
+                          struct ast_variable *headers,
+                          struct stasis_http_response *response)
+{
+       struct ast_variable *header;
+       char const *acr_method = NULL;
+       char const *acr_headers = NULL;
+       char const *origin = NULL;
+
+       RAII_VAR(struct ast_str *, allow, NULL, ast_free);
+       enum ast_http_method m;
+       int allowed = 0;
+
+       /* Regular OPTIONS response */
+       add_allow_header(handler, response);
+       response->response_code = 204;
+       response->response_text = "No Content";
+       response->message = NULL;
+
+       /* Parse CORS headers */
+       for (header = headers; header != NULL; header = header->next) {
+               if (strcmp(ACR_METHOD, header->name) == 0) {
+                       acr_method = header->value;
+               } else if (strcmp(ACR_HEADERS, header->name) == 0) {
+                       acr_headers = header->value;
+               } else if (strcmp("Origin", header->name) == 0) {
+                       origin = header->value;
+               }
+       }
+
+       /* CORS 6.2, #1 - "If the Origin header is not present terminate this
+        * set of steps.
+        */
+       if (origin == NULL) {
+               return;
+       }
+
+       /* CORS 6.2, #2 - "If the value of the Origin header is not a
+        * case-sensitive match for any of the values in list of origins do not
+        * set any additional headers and terminate this set of steps.
+        *
+        * "Always matching is acceptable since the list of origins can be
+        * unbounded.
+        *
+        * "The Origin header can only contain a single origin as the user agent
+        * will not follow redirects.
+        *
+        * TODO - pull list of allowed origins from config
+        */
+
+       /* CORS 6.2, #3 - "If there is no Access-Control-Request-Method header
+        * or if parsing failed, do not set any additional headers and terminate
+        * this set of steps."
+        */
+       if (acr_method == NULL) {
+               return;
+       }
+
+       /* CORS 6.2, #4 - "If there are no Access-Control-Request-Headers
+        * headers let header field-names be the empty list."
+        */
+       if (acr_headers == NULL) {
+               acr_headers = "";
+       }
+
+       /* CORS 6.2, #5 - "If method is not a case-sensitive match for any of
+        * the values in list of methods do not set any additional headers and
+        * terminate this set of steps."
+        */
+       allow = ast_str_create(20);
+
+       if (!allow) {
+               stasis_http_response_alloc_failed(response);
+               return;
+       }
+
+       /* Go ahead and build the ACA_METHODS header at the same time */
+       for (m = 0; m < AST_HTTP_MAX_METHOD; ++m) {
+               if (handler->callbacks[m] != NULL) {
+                       char const *m_str = ast_get_http_method(m);
+                       if (strcmp(m_str, acr_method) == 0) {
+                               allowed = 1;
+                       }
+                       ast_str_append(&allow, 0, ",%s", m_str);
+               }
+       }
+
+       if (!allowed) {
+               return;
+       }
+
+       /* CORS 6.2 #6 - "If any of the header field-names is not a ASCII
+        * case-insensitive match for any of the values in list of headers do
+        * not set any additional headers and terminate this set of steps.
+        *
+        * "Note: Always matching is acceptable since the list of headers can be
+        * unbounded."
+        */
+
+       /* CORS 6.2 #7 - "If the resource supports credentials add a single
+        * Access-Control-Allow-Origin header, with the value of the Origin
+        * header as value, and add a single Access-Control-Allow-Credentials
+        * header with the case-sensitive string "true" as value."
+        *
+        * Added by process_cors_request() earlier in the request.
+        */
+
+       /* CORS 6.2 #8 - "Optionally add a single Access-Control-Max-Age
+        * header..."
+        */
+
+       /* CORS 6.2 #9 - "Add one or more Access-Control-Allow-Methods headers
+        * consisting of (a subset of) the list of methods."
+        */
+       ast_str_append(&response->headers, 0, "%s: OPTIONS,%s\r\n",
+                      ACA_METHODS, ast_str_buffer(allow));
+
+
+       /* CORS 6.2, #10 - "Add one or more Access-Control-Allow-Headers headers
+        * consisting of (a subset of) the list of headers.
+        *
+        * "Since the list of headers can be unbounded simply returning headers
+        * can be enough."
+        */
+       if (!ast_strlen_zero(acr_headers)) {
+               ast_str_append(&response->headers, 0, "%s: %s\r\n",
+                              ACA_HEADERS, acr_headers);
+       }
+}
+
+void stasis_http_invoke(const char *uri,
+                       enum ast_http_method method,
+                       struct ast_variable *get_params,
+                       struct ast_variable *headers,
+                       struct stasis_http_response *response)
+{
+       RAII_VAR(char *, response_text, NULL, ast_free);
+       RAII_VAR(struct stasis_rest_handlers *, root, NULL, ao2_cleanup);
+       struct stasis_rest_handlers *handler;
+       struct ast_variable *path_vars = NULL;
+       char *path = ast_strdupa(uri);
+       const char *path_segment;
+       stasis_rest_callback callback;
+
+       root = handler = get_root_handler();
+       ast_assert(root != NULL);
+
+       while ((path_segment = strsep(&path, "/")) && (strlen(path_segment) > 0)) {
+               struct stasis_rest_handlers *found_handler = NULL;
+               int i;
+               ast_debug(3, "Finding handler for %s\n", path_segment);
+               for (i = 0; found_handler == NULL && i < handler->num_children; ++i) {
+                       struct stasis_rest_handlers *child = handler->children[i];
+
+                       ast_debug(3, "  Checking %s\n", child->path_segment);
+                       if (child->is_wildcard) {
+                               /* Record the path variable */
+                               struct ast_variable *path_var = ast_variable_new(child->path_segment, path_segment, __FILE__);
+                               path_var->next = path_vars;
+                               path_vars = path_var;
+                               found_handler = child;
+                       } else if (strcmp(child->path_segment, path_segment) == 0) {
+                               found_handler = child;
+                       }
+               }
+
+               if (found_handler == NULL) {
+                       /* resource not found */
+                       ast_debug(3, "  Handler not found\n");
+                       stasis_http_response_error(
+                               response, 404, "Not Found",
+                               "Resource not found");
+                       return;
+               } else {
+                       ast_debug(3, "  Got it!\n");
+                       handler = found_handler;
+               }
+       }
+
+       ast_assert(handler != NULL);
+       if (method == AST_HTTP_OPTIONS) {
+               handle_options(handler, headers, response);
+               return;
+       }
+
+       if (method < 0 || method >= AST_HTTP_MAX_METHOD) {
+               add_allow_header(handler, response);
+               stasis_http_response_error(
+                       response, 405, "Method Not Allowed",
+                       "Invalid method");
+               return;
+       }
+
+       callback = handler->callbacks[method];
+       if (callback == NULL) {
+               add_allow_header(handler, response);
+               stasis_http_response_error(
+                       response, 405, "Method Not Allowed",
+                       "Invalid method");
+               return;
+       }
+
+       callback(get_params, path_vars, headers, response);
+       if (response->message == NULL && response->response_code == 0) {
+               /* Really should not happen */
+               ast_assert(0);
+               stasis_http_response_error(
+                       response, 418, "I'm a teapot",
+                       "Method not implemented");
+       }
+}
+
+void stasis_http_get_docs(const char *uri, struct ast_variable *headers,
+                         struct stasis_http_response *response)
+{
+       RAII_VAR(struct ast_str *, absolute_path_builder, NULL, ast_free);
+       RAII_VAR(char *, absolute_api_dirname, NULL, free);
+       RAII_VAR(char *, absolute_filename, NULL, free);
+       struct ast_json *obj = NULL;
+       struct ast_variable *host = NULL;
+       struct ast_json_error error = {};
+       struct stat file_stat;
+
+       ast_debug(3, "%s(%s)\n", __func__, uri);
+
+       absolute_path_builder = ast_str_create(80);
+       if (absolute_path_builder == NULL) {
+               stasis_http_response_alloc_failed(response);
+               return;
+       }
+
+       /* absolute path to the rest-api directory */
+       ast_str_append(&absolute_path_builder, 0, "%s", ast_config_AST_DATA_DIR);
+       ast_str_append(&absolute_path_builder, 0, "/rest-api/");
+       absolute_api_dirname = realpath(ast_str_buffer(absolute_path_builder), NULL);
+       if (absolute_api_dirname == NULL) {
+               ast_log(LOG_ERROR, "Error determining real directory for rest-api\n");
+               stasis_http_response_error(
+                       response, 500, "Internal Server Error",
+                       "Cannot find rest-api directory");
+               return;
+       }
+
+       /* absolute path to the requested file */
+       ast_str_append(&absolute_path_builder, 0, "%s", uri);
+       absolute_filename = realpath(ast_str_buffer(absolute_path_builder), NULL);
+       if (absolute_filename == NULL) {
+               switch (errno) {
+               case ENAMETOOLONG:
+               case ENOENT:
+               case ENOTDIR:
+                       stasis_http_response_error(
+                               response, 404, "Not Found",
+                               "Resource not found");
+                       break;
+               case EACCES:
+                       stasis_http_response_error(
+                               response, 403, "Forbidden",
+                               "Permission denied");
+                       break;
+               default:
+                       ast_log(LOG_ERROR,
+                               "Error determining real path for uri '%s': %s\n",
+                               uri, strerror(errno));
+                       stasis_http_response_error(
+                               response, 500, "Internal Server Error",
+                               "Cannot find file");
+                       break;
+               }
+               return;
+       }
+
+       if (!ast_begins_with(absolute_filename, absolute_api_dirname)) {
+               /* HACKERZ! */
+               ast_log(LOG_ERROR,
+                       "Invalid attempt to access '%s' (not in %s)\n",
+                       absolute_filename, absolute_api_dirname);
+               stasis_http_response_error(
+                       response, 404, "Not Found",
+                       "Resource not found");
+               return;
+       }
+
+       if (stat(absolute_filename, &file_stat) == 0) {
+               if (!(file_stat.st_mode & S_IFREG)) {
+                       /* Not a file */
+                       stasis_http_response_error(
+                               response, 403, "Forbidden",
+                               "Invalid access");
+                       return;
+               }
+       } else {
+               /* Does not exist */
+               stasis_http_response_error(
+                       response, 404, "Not Found",
+                       "Resource not found");
+               return;
+       }
+
+       /* Load resource object from file */
+       obj = ast_json_load_new_file(absolute_filename, &error);
+       if (obj == NULL) {
+               ast_log(LOG_ERROR, "Error parsing resource file: %s:%d(%d) %s\n",
+                       error.source, error.line, error.column, error.text);
+               stasis_http_response_error(
+                       response, 500, "Internal Server Error",
+                       "Yikes! Cannot parse resource");
+               return;
+       }
+
+       /* Update the basePath properly */
+       if (ast_json_object_get(obj, "basePath") != NULL) {
+               for (host = headers; host; host = host->next) {
+                       if (strcasecmp(host->name, "Host") == 0) {
+                               break;
+                       }
+               }
+               if (host != NULL) {
+                       ast_json_object_set(
+                               obj, "basePath",
+                               ast_json_stringf("http://%s/stasis", host->value));
+               } else {
+                       /* Without the host, we don't have the basePath */
+                       ast_json_object_del(obj, "basePath");
+               }
+       }
+
+       stasis_http_response_ok(response, obj);
+}
+
+static void remove_trailing_slash(const char *uri,
+                                 struct stasis_http_response *response)
+{
+       char *slashless = ast_strdupa(uri);
+       slashless[strlen(slashless) - 1] = '\0';
+
+       ast_str_append(&response->headers, 0,
+                      "Location: /stasis/%s\r\n", slashless);
+       stasis_http_response_error(response, 302, "Found",
+                                  "Redirecting to %s", slashless);
+}
+
+/*!
+ * \brief Handle CORS headers for simple requests.
+ *
+ * See http://www.w3.org/TR/cors/ for the spec. Especially section 6.1.
+ */
+static void process_cors_request(struct ast_variable *headers,
+                                struct stasis_http_response *response)
+{
+       char const *origin = NULL;
+       struct ast_variable *header;
+
+       /* Parse CORS headers */
+       for (header = headers; header != NULL; header = header->next) {
+               if (strcmp("Origin", header->name) == 0) {
+                       origin = header->value;
+               }
+       }
+
+       /* CORS 6.1, #1 - "If the Origin header is not present terminate this
+        * set of steps."
+        */
+       if (origin == NULL) {
+               return;
+       }
+
+       /* CORS 6.1, #2 - "If the value of the Origin header is not a
+        * case-sensitive match for any of the values in list of origins, do not
+        * set any additional headers and terminate this set of steps.
+        *
+        * "Note: Always matching is acceptable since the list of origins can be
+        * unbounded."
+        *
+        * TODO - pull list of allowed origins from config
+        */
+
+       /* CORS 6.1, #3 - "If the resource supports credentials add a single
+        * Access-Control-Allow-Origin header, with the value of the Origin
+        * header as value, and add a single Access-Control-Allow-Credentials
+        * header with the case-sensitive string "true" as value.
+        *
+        * "Otherwise, add a single Access-Control-Allow-Origin header, with
+        * either the value of the Origin header or the string "*" as value."
+        *
+        * TODO - when we add authentication, this will change to
+        * Access-Control-Allow-Credentials.
+        */
+       ast_str_append(&response->headers, 0,
+                      "Access-Control-Allow-Origin: %s\r\n", origin);
+
+       /* CORS 6.1, #4 - "If the list of exposed headers is not empty add one
+        * or more Access-Control-Expose-Headers headers, with as values the
+        * header field names given in the list of exposed headers."
+        *
+        * No exposed headers; skipping
+        */
+}
+
+
+/*!
+ * \internal
+ * \brief Stasis HTTP handler.
+ *
+ * This handler takes the HTTP request and turns it into the appropriate
+ * RESTful request (conversion to JSON, routing, etc.)
+ *
+ * \param ser TCP session.
+ * \param urih URI handler.
+ * \param uri URI requested.
+ * \param method HTTP method.
+ * \param get_params HTTP \c GET params.
+ * \param headers HTTP headers.
+ */
+static int stasis_http_callback(struct ast_tcptls_session_instance *ser,
+                               const struct ast_http_uri *urih,
+                               const char *uri,
+                               enum ast_http_method method,
+                               struct ast_variable *get_params,
+                               struct ast_variable *headers)
+{
+       RAII_VAR(struct conf *, cfg, ao2_global_obj_ref(confs), ao2_cleanup);
+       RAII_VAR(struct ast_str *, response_headers, ast_str_create(40), ast_free);
+       RAII_VAR(struct ast_str *, response_body, ast_str_create(256), ast_free);
+       struct stasis_http_response response = {};
+       int ret = 0;
+
+       if (!response_headers || !response_body) {
+               return -1;
+       }
+
+       response.headers = ast_str_create(40);
+
+       process_cors_request(headers, &response);
+
+       if (ast_ends_with(uri, "/")) {
+               remove_trailing_slash(uri, &response);
+       } else if (ast_begins_with(uri, "api-docs/")) {
+               /* Serving up API docs */
+               if (method != AST_HTTP_GET) {
+                       response.message =
+                               ast_json_pack("{s: s}",
+                                             "message", "Unsupported method");
+                       response.response_code = 405;
+                       response.response_text = "Method Not Allowed";
+               } else {
+                       /* Skip the api-docs prefix */
+                       stasis_http_get_docs(strchr(uri, '/') + 1, headers, &response);
+               }
+       } else {
+               /* Other RESTful resources */
+               stasis_http_invoke(uri, method, get_params, headers, &response);
+       }
+
+       /* Leaving message unset is only allowed for 204 (No Content).
+        * If you explicitly want to have no content for a different return
+        * code, set message to ast_json_null().
+        */
+       ast_assert(response.response_code == 204 || response.message != NULL);
+       ast_assert(response.response_code > 0);
+
+       ast_str_append(&response_headers, 0, "%s", ast_str_buffer(response.headers));
+
+       /* response.message could be NULL, in which case the empty response_body
+        * is correct
+        */
+       if (response.message && !ast_json_is_null(response.message)) {
+               ast_str_append(&response_headers, 0,
+                              "Content-type: application/json\r\n");
+               if (ast_json_dump_str_format(response.message, &response_body, cfg->global->format) != 0) {
+                       /* Error encoding response */
+                       response.response_code = 500;
+                       response.response_text = "Internal Server Error";
+                       ast_str_set(&response_body, 0, "%s", "");
+                       ast_str_set(&response_headers, 0, "%s", "");
+                       ret = -1;
+               }
+       }
+
+       ast_http_send(ser, method, response.response_code,
+                     response.response_text, response_headers, response_body,
+                     0, 0);
+       /* ast_http_send takes ownership, so we don't have to free them */
+       response_headers = NULL;
+       response_body = NULL;
+
+       ast_json_unref(response.message);
+       return ret;
+}
+
+static struct ast_http_uri http_uri = {
+       .callback = stasis_http_callback,
+       .description = "Asterisk RESTful API",
+       .uri = "stasis",
+
+       .has_subtree = 1,
+       .data = NULL,
+       .key = __FILE__,
+};
+
+static int load_module(void)
+{
+       ast_mutex_init(&root_handler_lock);
+       root_handler = root_handler_create();
+       if (!root_handler) {
+               return AST_MODULE_LOAD_FAILURE;
+       }
+
+       if (aco_info_init(&cfg_info)) {
+               aco_info_destroy(&cfg_info);
+               return AST_MODULE_LOAD_DECLINE;
+       }
+
+       aco_option_register_custom(&cfg_info, "enabled", ACO_EXACT, global_options,
+                                  "yes", conf_bitfield_handler, 0);
+       aco_option_register_custom(&cfg_info, "pretty", ACO_EXACT, global_options,
+                                  "no",  encoding_format_handler, 0);
+
+       if (aco_process_config(&cfg_info, 0)) {
+               aco_info_destroy(&cfg_info);
+               return AST_MODULE_LOAD_DECLINE;
+       }
+
+       alloc_failed_message = ast_json_pack(
+               "{s: s}", "message", "Allocation failed");
+
+       if (is_enabled()) {
+               ast_http_uri_link(&http_uri);
+       }
+
+       return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+       ast_json_unref(alloc_failed_message);
+       alloc_failed_message = NULL;
+
+       if (is_enabled()) {
+               ast_http_uri_unlink(&http_uri);
+       }
+
+       aco_info_destroy(&cfg_info);
+       ao2_global_obj_release(confs);
+
+       ao2_cleanup(root_handler);
+       ast_mutex_destroy(&root_handler_lock);
+
+       return 0;
+}
+
+static int reload_module(void)
+{
+       char was_enabled = is_enabled();
+
+       if (aco_process_config(&cfg_info, 1)) {
+               return AST_MODULE_LOAD_DECLINE;
+       }
+
+       if (was_enabled && !is_enabled()) {
+               ast_http_uri_unlink(&http_uri);
+       } else if (!was_enabled && is_enabled()) {
+               ast_http_uri_link(&http_uri);
+       }
+
+       return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY,
+       AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER,
+       "Stasis HTTP bindings",
+       .load = load_module,
+       .unload = unload_module,
+       .reload = reload_module,
+       .nonoptreq = "app_stasis",
+       .load_pri = AST_MODPRI_APP_DEPEND,
+       );
diff --git a/res/res_stasis_http.exports.in b/res/res_stasis_http.exports.in
new file mode 100644 (file)
index 0000000..726a864
--- /dev/null
@@ -0,0 +1,6 @@
+{
+       global:
+               LINKER_SYMBOL_PREFIXstasis_http_*;
+       local:
+               *;
+};
diff --git a/res/res_stasis_http_asterisk.c b/res/res_stasis_http_asterisk.c
new file mode 100644 (file)
index 0000000..333c66b
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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.
+ */
+
+/*
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * !!!!!                               DO NOT EDIT                        !!!!!
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This file is generated by a mustache template. Please see the original
+ * template in rest-api-templates/res_stasis_http_resource.c.mustache
+ */
+
+/*! \file
+ *
+ * \brief Asterisk resources
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+/*** MODULEINFO
+       <depend type="module">res_stasis_http</depend>
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/module.h"
+#include "stasis_http/resource_asterisk.h"
+
+/*!
+ * \brief Parameter parsing callback for /asterisk/info.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_get_asterisk_info_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_get_asterisk_info_args args = {};
+       struct ast_variable *i;
+
+       for (i = get_params; i; i = i->next) {
+               if (strcmp(i->name, "only") == 0) {
+                       args.only = (i->value);
+               } else
+               {}
+       }
+       stasis_http_get_asterisk_info(headers, &args, response);
+}
+
+/*! \brief REST handler for /api-docs/asterisk.{format} */
+static struct stasis_rest_handlers asterisk_info = {
+       .path_segment = "info",
+       .callbacks = {
+               [AST_HTTP_GET] = stasis_http_get_asterisk_info_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/asterisk.{format} */
+static struct stasis_rest_handlers asterisk = {
+       .path_segment = "asterisk",
+       .callbacks = {
+       },
+       .num_children = 1,
+       .children = { &asterisk_info, }
+};
+
+static int load_module(void)
+{
+       return stasis_http_add_handler(&asterisk);
+}
+
+static int unload_module(void)
+{
+       stasis_http_remove_handler(&asterisk);
+       return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT,
+       "RESTful API module - Asterisk resources",
+       .load = load_module,
+       .unload = unload_module,
+       .nonoptreq = "res_stasis_http",
+       );
diff --git a/res/res_stasis_http_bridges.c b/res/res_stasis_http_bridges.c
new file mode 100644 (file)
index 0000000..48d5b72
--- /dev/null
@@ -0,0 +1,291 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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.
+ */
+
+/*
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * !!!!!                               DO NOT EDIT                        !!!!!
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This file is generated by a mustache template. Please see the original
+ * template in rest-api-templates/res_stasis_http_resource.c.mustache
+ */
+
+/*! \file
+ *
+ * \brief Bridge resources
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+/*** MODULEINFO
+       <depend type="module">res_stasis_http</depend>
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/module.h"
+#include "stasis_http/resource_bridges.h"
+
+/*!
+ * \brief Parameter parsing callback for /bridges.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_get_bridges_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_get_bridges_args args = {};
+       stasis_http_get_bridges(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /bridges.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_new_bridge_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_new_bridge_args args = {};
+       struct ast_variable *i;
+
+       for (i = get_params; i; i = i->next) {
+               if (strcmp(i->name, "type") == 0) {
+                       args.type = (i->value);
+               } else
+               {}
+       }
+       stasis_http_new_bridge(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /bridges/{bridgeId}.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_get_bridge_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_get_bridge_args args = {};
+       struct ast_variable *i;
+
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "bridgeId") == 0) {
+                       args.bridge_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_get_bridge(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /bridges/{bridgeId}.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_delete_bridge_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_delete_bridge_args args = {};
+       struct ast_variable *i;
+
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "bridgeId") == 0) {
+                       args.bridge_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_delete_bridge(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /bridges/{bridgeId}/addChannel.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_add_channel_to_bridge_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_add_channel_to_bridge_args args = {};
+       struct ast_variable *i;
+
+       for (i = get_params; i; i = i->next) {
+               if (strcmp(i->name, "channel") == 0) {
+                       args.channel = (i->value);
+               } else
+               {}
+       }
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "bridgeId") == 0) {
+                       args.bridge_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_add_channel_to_bridge(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /bridges/{bridgeId}/removeChannel.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_remove_channel_from_bridge_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_remove_channel_from_bridge_args args = {};
+       struct ast_variable *i;
+
+       for (i = get_params; i; i = i->next) {
+               if (strcmp(i->name, "channel") == 0) {
+                       args.channel = (i->value);
+               } else
+               {}
+       }
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "bridgeId") == 0) {
+                       args.bridge_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_remove_channel_from_bridge(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /bridges/{bridgeId}/record.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_record_bridge_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_record_bridge_args args = {};
+       struct ast_variable *i;
+
+       for (i = get_params; i; i = i->next) {
+               if (strcmp(i->name, "name") == 0) {
+                       args.name = (i->value);
+               } else
+               if (strcmp(i->name, "maxDurationSeconds") == 0) {
+                       args.max_duration_seconds = atoi(i->value);
+               } else
+               if (strcmp(i->name, "maxSilenceSeconds") == 0) {
+                       args.max_silence_seconds = atoi(i->value);
+               } else
+               if (strcmp(i->name, "append") == 0) {
+                       args.append = atoi(i->value);
+               } else
+               if (strcmp(i->name, "beep") == 0) {
+                       args.beep = atoi(i->value);
+               } else
+               if (strcmp(i->name, "terminateOn") == 0) {
+                       args.terminate_on = (i->value);
+               } else
+               {}
+       }
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "bridgeId") == 0) {
+                       args.bridge_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_record_bridge(headers, &args, response);
+}
+
+/*! \brief REST handler for /api-docs/bridges.{format} */
+static struct stasis_rest_handlers bridges_bridgeId_addChannel = {
+       .path_segment = "addChannel",
+       .callbacks = {
+               [AST_HTTP_POST] = stasis_http_add_channel_to_bridge_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/bridges.{format} */
+static struct stasis_rest_handlers bridges_bridgeId_removeChannel = {
+       .path_segment = "removeChannel",
+       .callbacks = {
+               [AST_HTTP_POST] = stasis_http_remove_channel_from_bridge_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/bridges.{format} */
+static struct stasis_rest_handlers bridges_bridgeId_record = {
+       .path_segment = "record",
+       .callbacks = {
+               [AST_HTTP_POST] = stasis_http_record_bridge_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/bridges.{format} */
+static struct stasis_rest_handlers bridges_bridgeId = {
+       .path_segment = "bridgeId",
+       .is_wildcard = 1,
+       .callbacks = {
+               [AST_HTTP_GET] = stasis_http_get_bridge_cb,
+               [AST_HTTP_DELETE] = stasis_http_delete_bridge_cb,
+       },
+       .num_children = 3,
+       .children = { &bridges_bridgeId_addChannel,&bridges_bridgeId_removeChannel,&bridges_bridgeId_record, }
+};
+/*! \brief REST handler for /api-docs/bridges.{format} */
+static struct stasis_rest_handlers bridges = {
+       .path_segment = "bridges",
+       .callbacks = {
+               [AST_HTTP_GET] = stasis_http_get_bridges_cb,
+               [AST_HTTP_POST] = stasis_http_new_bridge_cb,
+       },
+       .num_children = 1,
+       .children = { &bridges_bridgeId, }
+};
+
+static int load_module(void)
+{
+       return stasis_http_add_handler(&bridges);
+}
+
+static int unload_module(void)
+{
+       stasis_http_remove_handler(&bridges);
+       return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT,
+       "RESTful API module - Bridge resources",
+       .load = load_module,
+       .unload = unload_module,
+       .nonoptreq = "res_stasis_http",
+       );
diff --git a/res/res_stasis_http_channels.c b/res/res_stasis_http_channels.c
new file mode 100644 (file)
index 0000000..c21bc10
--- /dev/null
@@ -0,0 +1,504 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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.
+ */
+
+/*
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * !!!!!                               DO NOT EDIT                        !!!!!
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This file is generated by a mustache template. Please see the original
+ * template in rest-api-templates/res_stasis_http_resource.c.mustache
+ */
+
+/*! \file
+ *
+ * \brief Channel resources
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+/*** MODULEINFO
+       <depend type="module">res_stasis_http</depend>
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/module.h"
+#include "stasis_http/resource_channels.h"
+
+/*!
+ * \brief Parameter parsing callback for /channels.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_get_channels_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_get_channels_args args = {};
+       stasis_http_get_channels(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /channels.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_originate_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_originate_args args = {};
+       struct ast_variable *i;
+
+       for (i = get_params; i; i = i->next) {
+               if (strcmp(i->name, "endpoint") == 0) {
+                       args.endpoint = (i->value);
+               } else
+               if (strcmp(i->name, "extension") == 0) {
+                       args.extension = (i->value);
+               } else
+               if (strcmp(i->name, "context") == 0) {
+                       args.context = (i->value);
+               } else
+               {}
+       }
+       stasis_http_originate(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /channels/{channelId}.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_get_channel_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_get_channel_args args = {};
+       struct ast_variable *i;
+
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "channelId") == 0) {
+                       args.channel_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_get_channel(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /channels/{channelId}.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_delete_channel_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_delete_channel_args args = {};
+       struct ast_variable *i;
+
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "channelId") == 0) {
+                       args.channel_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_delete_channel(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /channels/{channelId}/dial.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_dial_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_dial_args args = {};
+       struct ast_variable *i;
+
+       for (i = get_params; i; i = i->next) {
+               if (strcmp(i->name, "endpoint") == 0) {
+                       args.endpoint = (i->value);
+               } else
+               if (strcmp(i->name, "extension") == 0) {
+                       args.extension = (i->value);
+               } else
+               if (strcmp(i->name, "context") == 0) {
+                       args.context = (i->value);
+               } else
+               {}
+       }
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "channelId") == 0) {
+                       args.channel_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_dial(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /channels/{channelId}/continue.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_continue_in_dialplan_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_continue_in_dialplan_args args = {};
+       struct ast_variable *i;
+
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "channelId") == 0) {
+                       args.channel_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_continue_in_dialplan(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /channels/{channelId}/answer.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_answer_channel_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_answer_channel_args args = {};
+       struct ast_variable *i;
+
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "channelId") == 0) {
+                       args.channel_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_answer_channel(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /channels/{channelId}/mute.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_mute_channel_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_mute_channel_args args = {};
+       struct ast_variable *i;
+
+       for (i = get_params; i; i = i->next) {
+               if (strcmp(i->name, "direction") == 0) {
+                       args.direction = (i->value);
+               } else
+               {}
+       }
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "channelId") == 0) {
+                       args.channel_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_mute_channel(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /channels/{channelId}/unmute.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_unmute_channel_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_unmute_channel_args args = {};
+       struct ast_variable *i;
+
+       for (i = get_params; i; i = i->next) {
+               if (strcmp(i->name, "direction") == 0) {
+                       args.direction = (i->value);
+               } else
+               {}
+       }
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "channelId") == 0) {
+                       args.channel_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_unmute_channel(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /channels/{channelId}/hold.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_hold_channel_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_hold_channel_args args = {};
+       struct ast_variable *i;
+
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "channelId") == 0) {
+                       args.channel_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_hold_channel(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /channels/{channelId}/unhold.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_unhold_channel_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_unhold_channel_args args = {};
+       struct ast_variable *i;
+
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "channelId") == 0) {
+                       args.channel_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_unhold_channel(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /channels/{channelId}/play.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_play_on_channel_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_play_on_channel_args args = {};
+       struct ast_variable *i;
+
+       for (i = get_params; i; i = i->next) {
+               if (strcmp(i->name, "media") == 0) {
+                       args.media = (i->value);
+               } else
+               {}
+       }
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "channelId") == 0) {
+                       args.channel_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_play_on_channel(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /channels/{channelId}/record.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_record_channel_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_record_channel_args args = {};
+       struct ast_variable *i;
+
+       for (i = get_params; i; i = i->next) {
+               if (strcmp(i->name, "name") == 0) {
+                       args.name = (i->value);
+               } else
+               if (strcmp(i->name, "format") == 0) {
+                       args.format = (i->value);
+               } else
+               if (strcmp(i->name, "maxDurationSeconds") == 0) {
+                       args.max_duration_seconds = atoi(i->value);
+               } else
+               if (strcmp(i->name, "maxSilenceSeconds") == 0) {
+                       args.max_silence_seconds = atoi(i->value);
+               } else
+               if (strcmp(i->name, "append") == 0) {
+                       args.append = atoi(i->value);
+               } else
+               if (strcmp(i->name, "beep") == 0) {
+                       args.beep = atoi(i->value);
+               } else
+               if (strcmp(i->name, "terminateOn") == 0) {
+                       args.terminate_on = (i->value);
+               } else
+               {}
+       }
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "channelId") == 0) {
+                       args.channel_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_record_channel(headers, &args, response);
+}
+
+/*! \brief REST handler for /api-docs/channels.{format} */
+static struct stasis_rest_handlers channels_channelId_dial = {
+       .path_segment = "dial",
+       .callbacks = {
+               [AST_HTTP_POST] = stasis_http_dial_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/channels.{format} */
+static struct stasis_rest_handlers channels_channelId_continue = {
+       .path_segment = "continue",
+       .callbacks = {
+               [AST_HTTP_POST] = stasis_http_continue_in_dialplan_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/channels.{format} */
+static struct stasis_rest_handlers channels_channelId_answer = {
+       .path_segment = "answer",
+       .callbacks = {
+               [AST_HTTP_POST] = stasis_http_answer_channel_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/channels.{format} */
+static struct stasis_rest_handlers channels_channelId_mute = {
+       .path_segment = "mute",
+       .callbacks = {
+               [AST_HTTP_POST] = stasis_http_mute_channel_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/channels.{format} */
+static struct stasis_rest_handlers channels_channelId_unmute = {
+       .path_segment = "unmute",
+       .callbacks = {
+               [AST_HTTP_POST] = stasis_http_unmute_channel_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/channels.{format} */
+static struct stasis_rest_handlers channels_channelId_hold = {
+       .path_segment = "hold",
+       .callbacks = {
+               [AST_HTTP_POST] = stasis_http_hold_channel_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/channels.{format} */
+static struct stasis_rest_handlers channels_channelId_unhold = {
+       .path_segment = "unhold",
+       .callbacks = {
+               [AST_HTTP_POST] = stasis_http_unhold_channel_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/channels.{format} */
+static struct stasis_rest_handlers channels_channelId_play = {
+       .path_segment = "play",
+       .callbacks = {
+               [AST_HTTP_POST] = stasis_http_play_on_channel_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/channels.{format} */
+static struct stasis_rest_handlers channels_channelId_record = {
+       .path_segment = "record",
+       .callbacks = {
+               [AST_HTTP_POST] = stasis_http_record_channel_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/channels.{format} */
+static struct stasis_rest_handlers channels_channelId = {
+       .path_segment = "channelId",
+       .is_wildcard = 1,
+       .callbacks = {
+               [AST_HTTP_GET] = stasis_http_get_channel_cb,
+               [AST_HTTP_DELETE] = stasis_http_delete_channel_cb,
+       },
+       .num_children = 9,
+       .children = { &channels_channelId_dial,&channels_channelId_continue,&channels_channelId_answer,&channels_channelId_mute,&channels_channelId_unmute,&channels_channelId_hold,&channels_channelId_unhold,&channels_channelId_play,&channels_channelId_record, }
+};
+/*! \brief REST handler for /api-docs/channels.{format} */
+static struct stasis_rest_handlers channels = {
+       .path_segment = "channels",
+       .callbacks = {
+               [AST_HTTP_GET] = stasis_http_get_channels_cb,
+               [AST_HTTP_POST] = stasis_http_originate_cb,
+       },
+       .num_children = 1,
+       .children = { &channels_channelId, }
+};
+
+static int load_module(void)
+{
+       return stasis_http_add_handler(&channels);
+}
+
+static int unload_module(void)
+{
+       stasis_http_remove_handler(&channels);
+       return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT,
+       "RESTful API module - Channel resources",
+       .load = load_module,
+       .unload = unload_module,
+       .nonoptreq = "res_stasis_http",
+       );
diff --git a/res/res_stasis_http_endpoints.c b/res/res_stasis_http_endpoints.c
new file mode 100644 (file)
index 0000000..a420d4e
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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.
+ */
+
+/*
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * !!!!!                               DO NOT EDIT                        !!!!!
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This file is generated by a mustache template. Please see the original
+ * template in rest-api-templates/res_stasis_http_resource.c.mustache
+ */
+
+/*! \file
+ *
+ * \brief Endpoint resources
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+/*** MODULEINFO
+       <depend type="module">res_stasis_http</depend>
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/module.h"
+#include "stasis_http/resource_endpoints.h"
+
+/*!
+ * \brief Parameter parsing callback for /endpoints.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_get_endpoints_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_get_endpoints_args args = {};
+       struct ast_variable *i;
+
+       for (i = get_params; i; i = i->next) {
+               if (strcmp(i->name, "withType") == 0) {
+                       args.with_type = (i->value);
+               } else
+               {}
+       }
+       stasis_http_get_endpoints(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /endpoints/{endpointId}.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_get_endpoint_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_get_endpoint_args args = {};
+       struct ast_variable *i;
+
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "endpointId") == 0) {
+                       args.endpoint_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_get_endpoint(headers, &args, response);
+}
+
+/*! \brief REST handler for /api-docs/endpoints.{format} */
+static struct stasis_rest_handlers endpoints_endpointId = {
+       .path_segment = "endpointId",
+       .is_wildcard = 1,
+       .callbacks = {
+               [AST_HTTP_GET] = stasis_http_get_endpoint_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/endpoints.{format} */
+static struct stasis_rest_handlers endpoints = {
+       .path_segment = "endpoints",
+       .callbacks = {
+               [AST_HTTP_GET] = stasis_http_get_endpoints_cb,
+       },
+       .num_children = 1,
+       .children = { &endpoints_endpointId, }
+};
+
+static int load_module(void)
+{
+       return stasis_http_add_handler(&endpoints);
+}
+
+static int unload_module(void)
+{
+       stasis_http_remove_handler(&endpoints);
+       return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT,
+       "RESTful API module - Endpoint resources",
+       .load = load_module,
+       .unload = unload_module,
+       .nonoptreq = "res_stasis_http",
+       );
diff --git a/res/res_stasis_http_events.c b/res/res_stasis_http_events.c
new file mode 100644 (file)
index 0000000..62ed44f
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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.
+ */
+
+/*
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * !!!!!                               DO NOT EDIT                        !!!!!
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This file is generated by a mustache template. Please see the original
+ * template in rest-api-templates/res_stasis_http_resource.c.mustache
+ */
+
+/*! \file
+ *
+ * \brief WebSocket resource
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+/*** MODULEINFO
+       <depend type="module">res_stasis_http</depend>
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/module.h"
+#include "stasis_http/resource_events.h"
+
+/*!
+ * \brief Parameter parsing callback for /events.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_event_websocket_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_event_websocket_args args = {};
+       struct ast_variable *i;
+
+       for (i = get_params; i; i = i->next) {
+               if (strcmp(i->name, "app") == 0) {
+                       args.app = (i->value);
+               } else
+               {}
+       }
+       stasis_http_event_websocket(headers, &args, response);
+}
+
+/*! \brief REST handler for /api-docs/events.{format} */
+static struct stasis_rest_handlers events = {
+       .path_segment = "events",
+       .callbacks = {
+               [AST_HTTP_GET] = stasis_http_event_websocket_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+
+static int load_module(void)
+{
+       return stasis_http_add_handler(&events);
+}
+
+static int unload_module(void)
+{
+       stasis_http_remove_handler(&events);
+       return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT,
+       "RESTful API module - WebSocket resource",
+       .load = load_module,
+       .unload = unload_module,
+       .nonoptreq = "res_stasis_http",
+       );
diff --git a/res/res_stasis_http_playback.c b/res/res_stasis_http_playback.c
new file mode 100644 (file)
index 0000000..77dcee4
--- /dev/null
@@ -0,0 +1,164 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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.
+ */
+
+/*
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * !!!!!                               DO NOT EDIT                        !!!!!
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This file is generated by a mustache template. Please see the original
+ * template in rest-api-templates/res_stasis_http_resource.c.mustache
+ */
+
+/*! \file
+ *
+ * \brief Playback control resources
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+/*** MODULEINFO
+       <depend type="module">res_stasis_http</depend>
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/module.h"
+#include "stasis_http/resource_playback.h"
+
+/*!
+ * \brief Parameter parsing callback for /playback/{playbackId}.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_get_playback_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_get_playback_args args = {};
+       struct ast_variable *i;
+
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "playbackId") == 0) {
+                       args.playback_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_get_playback(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /playback/{playbackId}.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_stop_playback_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_stop_playback_args args = {};
+       struct ast_variable *i;
+
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "playbackId") == 0) {
+                       args.playback_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_stop_playback(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /playback/{playbackId}/control.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_control_playback_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_control_playback_args args = {};
+       struct ast_variable *i;
+
+       for (i = get_params; i; i = i->next) {
+               if (strcmp(i->name, "operation") == 0) {
+                       args.operation = (i->value);
+               } else
+               {}
+       }
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "playbackId") == 0) {
+                       args.playback_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_control_playback(headers, &args, response);
+}
+
+/*! \brief REST handler for /api-docs/playback.{format} */
+static struct stasis_rest_handlers playback_playbackId_control = {
+       .path_segment = "control",
+       .callbacks = {
+               [AST_HTTP_POST] = stasis_http_control_playback_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/playback.{format} */
+static struct stasis_rest_handlers playback_playbackId = {
+       .path_segment = "playbackId",
+       .is_wildcard = 1,
+       .callbacks = {
+               [AST_HTTP_GET] = stasis_http_get_playback_cb,
+               [AST_HTTP_DELETE] = stasis_http_stop_playback_cb,
+       },
+       .num_children = 1,
+       .children = { &playback_playbackId_control, }
+};
+/*! \brief REST handler for /api-docs/playback.{format} */
+static struct stasis_rest_handlers playback = {
+       .path_segment = "playback",
+       .callbacks = {
+       },
+       .num_children = 1,
+       .children = { &playback_playbackId, }
+};
+
+static int load_module(void)
+{
+       return stasis_http_add_handler(&playback);
+}
+
+static int unload_module(void)
+{
+       stasis_http_remove_handler(&playback);
+       return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT,
+       "RESTful API module - Playback control resources",
+       .load = load_module,
+       .unload = unload_module,
+       .nonoptreq = "res_stasis_http",
+       );
diff --git a/res/res_stasis_http_recordings.c b/res/res_stasis_http_recordings.c
new file mode 100644 (file)
index 0000000..b6a3b41
--- /dev/null
@@ -0,0 +1,398 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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.
+ */
+
+/*
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * !!!!!                               DO NOT EDIT                        !!!!!
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This file is generated by a mustache template. Please see the original
+ * template in rest-api-templates/res_stasis_http_resource.c.mustache
+ */
+
+/*! \file
+ *
+ * \brief Recording resources
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+/*** MODULEINFO
+       <depend type="module">res_stasis_http</depend>
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/module.h"
+#include "stasis_http/resource_recordings.h"
+
+/*!
+ * \brief Parameter parsing callback for /recordings.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_get_recordings_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_get_recordings_args args = {};
+       stasis_http_get_recordings(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /recordings/stored.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_get_stored_recordings_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_get_stored_recordings_args args = {};
+       stasis_http_get_stored_recordings(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /recordings/stored/{recordingId}.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_get_stored_recording_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_get_stored_recording_args args = {};
+       struct ast_variable *i;
+
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "recordingId") == 0) {
+                       args.recording_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_get_stored_recording(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /recordings/stored/{recordingId}.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_delete_stored_recording_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_delete_stored_recording_args args = {};
+       struct ast_variable *i;
+
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "recordingId") == 0) {
+                       args.recording_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_delete_stored_recording(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /recordings/live.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_get_live_recordings_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_get_live_recordings_args args = {};
+       stasis_http_get_live_recordings(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /recordings/live/{recordingId}.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_get_live_recording_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_get_live_recording_args args = {};
+       struct ast_variable *i;
+
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "recordingId") == 0) {
+                       args.recording_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_get_live_recording(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /recordings/live/{recordingId}.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_cancel_recording_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_cancel_recording_args args = {};
+       struct ast_variable *i;
+
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "recordingId") == 0) {
+                       args.recording_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_cancel_recording(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /recordings/live/{recordingId}/stop.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_stop_recording_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_stop_recording_args args = {};
+       struct ast_variable *i;
+
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "recordingId") == 0) {
+                       args.recording_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_stop_recording(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /recordings/live/{recordingId}/pause.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_pause_recording_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_pause_recording_args args = {};
+       struct ast_variable *i;
+
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "recordingId") == 0) {
+                       args.recording_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_pause_recording(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /recordings/live/{recordingId}/unpause.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_unpause_recording_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_unpause_recording_args args = {};
+       struct ast_variable *i;
+
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "recordingId") == 0) {
+                       args.recording_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_unpause_recording(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /recordings/live/{recordingId}/mute.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_mute_recording_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_mute_recording_args args = {};
+       struct ast_variable *i;
+
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "recordingId") == 0) {
+                       args.recording_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_mute_recording(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /recordings/live/{recordingId}/unmute.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_unmute_recording_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_unmute_recording_args args = {};
+       struct ast_variable *i;
+
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "recordingId") == 0) {
+                       args.recording_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_unmute_recording(headers, &args, response);
+}
+
+/*! \brief REST handler for /api-docs/recordings.{format} */
+static struct stasis_rest_handlers recordings_stored_recordingId = {
+       .path_segment = "recordingId",
+       .is_wildcard = 1,
+       .callbacks = {
+               [AST_HTTP_GET] = stasis_http_get_stored_recording_cb,
+               [AST_HTTP_DELETE] = stasis_http_delete_stored_recording_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/recordings.{format} */
+static struct stasis_rest_handlers recordings_stored = {
+       .path_segment = "stored",
+       .callbacks = {
+               [AST_HTTP_GET] = stasis_http_get_stored_recordings_cb,
+       },
+       .num_children = 1,
+       .children = { &recordings_stored_recordingId, }
+};
+/*! \brief REST handler for /api-docs/recordings.{format} */
+static struct stasis_rest_handlers recordings_live_recordingId_stop = {
+       .path_segment = "stop",
+       .callbacks = {
+               [AST_HTTP_POST] = stasis_http_stop_recording_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/recordings.{format} */
+static struct stasis_rest_handlers recordings_live_recordingId_pause = {
+       .path_segment = "pause",
+       .callbacks = {
+               [AST_HTTP_POST] = stasis_http_pause_recording_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/recordings.{format} */
+static struct stasis_rest_handlers recordings_live_recordingId_unpause = {
+       .path_segment = "unpause",
+       .callbacks = {
+               [AST_HTTP_POST] = stasis_http_unpause_recording_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/recordings.{format} */
+static struct stasis_rest_handlers recordings_live_recordingId_mute = {
+       .path_segment = "mute",
+       .callbacks = {
+               [AST_HTTP_POST] = stasis_http_mute_recording_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/recordings.{format} */
+static struct stasis_rest_handlers recordings_live_recordingId_unmute = {
+       .path_segment = "unmute",
+       .callbacks = {
+               [AST_HTTP_POST] = stasis_http_unmute_recording_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/recordings.{format} */
+static struct stasis_rest_handlers recordings_live_recordingId = {
+       .path_segment = "recordingId",
+       .is_wildcard = 1,
+       .callbacks = {
+               [AST_HTTP_GET] = stasis_http_get_live_recording_cb,
+               [AST_HTTP_DELETE] = stasis_http_cancel_recording_cb,
+       },
+       .num_children = 5,
+       .children = { &recordings_live_recordingId_stop,&recordings_live_recordingId_pause,&recordings_live_recordingId_unpause,&recordings_live_recordingId_mute,&recordings_live_recordingId_unmute, }
+};
+/*! \brief REST handler for /api-docs/recordings.{format} */
+static struct stasis_rest_handlers recordings_live = {
+       .path_segment = "live",
+       .callbacks = {
+               [AST_HTTP_GET] = stasis_http_get_live_recordings_cb,
+       },
+       .num_children = 1,
+       .children = { &recordings_live_recordingId, }
+};
+/*! \brief REST handler for /api-docs/recordings.{format} */
+static struct stasis_rest_handlers recordings = {
+       .path_segment = "recordings",
+       .callbacks = {
+               [AST_HTTP_GET] = stasis_http_get_recordings_cb,
+       },
+       .num_children = 2,
+       .children = { &recordings_stored,&recordings_live, }
+};
+
+static int load_module(void)
+{
+       return stasis_http_add_handler(&recordings);
+}
+
+static int unload_module(void)
+{
+       stasis_http_remove_handler(&recordings);
+       return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT,
+       "RESTful API module - Recording resources",
+       .load = load_module,
+       .unload = unload_module,
+       .nonoptreq = "res_stasis_http",
+       );
diff --git a/res/res_stasis_http_sounds.c b/res/res_stasis_http_sounds.c
new file mode 100644 (file)
index 0000000..39ad71e
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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.
+ */
+
+/*
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * !!!!!                               DO NOT EDIT                        !!!!!
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This file is generated by a mustache template. Please see the original
+ * template in rest-api-templates/res_stasis_http_resource.c.mustache
+ */
+
+/*! \file
+ *
+ * \brief Sound resources
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+/*** MODULEINFO
+       <depend type="module">res_stasis_http</depend>
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/module.h"
+#include "stasis_http/resource_sounds.h"
+
+/*!
+ * \brief Parameter parsing callback for /sounds.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_get_sounds_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_get_sounds_args args = {};
+       struct ast_variable *i;
+
+       for (i = get_params; i; i = i->next) {
+               if (strcmp(i->name, "lang") == 0) {
+                       args.lang = (i->value);
+               } else
+               if (strcmp(i->name, "format") == 0) {
+                       args.format = (i->value);
+               } else
+               {}
+       }
+       stasis_http_get_sounds(headers, &args, response);
+}
+/*!
+ * \brief Parameter parsing callback for /sounds/{soundId}.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_get_stored_sound_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_get_stored_sound_args args = {};
+       struct ast_variable *i;
+
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "soundId") == 0) {
+                       args.sound_id = (i->value);
+               } else
+               {}
+       }
+       stasis_http_get_stored_sound(headers, &args, response);
+}
+
+/*! \brief REST handler for /api-docs/sounds.{format} */
+static struct stasis_rest_handlers sounds_soundId = {
+       .path_segment = "soundId",
+       .is_wildcard = 1,
+       .callbacks = {
+               [AST_HTTP_GET] = stasis_http_get_stored_sound_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/sounds.{format} */
+static struct stasis_rest_handlers sounds = {
+       .path_segment = "sounds",
+       .callbacks = {
+               [AST_HTTP_GET] = stasis_http_get_sounds_cb,
+       },
+       .num_children = 1,
+       .children = { &sounds_soundId, }
+};
+
+static int load_module(void)
+{
+       return stasis_http_add_handler(&sounds);
+}
+
+static int unload_module(void)
+{
+       stasis_http_remove_handler(&sounds);
+       return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT,
+       "RESTful API module - Sound resources",
+       .load = load_module,
+       .unload = unload_module,
+       .nonoptreq = "res_stasis_http",
+       );
diff --git a/res/stasis_http.make b/res/stasis_http.make
new file mode 100644 (file)
index 0000000..3d80e5e
--- /dev/null
@@ -0,0 +1,51 @@
+#
+# Asterisk -- A telephony toolkit for Linux.
+#
+# Generated Makefile for res_stasis_http dependencies.
+#
+# Copyright (C) 2013, Digium, Inc.
+#
+# This program is free software, distributed under the terms of
+# the GNU General Public License
+#
+
+#
+# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+# !!!!!                               DO NOT EDIT                        !!!!!
+# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+# This file is generated by a template. Please see the original template at
+# rest-api-templates/stasis_http.make.mustache
+#
+
+res_stasis_http_asterisk.so: stasis_http/resource_asterisk.o
+
+stasis_http/resource_asterisk.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_stasis_http_asterisk)
+
+res_stasis_http_endpoints.so: stasis_http/resource_endpoints.o
+
+stasis_http/resource_endpoints.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_stasis_http_endpoints)
+
+res_stasis_http_channels.so: stasis_http/resource_channels.o
+
+stasis_http/resource_channels.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_stasis_http_channels)
+
+res_stasis_http_bridges.so: stasis_http/resource_bridges.o
+
+stasis_http/resource_bridges.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_stasis_http_bridges)
+
+res_stasis_http_recordings.so: stasis_http/resource_recordings.o
+
+stasis_http/resource_recordings.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_stasis_http_recordings)
+
+res_stasis_http_sounds.so: stasis_http/resource_sounds.o
+
+stasis_http/resource_sounds.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_stasis_http_sounds)
+
+res_stasis_http_playback.so: stasis_http/resource_playback.o
+
+stasis_http/resource_playback.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_stasis_http_playback)
+
+res_stasis_http_events.so: stasis_http/resource_events.o
+
+stasis_http/resource_events.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_stasis_http_events)
+
diff --git a/res/stasis_http/resource_asterisk.c b/res/stasis_http/resource_asterisk.c
new file mode 100644 (file)
index 0000000..b5e8b0f
--- /dev/null
@@ -0,0 +1,39 @@
+/* -*- C -*-
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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 for stasis-http stubs.
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+/*** MODULEINFO
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "resource_asterisk.h"
+
+void stasis_http_get_asterisk_info(struct ast_variable *headers, struct ast_get_asterisk_info_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_get_asterisk_info\n");
+}
diff --git a/res/stasis_http/resource_asterisk.h b/res/stasis_http/resource_asterisk.h
new file mode 100644 (file)
index 0000000..0d373cc
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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 Generated file - declares stubs to be implemented in
+ * res/stasis_http/resource_asterisk.c
+ *
+ * Asterisk resources
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+/*
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * !!!!!                               DO NOT EDIT                        !!!!!
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This file is generated by a mustache template. Please see the original
+ * template in rest-api-templates/stasis_http_resource.h.mustache
+ */
+
+#ifndef _ASTERISK_RESOURCE_ASTERISK_H
+#define _ASTERISK_RESOURCE_ASTERISK_H
+
+#include "asterisk/stasis_http.h"
+
+/*! \brief Argument struct for stasis_http_get_asterisk_info() */
+struct ast_get_asterisk_info_args {
+       /*! \brief Filter information returned */
+       const char *only;
+};
+/*!
+ * \brief Gets Asterisk system information.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_get_asterisk_info(struct ast_variable *headers, struct ast_get_asterisk_info_args *args, struct stasis_http_response *response);
+
+#endif /* _ASTERISK_RESOURCE_ASTERISK_H */
diff --git a/res/stasis_http/resource_bridges.c b/res/stasis_http/resource_bridges.c
new file mode 100644 (file)
index 0000000..ca48ee7
--- /dev/null
@@ -0,0 +1,63 @@
+/* -*- C -*-
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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 for stasis-http stubs.
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+/*** MODULEINFO
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "resource_bridges.h"
+
+void stasis_http_add_channel_to_bridge(struct ast_variable *headers, struct ast_add_channel_to_bridge_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_add_channel_to_bridge\n");
+}
+void stasis_http_remove_channel_from_bridge(struct ast_variable *headers, struct ast_remove_channel_from_bridge_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_remove_channel_from_bridge\n");
+}
+void stasis_http_record_bridge(struct ast_variable *headers, struct ast_record_bridge_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_record_bridge\n");
+}
+void stasis_http_get_bridge(struct ast_variable *headers, struct ast_get_bridge_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_get_bridge\n");
+}
+void stasis_http_delete_bridge(struct ast_variable *headers, struct ast_delete_bridge_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_delete_bridge\n");
+}
+void stasis_http_get_bridges(struct ast_variable *headers, struct ast_get_bridges_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_get_bridges\n");
+}
+void stasis_http_new_bridge(struct ast_variable *headers, struct ast_new_bridge_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_new_bridge\n");
+}
diff --git a/res/stasis_http/resource_bridges.h b/res/stasis_http/resource_bridges.h
new file mode 100644 (file)
index 0000000..db6db20
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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 Generated file - declares stubs to be implemented in
+ * res/stasis_http/resource_bridges.c
+ *
+ * Bridge resources
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+/*
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * !!!!!                               DO NOT EDIT                        !!!!!
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This file is generated by a mustache template. Please see the original
+ * template in rest-api-templates/stasis_http_resource.h.mustache
+ */
+
+#ifndef _ASTERISK_RESOURCE_BRIDGES_H
+#define _ASTERISK_RESOURCE_BRIDGES_H
+
+#include "asterisk/stasis_http.h"
+
+/*! \brief Argument struct for stasis_http_get_bridges() */
+struct ast_get_bridges_args {
+};
+/*!
+ * \brief List active bridges.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_get_bridges(struct ast_variable *headers, struct ast_get_bridges_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_new_bridge() */
+struct ast_new_bridge_args {
+       /*! \brief Type of bridge to create. */
+       const char *type;
+};
+/*!
+ * \brief Create a new bridge.
+ *
+ * This bridge persists until it has been shut down, or Asterisk has been shut down.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_new_bridge(struct ast_variable *headers, struct ast_new_bridge_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_get_bridge() */
+struct ast_get_bridge_args {
+       /*! \brief Bridge's id */
+       const char *bridge_id;
+};
+/*!
+ * \brief Get bridge details.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_get_bridge(struct ast_variable *headers, struct ast_get_bridge_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_delete_bridge() */
+struct ast_delete_bridge_args {
+       /*! \brief Bridge's id */
+       const char *bridge_id;
+};
+/*!
+ * \brief Shut down a bridge bridge.
+ *
+ * If any channels are in this bridge, they will be removed and resume whatever they were doing beforehand.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_delete_bridge(struct ast_variable *headers, struct ast_delete_bridge_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_add_channel_to_bridge() */
+struct ast_add_channel_to_bridge_args {
+       /*! \brief Bridge's id */
+       const char *bridge_id;
+       /*! \brief Channel's id */
+       const char *channel;
+};
+/*!
+ * \brief Add a channel to a bridge.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_add_channel_to_bridge(struct ast_variable *headers, struct ast_add_channel_to_bridge_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_remove_channel_from_bridge() */
+struct ast_remove_channel_from_bridge_args {
+       /*! \brief Bridge's id */
+       const char *bridge_id;
+       /*! \brief Channel's id */
+       const char *channel;
+};
+/*!
+ * \brief Remove a channel from a bridge.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_remove_channel_from_bridge(struct ast_variable *headers, struct ast_remove_channel_from_bridge_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_record_bridge() */
+struct ast_record_bridge_args {
+       /*! \brief Bridge's id */
+       const char *bridge_id;
+       /*! \brief Recording's filename */
+       const char *name;
+       /*! \brief Maximum duration of the recording, in seconds. 0 for no limit. */
+       int max_duration_seconds;
+       /*! \brief Maximum duration of silence, in seconds. 0 for no limit. */
+       int max_silence_seconds;
+       /*! \brief If true, and recording already exists, append to recording. */
+       int append;
+       /*! \brief Play beep when recording begins */
+       int beep;
+       /*! \brief DTMF input to terminate recording. */
+       const char *terminate_on;
+};
+/*!
+ * \brief Start a recording.
+ *
+ * This records the mixed audio from all channels participating in this bridge.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_record_bridge(struct ast_variable *headers, struct ast_record_bridge_args *args, struct stasis_http_response *response);
+
+#endif /* _ASTERISK_RESOURCE_BRIDGES_H */
diff --git a/res/stasis_http/resource_channels.c b/res/stasis_http/resource_channels.c
new file mode 100644 (file)
index 0000000..3cc97c5
--- /dev/null
@@ -0,0 +1,251 @@
+/* -*- C -*-
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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 for stasis-http stubs.
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+/*** MODULEINFO
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/stasis_app.h"
+#include "asterisk/stasis_channels.h"
+#include "resource_channels.h"
+
+/*!
+ * \brief Finds the control object for a channel, filling the response with an
+ * error, if appropriate.
+ * \param[out] response Response to fill with an error if control is not found.
+ * \param channel_id ID of the channel to lookup.
+ * \return Channel control object.
+ * \return \c NULL if control object does not exist.
+ */
+static struct stasis_app_control *find_control(
+       struct stasis_http_response *response,
+       const char *channel_id)
+{
+       RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
+
+       ast_assert(response != NULL);
+
+       control = stasis_app_control_find_by_channel_id(channel_id);
+       if (control == NULL) {
+               /* Distinguish between 404 and 409 errors */
+               RAII_VAR(struct ast_channel *, chan, NULL, ao2_cleanup);
+               chan = ast_channel_get_by_name(channel_id);
+               if (chan == NULL) {
+                       stasis_http_response_error(response, 404, "Not Found",
+                                  "Channel not found");
+                       return NULL;
+               }
+
+               stasis_http_response_error(response, 409, "Conflict",
+                          "Channel not in Stasis application");
+               return NULL;
+       }
+
+       ao2_ref(control, +1);
+       return control;
+}
+
+void stasis_http_dial(struct ast_variable *headers, struct ast_dial_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_dial\n");
+}
+
+void stasis_http_continue_in_dialplan(
+       struct ast_variable *headers,
+       struct ast_continue_in_dialplan_args *args,
+       struct stasis_http_response *response)
+{
+       RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
+
+       ast_assert(response != NULL);
+
+       control = find_control(response, args->channel_id);
+       if (control == NULL) {
+               return;
+       }
+
+       stasis_app_control_continue(control);
+       stasis_http_response_no_content(response);
+}
+
+void stasis_http_answer_channel(struct ast_variable *headers,
+                               struct ast_answer_channel_args *args,
+                               struct stasis_http_response *response)
+{
+       RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
+
+       control = find_control(response, args->channel_id);
+       if (control == NULL) {
+               return;
+       }
+
+       if (stasis_app_control_answer(control) != 0) {
+               stasis_http_response_error(
+                       response, 500, "Internal Server Error",
+                       "Failed to answer channel");
+               return;
+       }
+
+       stasis_http_response_no_content(response);
+}
+
+void stasis_http_mute_channel(struct ast_variable *headers, struct ast_mute_channel_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_mute_channel\n");
+}
+void stasis_http_unmute_channel(struct ast_variable *headers, struct ast_unmute_channel_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_unmute_channel\n");
+}
+void stasis_http_hold_channel(struct ast_variable *headers, struct ast_hold_channel_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_hold_channel\n");
+}
+void stasis_http_unhold_channel(struct ast_variable *headers, struct ast_unhold_channel_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_unhold_channel\n");
+}
+void stasis_http_play_on_channel(struct ast_variable *headers, struct ast_play_on_channel_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_play_on_channel\n");
+}
+void stasis_http_record_channel(struct ast_variable *headers, struct ast_record_channel_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_record_channel\n");
+}
+void stasis_http_get_channel(struct ast_variable *headers,
+                            struct ast_get_channel_args *args,
+                            struct stasis_http_response *response)
+{
+       RAII_VAR(struct stasis_caching_topic *, caching_topic, NULL, ao2_cleanup);
+       RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+       struct ast_channel_snapshot *snapshot;
+
+       caching_topic = ast_channel_topic_all_cached();
+       if (!caching_topic) {
+               stasis_http_response_error(
+                       response, 500, "Internal Server Error",
+                       "Message bus not initialized");
+               return;
+       }
+       ao2_ref(caching_topic, +1);
+
+       msg = stasis_cache_get(caching_topic, ast_channel_snapshot_type(),
+                              args->channel_id);
+       if (!msg) {
+               stasis_http_response_error(
+                       response, 404, "Not Found",
+                       "Channel not found");
+               return;
+       }
+
+       snapshot = stasis_message_data(msg);
+       ast_assert(snapshot != NULL);
+
+       stasis_http_response_ok(response,
+                               ast_channel_snapshot_to_json(snapshot));
+}
+
+void stasis_http_delete_channel(struct ast_variable *headers,
+                               struct ast_delete_channel_args *args,
+                               struct stasis_http_response *response)
+{
+       RAII_VAR(struct ast_channel *, chan, NULL, ao2_cleanup);
+
+       chan = ast_channel_get_by_name(args->channel_id);
+       if (chan == NULL) {
+               stasis_http_response_error(
+                       response, 404, "Not Found",
+                       "Channel not found");
+               return;
+       }
+
+       ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT);
+
+       stasis_http_response_no_content(response);
+}
+
+void stasis_http_get_channels(struct ast_variable *headers,
+                             struct ast_get_channels_args *args,
+                             struct stasis_http_response *response)
+{
+       RAII_VAR(struct stasis_caching_topic *, caching_topic, NULL, ao2_cleanup);
+       RAII_VAR(struct ao2_container *, snapshots, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
+       struct ao2_iterator i;
+       void *obj;
+
+       caching_topic = ast_channel_topic_all_cached();
+       if (!caching_topic) {
+               stasis_http_response_error(
+                       response, 500, "Internal Server Error",
+                       "Message bus not initialized");
+               return;
+       }
+       ao2_ref(caching_topic, +1);
+
+       snapshots = stasis_cache_dump(caching_topic, ast_channel_snapshot_type());
+       if (!snapshots) {
+               stasis_http_response_alloc_failed(response);
+               return;
+       }
+
+       json = ast_json_array_create();
+       if (!json) {
+               stasis_http_response_alloc_failed(response);
+               return;
+       }
+
+       i = ao2_iterator_init(snapshots, 0);
+       while ((obj = ao2_iterator_next(&i))) {
+               RAII_VAR(struct stasis_message *, msg, obj, ao2_cleanup);
+               struct ast_channel_snapshot *snapshot = stasis_message_data(msg);
+               int r = ast_json_array_append(
+                       json, ast_channel_snapshot_to_json(snapshot));
+               if (r != 0) {
+                       stasis_http_response_alloc_failed(response);
+                       return;
+               }
+       }
+       ao2_iterator_destroy(&i);
+
+       stasis_http_response_ok(response, ast_json_ref(json));
+}
+
+void stasis_http_originate(struct ast_variable *headers,
+                          struct ast_originate_args *args,
+                          struct stasis_http_response *response)
+{
+       if (args->endpoint) {
+               ast_log(LOG_DEBUG, "Dialing specific endpoint %s\n", args->endpoint);
+       }
+
+       ast_log(LOG_DEBUG, "Dialing %s@%s\n", args->extension, args->context);
+       /* ast_pbx_outgoing_app - originates a channel, putting it into an application */
+}
diff --git a/res/stasis_http/resource_channels.h b/res/stasis_http/resource_channels.h
new file mode 100644 (file)
index 0000000..2c78589
--- /dev/null
@@ -0,0 +1,244 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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 Generated file - declares stubs to be implemented in
+ * res/stasis_http/resource_channels.c
+ *
+ * Channel resources
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+/*
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * !!!!!                               DO NOT EDIT                        !!!!!
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This file is generated by a mustache template. Please see the original
+ * template in rest-api-templates/stasis_http_resource.h.mustache
+ */
+
+#ifndef _ASTERISK_RESOURCE_CHANNELS_H
+#define _ASTERISK_RESOURCE_CHANNELS_H
+
+#include "asterisk/stasis_http.h"
+
+/*! \brief Argument struct for stasis_http_get_channels() */
+struct ast_get_channels_args {
+};
+/*!
+ * \brief List active channels.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_get_channels(struct ast_variable *headers, struct ast_get_channels_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_originate() */
+struct ast_originate_args {
+       /*! \brief Endpoint to call. If not specified, originate is routed via dialplan */
+       const char *endpoint;
+       /*! \brief Extension to dial */
+       const char *extension;
+       /*! \brief When routing via dialplan, the context use. If omitted, uses 'default' */
+       const char *context;
+};
+/*!
+ * \brief Create a new channel (originate).
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_originate(struct ast_variable *headers, struct ast_originate_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_get_channel() */
+struct ast_get_channel_args {
+       /*! \brief Channel's id */
+       const char *channel_id;
+};
+/*!
+ * \brief Channel details.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_get_channel(struct ast_variable *headers, struct ast_get_channel_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_delete_channel() */
+struct ast_delete_channel_args {
+       /*! \brief Channel's id */
+       const char *channel_id;
+};
+/*!
+ * \brief Delete (i.e. hangup) a channel.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_delete_channel(struct ast_variable *headers, struct ast_delete_channel_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_dial() */
+struct ast_dial_args {
+       /*! \brief Channel's id */
+       const char *channel_id;
+       /*! \brief Endpoint to call. If not specified, dial is routed via dialplan */
+       const char *endpoint;
+       /*! \brief Extension to dial */
+       const char *extension;
+       /*! \brief When routing via dialplan, the context use. If omitted, uses 'default' */
+       const char *context;
+};
+/*!
+ * \brief Create a new channel (originate) and bridge to this channel.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_dial(struct ast_variable *headers, struct ast_dial_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_continue_in_dialplan() */
+struct ast_continue_in_dialplan_args {
+       /*! \brief Channel's id */
+       const char *channel_id;
+};
+/*!
+ * \brief Exit application; continue execution in the dialplan.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_continue_in_dialplan(struct ast_variable *headers, struct ast_continue_in_dialplan_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_answer_channel() */
+struct ast_answer_channel_args {
+       /*! \brief Channel's id */
+       const char *channel_id;
+};
+/*!
+ * \brief Answer a channel.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_answer_channel(struct ast_variable *headers, struct ast_answer_channel_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_mute_channel() */
+struct ast_mute_channel_args {
+       /*! \brief Channel's id */
+       const char *channel_id;
+       /*! \brief Direction in which to mute audio */
+       const char *direction;
+};
+/*!
+ * \brief Mute a channel.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_mute_channel(struct ast_variable *headers, struct ast_mute_channel_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_unmute_channel() */
+struct ast_unmute_channel_args {
+       /*! \brief Channel's id */
+       const char *channel_id;
+       /*! \brief Direction in which to unmute audio */
+       const char *direction;
+};
+/*!
+ * \brief Unmute a channel.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_unmute_channel(struct ast_variable *headers, struct ast_unmute_channel_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_hold_channel() */
+struct ast_hold_channel_args {
+       /*! \brief Channel's id */
+       const char *channel_id;
+};
+/*!
+ * \brief Hold a channel.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_hold_channel(struct ast_variable *headers, struct ast_hold_channel_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_unhold_channel() */
+struct ast_unhold_channel_args {
+       /*! \brief Channel's id */
+       const char *channel_id;
+};
+/*!
+ * \brief Remove a channel from hold.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_unhold_channel(struct ast_variable *headers, struct ast_unhold_channel_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_play_on_channel() */
+struct ast_play_on_channel_args {
+       /*! \brief Channel's id */
+       const char *channel_id;
+       /*! \brief Media's URI to play. */
+       const char *media;
+};
+/*!
+ * \brief Start playback of media.
+ *
+ * The media URI may be any of a number of URI's. You may use http: and https: URI's, as well as sound: and recording: URI's. This operation creates a playback resource that can be used to control the playback of media (pause, rewind, fast forward, etc.)
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_play_on_channel(struct ast_variable *headers, struct ast_play_on_channel_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_record_channel() */
+struct ast_record_channel_args {
+       /*! \brief Channel's id */
+       const char *channel_id;
+       /*! \brief Recording's filename */
+       const char *name;
+       /*! \brief Format to encode audio in */
+       const char *format;
+       /*! \brief Maximum duration of the recording, in seconds. 0 for no limit */
+       int max_duration_seconds;
+       /*! \brief Maximum duration of silence, in seconds. 0 for no limit */
+       int max_silence_seconds;
+       /*! \brief If true, and recording already exists, append to recording */
+       int append;
+       /*! \brief Play beep when recording begins */
+       int beep;
+       /*! \brief DTMF input to terminate recording */
+       const char *terminate_on;
+};
+/*!
+ * \brief Start a recording.
+ *
+ * Record audio from a channel. Note that this will not capture audio sent to the channel. The bridge itself has a record feature if that's what you want.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_record_channel(struct ast_variable *headers, struct ast_record_channel_args *args, struct stasis_http_response *response);
+
+#endif /* _ASTERISK_RESOURCE_CHANNELS_H */
diff --git a/res/stasis_http/resource_endpoints.c b/res/stasis_http/resource_endpoints.c
new file mode 100644 (file)
index 0000000..b2611ba
--- /dev/null
@@ -0,0 +1,43 @@
+/* -*- C -*-
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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 for stasis-http stubs.
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+/*** MODULEINFO
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "resource_endpoints.h"
+
+void stasis_http_get_endpoint(struct ast_variable *headers, struct ast_get_endpoint_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_get_endpoint\n");
+}
+void stasis_http_get_endpoints(struct ast_variable *headers, struct ast_get_endpoints_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_get_endpoints\n");
+}
diff --git a/res/stasis_http/resource_endpoints.h b/res/stasis_http/resource_endpoints.h
new file mode 100644 (file)
index 0000000..57f4b91
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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 Generated file - declares stubs to be implemented in
+ * res/stasis_http/resource_endpoints.c
+ *
+ * Endpoint resources
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+/*
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * !!!!!                               DO NOT EDIT                        !!!!!
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This file is generated by a mustache template. Please see the original
+ * template in rest-api-templates/stasis_http_resource.h.mustache
+ */
+
+#ifndef _ASTERISK_RESOURCE_ENDPOINTS_H
+#define _ASTERISK_RESOURCE_ENDPOINTS_H
+
+#include "asterisk/stasis_http.h"
+
+/*! \brief Argument struct for stasis_http_get_endpoints() */
+struct ast_get_endpoints_args {
+       /*! \brief Filter endpoints by type (sip,iax2,dhadi,...) */
+       const char *with_type;
+};
+/*!
+ * \brief List available endoints.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_get_endpoints(struct ast_variable *headers, struct ast_get_endpoints_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_get_endpoint() */
+struct ast_get_endpoint_args {
+       /*! \brief ID of the endpoint */
+       const char *endpoint_id;
+};
+/*!
+ * \brief Details for an endpoint.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_get_endpoint(struct ast_variable *headers, struct ast_get_endpoint_args *args, struct stasis_http_response *response);
+
+#endif /* _ASTERISK_RESOURCE_ENDPOINTS_H */
diff --git a/res/stasis_http/resource_events.c b/res/stasis_http/resource_events.c
new file mode 100644 (file)
index 0000000..34563fe
--- /dev/null
@@ -0,0 +1,40 @@
+/* -*- C -*-
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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 for stasis-http stubs.
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+/*** MODULEINFO
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "resource_events.h"
+
+void stasis_http_event_websocket(struct ast_variable *headers, struct ast_event_websocket_args *args, struct stasis_http_response *response)
+{
+       /* TODO: This should promote this socket to a websocket connection */
+       ast_log(LOG_ERROR, "TODO: stasis_http_event_websocket\n");
+}
diff --git a/res/stasis_http/resource_events.h b/res/stasis_http/resource_events.h
new file mode 100644 (file)
index 0000000..0f58476
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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 Generated file - declares stubs to be implemented in
+ * res/stasis_http/resource_events.c
+ *
+ * WebSocket resource
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+/*
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * !!!!!                               DO NOT EDIT                        !!!!!
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This file is generated by a mustache template. Please see the original
+ * template in rest-api-templates/stasis_http_resource.h.mustache
+ */
+
+#ifndef _ASTERISK_RESOURCE_EVENTS_H
+#define _ASTERISK_RESOURCE_EVENTS_H
+
+#include "asterisk/stasis_http.h"
+
+/*! \brief Argument struct for stasis_http_event_websocket() */
+struct ast_event_websocket_args {
+       /*! \brief Comma seperated list of applications to subscribe to. */
+       const char *app;
+       /*! \brief RFC6455 header for upgrading a connection to a websocket. */
+       const char *upgrade;
+};
+/*!
+ * \brief WebSocket connection for events.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_event_websocket(struct ast_variable *headers, struct ast_event_websocket_args *args, struct stasis_http_response *response);
+
+#endif /* _ASTERISK_RESOURCE_EVENTS_H */
diff --git a/res/stasis_http/resource_playback.c b/res/stasis_http/resource_playback.c
new file mode 100644 (file)
index 0000000..99f2e09
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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 /api-docs/playback.{format} implementation- Playback control resources
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "resource_playback.h"
+
+void stasis_http_get_playback(struct ast_variable *headers, struct ast_get_playback_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_get_playback\n");
+}
+void stasis_http_stop_playback(struct ast_variable *headers, struct ast_stop_playback_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_stop_playback\n");
+}
+void stasis_http_control_playback(struct ast_variable *headers, struct ast_control_playback_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_control_playback\n");
+}
diff --git a/res/stasis_http/resource_playback.h b/res/stasis_http/resource_playback.h
new file mode 100644 (file)
index 0000000..36b05bc
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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 Generated file - declares stubs to be implemented in
+ * res/stasis_http/resource_playback.c
+ *
+ * Playback control resources
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+/*
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * !!!!!                               DO NOT EDIT                        !!!!!
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This file is generated by a mustache template. Please see the original
+ * template in rest-api-templates/stasis_http_resource.h.mustache
+ */
+
+#ifndef _ASTERISK_RESOURCE_PLAYBACK_H
+#define _ASTERISK_RESOURCE_PLAYBACK_H
+
+#include "asterisk/stasis_http.h"
+
+/*! \brief Argument struct for stasis_http_get_playback() */
+struct ast_get_playback_args {
+       /*! \brief Playback's id */
+       const char *playback_id;
+};
+/*!
+ * \brief Get a playback's details.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_get_playback(struct ast_variable *headers, struct ast_get_playback_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_stop_playback() */
+struct ast_stop_playback_args {
+       /*! \brief Playback's id */
+       const char *playback_id;
+};
+/*!
+ * \brief Stop a playback.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_stop_playback(struct ast_variable *headers, struct ast_stop_playback_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_control_playback() */
+struct ast_control_playback_args {
+       /*! \brief Playback's id */
+       const char *playback_id;
+       /*! \brief Operation to perform on the playback. */
+       const char *operation;
+};
+/*!
+ * \brief Get a playback's details.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_control_playback(struct ast_variable *headers, struct ast_control_playback_args *args, struct stasis_http_response *response);
+
+#endif /* _ASTERISK_RESOURCE_PLAYBACK_H */
diff --git a/res/stasis_http/resource_recordings.c b/res/stasis_http/resource_recordings.c
new file mode 100644 (file)
index 0000000..2400a68
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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 /api-docs/recordings.{format} implementation- Recording resources
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "resource_recordings.h"
+
+void stasis_http_get_recordings(struct ast_variable *headers, struct ast_get_recordings_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_get_recordings\n");
+}
+void stasis_http_get_stored_recordings(struct ast_variable *headers, struct ast_get_stored_recordings_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_get_stored_recordings\n");
+}
+void stasis_http_get_stored_recording(struct ast_variable *headers, struct ast_get_stored_recording_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_get_stored_recording\n");
+}
+void stasis_http_delete_stored_recording(struct ast_variable *headers, struct ast_delete_stored_recording_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_delete_stored_recording\n");
+}
+void stasis_http_get_live_recordings(struct ast_variable *headers, struct ast_get_live_recordings_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_get_live_recordings\n");
+}
+void stasis_http_get_live_recording(struct ast_variable *headers, struct ast_get_live_recording_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_get_live_recording\n");
+}
+void stasis_http_cancel_recording(struct ast_variable *headers, struct ast_cancel_recording_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_cancel_recording\n");
+}
+void stasis_http_stop_recording(struct ast_variable *headers, struct ast_stop_recording_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_stop_recording\n");
+}
+void stasis_http_pause_recording(struct ast_variable *headers, struct ast_pause_recording_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_pause_recording\n");
+}
+void stasis_http_unpause_recording(struct ast_variable *headers, struct ast_unpause_recording_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_unpause_recording\n");
+}
+void stasis_http_mute_recording(struct ast_variable *headers, struct ast_mute_recording_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_mute_recording\n");
+}
+void stasis_http_unmute_recording(struct ast_variable *headers, struct ast_unmute_recording_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_unmute_recording\n");
+}
diff --git a/res/stasis_http/resource_recordings.h b/res/stasis_http/resource_recordings.h
new file mode 100644 (file)
index 0000000..ee48e43
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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 Generated file - declares stubs to be implemented in
+ * res/stasis_http/resource_recordings.c
+ *
+ * Recording resources
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+/*
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * !!!!!                               DO NOT EDIT                        !!!!!
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This file is generated by a mustache template. Please see the original
+ * template in rest-api-templates/stasis_http_resource.h.mustache
+ */
+
+#ifndef _ASTERISK_RESOURCE_RECORDINGS_H
+#define _ASTERISK_RESOURCE_RECORDINGS_H
+
+#include "asterisk/stasis_http.h"
+
+/*! \brief Argument struct for stasis_http_get_recordings() */
+struct ast_get_recordings_args {
+};
+/*!
+ * \brief List all recordings.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_get_recordings(struct ast_variable *headers, struct ast_get_recordings_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_get_stored_recordings() */
+struct ast_get_stored_recordings_args {
+};
+/*!
+ * \brief List recordings that are complete.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_get_stored_recordings(struct ast_variable *headers, struct ast_get_stored_recordings_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_get_stored_recording() */
+struct ast_get_stored_recording_args {
+       /*! \brief Recording's id */
+       const char *recording_id;
+};
+/*!
+ * \brief Get a stored recording's details.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_get_stored_recording(struct ast_variable *headers, struct ast_get_stored_recording_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_delete_stored_recording() */
+struct ast_delete_stored_recording_args {
+       /*! \brief Recording's id */
+       const char *recording_id;
+};
+/*!
+ * \brief Delete a stored recording.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_delete_stored_recording(struct ast_variable *headers, struct ast_delete_stored_recording_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_get_live_recordings() */
+struct ast_get_live_recordings_args {
+};
+/*!
+ * \brief List libe recordings.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_get_live_recordings(struct ast_variable *headers, struct ast_get_live_recordings_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_get_live_recording() */
+struct ast_get_live_recording_args {
+       /*! \brief Recording's id */
+       const char *recording_id;
+};
+/*!
+ * \brief List live recordings.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_get_live_recording(struct ast_variable *headers, struct ast_get_live_recording_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_cancel_recording() */
+struct ast_cancel_recording_args {
+       /*! \brief Recording's id */
+       const char *recording_id;
+};
+/*!
+ * \brief Stop a live recording and discard it.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_cancel_recording(struct ast_variable *headers, struct ast_cancel_recording_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_stop_recording() */
+struct ast_stop_recording_args {
+       /*! \brief Recording's id */
+       const char *recording_id;
+};
+/*!
+ * \brief Stop a live recording and store it.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_stop_recording(struct ast_variable *headers, struct ast_stop_recording_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_pause_recording() */
+struct ast_pause_recording_args {
+       /*! \brief Recording's id */
+       const char *recording_id;
+};
+/*!
+ * \brief Pause a live recording.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_pause_recording(struct ast_variable *headers, struct ast_pause_recording_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_unpause_recording() */
+struct ast_unpause_recording_args {
+       /*! \brief Recording's id */
+       const char *recording_id;
+};
+/*!
+ * \brief Unpause a live recording.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_unpause_recording(struct ast_variable *headers, struct ast_unpause_recording_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_mute_recording() */
+struct ast_mute_recording_args {
+       /*! \brief Recording's id */
+       const char *recording_id;
+};
+/*!
+ * \brief Mute a live recording.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_mute_recording(struct ast_variable *headers, struct ast_mute_recording_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_unmute_recording() */
+struct ast_unmute_recording_args {
+       /*! \brief Recording's id */
+       const char *recording_id;
+};
+/*!
+ * \brief Unmute a live recording.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_unmute_recording(struct ast_variable *headers, struct ast_unmute_recording_args *args, struct stasis_http_response *response);
+
+#endif /* _ASTERISK_RESOURCE_RECORDINGS_H */
diff --git a/res/stasis_http/resource_sounds.c b/res/stasis_http/resource_sounds.c
new file mode 100644 (file)
index 0000000..a1808a1
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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 /api-docs/sounds.{format} implementation- Sound resources
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "resource_sounds.h"
+
+void stasis_http_get_sounds(struct ast_variable *headers, struct ast_get_sounds_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_get_sounds\n");
+}
+void stasis_http_get_stored_sound(struct ast_variable *headers, struct ast_get_stored_sound_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_get_stored_sound\n");
+}
diff --git a/res/stasis_http/resource_sounds.h b/res/stasis_http/resource_sounds.h
new file mode 100644 (file)
index 0000000..f3010a9
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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 Generated file - declares stubs to be implemented in
+ * res/stasis_http/resource_sounds.c
+ *
+ * Sound resources
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+/*
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * !!!!!                               DO NOT EDIT                        !!!!!
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This file is generated by a mustache template. Please see the original
+ * template in rest-api-templates/stasis_http_resource.h.mustache
+ */
+
+#ifndef _ASTERISK_RESOURCE_SOUNDS_H
+#define _ASTERISK_RESOURCE_SOUNDS_H
+
+#include "asterisk/stasis_http.h"
+
+/*! \brief Argument struct for stasis_http_get_sounds() */
+struct ast_get_sounds_args {
+       const char *lang;
+       const char *format;
+};
+/*!
+ * \brief List all sounds.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_get_sounds(struct ast_variable *headers, struct ast_get_sounds_args *args, struct stasis_http_response *response);
+/*! \brief Argument struct for stasis_http_get_stored_sound() */
+struct ast_get_stored_sound_args {
+       /*! \brief Sound's id */
+       const char *sound_id;
+};
+/*!
+ * \brief Get a sound's details.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_get_stored_sound(struct ast_variable *headers, struct ast_get_stored_sound_args *args, struct stasis_http_response *response);
+
+#endif /* _ASTERISK_RESOURCE_SOUNDS_H */
diff --git a/rest-api-templates/README.txt b/rest-api-templates/README.txt
new file mode 100644 (file)
index 0000000..e927ad7
--- /dev/null
@@ -0,0 +1,15 @@
+This directory contains templates and template processing code for generating
+HTTP bindings for the RESTful API's.
+
+The RESTful API's are declared using [Swagger][swagger]. While Swagger provides
+a [code generating toolkit][swagger-codegen], it requires Java to run, which
+would be an unusual dependency to require for Asterisk developers.
+
+This code generator is similar, but written in Python. Templates are processed
+by using [pystache][pystache], which is a fairly simply Python implementation of
+[mustache][mustache].
+
+ [swagger]: https://github.com/wordnik/swagger-core/wiki
+ [swagger-codegen]: https://github.com/wordnik/swagger-codegen
+ [pystache]: https://github.com/defunkt/pystache
+ [mustache]: http://mustache.github.io/
diff --git a/rest-api-templates/asterisk_processor.py b/rest-api-templates/asterisk_processor.py
new file mode 100644 (file)
index 0000000..81aefbb
--- /dev/null
@@ -0,0 +1,179 @@
+#
+# Asterisk -- An open source telephony toolkit.
+#
+# Copyright (C) 2013, Digium, Inc.
+#
+# David M. Lee, II <dlee@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.
+#
+
+"""Implementation of SwaggerPostProcessor which adds fields needed to generate
+Asterisk RESTful HTTP binding code.
+"""
+
+import re
+
+from swagger_model import *
+
+
+def simple_name(name):
+    """Removes the {markers} from a path segement.
+
+    @param name: Swagger path segement, with {pathVar} markers.
+    """
+    if name.startswith('{') and name.endswith('}'):
+        return name[1:-1]
+    return name
+
+
+def snakify(name):
+    """Helper to take a camelCase or dash-seperated name and make it
+    snake_case.
+    """
+    r = ''
+    prior_lower = False
+    for c in name:
+        if c.isupper() and prior_lower:
+            r += "_"
+        if c is '-':
+            c = '_'
+        prior_lower = c.islower()
+        r += c.lower()
+    return r
+
+
+class PathSegment(Stringify):
+    """Tree representation of a Swagger API declaration.
+    """
+    def __init__(self, name, parent):
+        """Ctor.
+
+        @param name: Name of this path segment. May have {pathVar} markers.
+        @param parent: Parent PathSegment.
+        """
+        #: Segment name, with {pathVar} markers removed
+        self.name = simple_name(name)
+        #: True if segment is a {pathVar}, else None.
+        self.is_wildcard = None
+        #: Underscore seperated name all ancestor segments
+        self.full_name = None
+        #: Dictionary of child PathSegements
+        self.__children = OrderedDict()
+        #: List of operations on this segement
+        self.operations = []
+
+        if self.name != name:
+            self.is_wildcard = True
+
+        if not self.name:
+            assert(not parent)
+            self.full_name = ''
+        if not parent or not parent.name:
+            self.full_name = name
+        else:
+            self.full_name = "%s_%s" % (parent.full_name, self.name)
+
+    def get_child(self, path):
+        """Walks decendents to get path, creating it if necessary.
+
+        @param path: List of path names.
+        @return: PageSegment corresponding to path.
+        """
+        assert simple_name(path[0]) == self.name
+        if (len(path) == 1):
+            return self
+        child = self.__children.get(path[1])
+        if not child:
+            child = PathSegment(path[1], self)
+            self.__children[path[1]] = child
+        return child.get_child(path[1:])
+
+    def children(self):
+        """Gets list of children.
+        """
+        return self.__children.values()
+
+    def num_children(self):
+        """Gets count of children.
+        """
+        return len(self.__children)
+
+
+class AsteriskProcessor(SwaggerPostProcessor):
+    """A SwaggerPostProcessor which adds fields needed to generate Asterisk
+    RESTful HTTP binding code.
+    """
+
+    #: How Swagger types map to C.
+    type_mapping = {
+        'string': 'const char *',
+        'boolean': 'int',
+        'number': 'int',
+        'int': 'int',
+        'long': 'long',
+        'double': 'double',
+        'float': 'float',
+    }
+
+    #: String conversion functions for string to C type.
+    convert_mapping = {
+        'const char *': '',
+        'int': 'atoi',
+        'long': 'atol',
+        'double': 'atof',
+    }
+
+    def process_api(self, resource_api, context):
+        # Derive a resource name from the API declaration's filename
+        resource_api.name = re.sub('\..*', '',
+                                   os.path.basename(resource_api.path))
+        # Now in all caps, from include guard
+        resource_api.name_caps = resource_api.name.upper()
+        # Construct the PathSegement tree for the API.
+        if resource_api.api_declaration:
+            resource_api.root_path = PathSegment('', None)
+            for api in resource_api.api_declaration.apis:
+                segment = resource_api.root_path.get_child(api.path.split('/'))
+                for operation in api.operations:
+                    segment.operations.append(operation)
+            # Since every API path should start with /[resource], root should
+            # have exactly one child.
+            if resource_api.root_path.num_children() != 1:
+                raise SwaggerError(
+                    "Should not mix resources in one API declaration", context)
+            # root_path isn't needed any more
+            resource_api.root_path = resource_api.root_path.children()[0]
+            if resource_api.name != resource_api.root_path.name:
+                raise SwaggerError(
+                    "API declaration name should match", context)
+            resource_api.root_full_name = resource_api.root_path.full_name
+
+    def process_operation(self, operation, context):
+        # Nicknames are camelcase, Asterisk coding is snake case
+        operation.c_nickname = snakify(operation.nickname)
+        operation.c_http_method = 'AST_HTTP_' + operation.http_method
+        if not operation.summary.endswith("."):
+            raise SwaggerError("Summary should end with .", context)
+
+    def process_parameter(self, parameter, context):
+        if not parameter.data_type in self.type_mapping:
+            raise SwaggerError(
+                "Invalid parameter type %s" % paramter.data_type, context)
+        # Parameter names are camelcase, Asterisk convention is snake case
+        parameter.c_name = snakify(parameter.name)
+        parameter.c_data_type = self.type_mapping[parameter.data_type]
+        parameter.c_convert = self.convert_mapping[parameter.c_data_type]
+        # You shouldn't put a space between 'char *' and the variable
+        if parameter.c_data_type.endswith('*'):
+            parameter.c_space = ''
+        else:
+            parameter.c_space = ' '
diff --git a/rest-api-templates/do-not-edit.mustache b/rest-api-templates/do-not-edit.mustache
new file mode 100644 (file)
index 0000000..05ba142
--- /dev/null
@@ -0,0 +1,4 @@
+{{! A partial for the big warning, so it's not in the template itself }}
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * !!!!!                               DO NOT EDIT                        !!!!!
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
diff --git a/rest-api-templates/make_stasis_http_stubs.py b/rest-api-templates/make_stasis_http_stubs.py
new file mode 100755 (executable)
index 0000000..1ec7c5c
--- /dev/null
@@ -0,0 +1,84 @@
+#!/usr/bin/env python
+# Asterisk -- An open source telephony toolkit.
+#
+# Copyright (C) 2013, Digium, Inc.
+#
+# David M. Lee, II <dlee@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.
+#
+
+try:
+    import pystache
+except ImportError:
+    print >> sys.stderr, "Pystache required. Please sudo pip install pystache."
+
+import os.path
+import pystache
+import sys
+
+from asterisk_processor import AsteriskProcessor
+from optparse import OptionParser
+from swagger_model import *
+from transform import Transform
+
+TOPDIR = os.path.dirname(os.path.abspath(__file__))
+
+
+def rel(file):
+    """Helper to get a file relative to the script's directory
+
+    @parm file: Relative file path.
+    """
+    return os.path.join(TOPDIR, file)
+
+API_TRANSFORMS = [
+    Transform(rel('res_stasis_http_resource.c.mustache'),
+              'res_stasis_http_{{name}}.c'),
+    Transform(rel('stasis_http_resource.h.mustache'),
+              'stasis_http/resource_{{name}}.h'),
+    Transform(rel('stasis_http_resource.c.mustache'),
+              'stasis_http/resource_{{name}}.c', False),
+]
+
+RESOURCES_TRANSFORMS = [
+    Transform(rel('stasis_http.make.mustache'), 'stasis_http.make'),
+]
+
+
+def main(argv):
+    parser = OptionParser(usage="Usage %prog [resources.json] [destdir]")
+
+    (options, args) = parser.parse_args(argv)
+
+    if len(args) != 3:
+        parser.error("Wrong number of arguments")
+
+    source = args[1]
+    dest_dir = args[2]
+    renderer = pystache.Renderer(search_dirs=[TOPDIR], missing_tags='strict')
+    processor = AsteriskProcessor()
+
+    # Build the models
+    base_dir = os.path.dirname(source)
+    resources = ResourceListing().load_file(source, processor)
+    for api in resources.apis:
+        api.load_api_declaration(base_dir, processor)
+
+    # Render the templates
+    for api in resources.apis:
+        for transform in API_TRANSFORMS:
+            transform.render(renderer, api, dest_dir)
+    for transform in RESOURCES_TRANSFORMS:
+        transform.render(renderer, resources, dest_dir)
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv) or 0)
diff --git a/rest-api-templates/odict.py b/rest-api-templates/odict.py
new file mode 100644 (file)
index 0000000..8f536a2
--- /dev/null
@@ -0,0 +1,261 @@
+# Downloaded from http://code.activestate.com/recipes/576693/
+# Licensed under the MIT License
+
+# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy.
+# Passes Python2.7's test suite and incorporates all the latest updates.
+
+try:
+    from thread import get_ident as _get_ident
+except ImportError:
+    from dummy_thread import get_ident as _get_ident
+
+try:
+    from _abcoll import KeysView, ValuesView, ItemsView
+except ImportError:
+    pass
+
+
+class OrderedDict(dict):
+    'Dictionary that remembers insertion order'
+    # An inherited dict maps keys to values.
+    # The inherited dict provides __getitem__, __len__, __contains__, and get.
+    # The remaining methods are order-aware.
+    # Big-O running times for all methods are the same as for regular dictionaries.
+
+    # The internal self.__map dictionary maps keys to links in a doubly linked list.
+    # The circular doubly linked list starts and ends with a sentinel element.
+    # The sentinel element never gets deleted (this simplifies the algorithm).
+    # Each link is stored as a list of length three:  [PREV, NEXT, KEY].
+
+    def __init__(self, *args, **kwds):
+        '''Initialize an ordered dictionary.  Signature is the same as for
+        regular dictionaries, but keyword arguments are not recommended
+        because their insertion order is arbitrary.
+
+        '''
+        if len(args) > 1:
+            raise TypeError('expected at most 1 arguments, got %d' % len(args))
+        try:
+            self.__root
+        except AttributeError:
+            self.__root = root = []                     # sentinel node
+            root[:] = [root, root, None]
+            self.__map = {}
+        self.__update(*args, **kwds)
+
+    def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
+        'od.__setitem__(i, y) <==> od[i]=y'
+        # Setting a new item creates a new link which goes at the end of the linked
+        # list, and the inherited dictionary is updated with the new key/value pair.
+        if key not in self:
+            root = self.__root
+            last = root[0]
+            last[1] = root[0] = self.__map[key] = [last, root, key]
+        dict_setitem(self, key, value)
+
+    def __delitem__(self, key, dict_delitem=dict.__delitem__):
+        'od.__delitem__(y) <==> del od[y]'
+        # Deleting an existing item uses self.__map to find the link which is
+        # then removed by updating the links in the predecessor and successor nodes.
+        dict_delitem(self, key)
+        link_prev, link_next, key = self.__map.pop(key)
+        link_prev[1] = link_next
+        link_next[0] = link_prev
+
+    def __iter__(self):
+        'od.__iter__() <==> iter(od)'
+        root = self.__root
+        curr = root[1]
+        while curr is not root:
+            yield curr[2]
+            curr = curr[1]
+
+    def __reversed__(self):
+        'od.__reversed__() <==> reversed(od)'
+        root = self.__root
+        curr = root[0]
+        while curr is not root:
+            yield curr[2]
+            curr = curr[0]
+
+    def clear(self):
+        'od.clear() -> None.  Remove all items from od.'
+        try:
+            for node in self.__map.itervalues():
+                del node[:]
+            root = self.__root
+            root[:] = [root, root, None]
+            self.__map.clear()
+        except AttributeError:
+            pass
+        dict.clear(self)
+
+    def popitem(self, last=True):
+        '''od.popitem() -> (k, v), return and remove a (key, value) pair.
+        Pairs are returned in LIFO order if last is true or FIFO order if false.
+
+        '''
+        if not self:
+            raise KeyError('dictionary is empty')
+        root = self.__root
+        if last:
+            link = root[0]
+            link_prev = link[0]
+            link_prev[1] = root
+            root[0] = link_prev
+        else:
+            link = root[1]
+            link_next = link[1]
+            root[1] = link_next
+            link_next[0] = root
+        key = link[2]
+        del self.__map[key]
+        value = dict.pop(self, key)
+        return key, value
+
+    # -- the following methods do not depend on the internal structure --
+
+    def keys(self):
+        'od.keys() -> list of keys in od'
+        return list(self)
+
+    def values(self):
+        'od.values() -> list of values in od'
+        return [self[key] for key in self]
+
+    def items(self):
+        'od.items() -> list of (key, value) pairs in od'
+        return [(key, self[key]) for key in self]
+
+    def iterkeys(self):
+        'od.iterkeys() -> an iterator over the keys in od'
+        return iter(self)
+
+    def itervalues(self):
+        'od.itervalues -> an iterator over the values in od'
+        for k in self:
+            yield self[k]
+
+    def iteritems(self):
+        'od.iteritems -> an iterator over the (key, value) items in od'
+        for k in self:
+            yield (k, self[k])
+
+    def update(*args, **kwds):
+        '''od.update(E, **F) -> None.  Update od from dict/iterable E and F.
+
+        If E is a dict instance, does:           for k in E: od[k] = E[k]
+        If E has a .keys() method, does:         for k in E.keys(): od[k] = E[k]
+        Or if E is an iterable of items, does:   for k, v in E: od[k] = v
+        In either case, this is followed by:     for k, v in F.items(): od[k] = v
+
+        '''
+        if len(args) > 2:
+            raise TypeError('update() takes at most 2 positional '
+                            'arguments (%d given)' % (len(args),))
+        elif not args:
+            raise TypeError('update() takes at least 1 argument (0 given)')
+        self = args[0]
+        # Make progressively weaker assumptions about "other"
+        other = ()
+        if len(args) == 2:
+            other = args[1]
+        if isinstance(other, dict):
+            for key in other:
+                self[key] = other[key]
+        elif hasattr(other, 'keys'):
+            for key in other.keys():
+                self[key] = other[key]
+        else:
+            for key, value in other:
+                self[key] = value
+        for key, value in kwds.items():
+            self[key] = value
+
+    __update = update  # let subclasses override update without breaking __init__
+
+    __marker = object()
+
+    def pop(self, key, default=__marker):
+        '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value.
+        If key is not found, d is returned if given, otherwise KeyError is raised.
+
+        '''
+        if key in self:
+            result = self[key]
+            del self[key]
+            return result
+        if default is self.__marker:
+            raise KeyError(key)
+        return default
+
+    def setdefault(self, key, default=None):
+        'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
+        if key in self:
+            return self[key]
+        self[key] = default
+        return default
+
+    def __repr__(self, _repr_running={}):
+        'od.__repr__() <==> repr(od)'
+        call_key = id(self), _get_ident()
+        if call_key in _repr_running:
+            return '...'
+        _repr_running[call_key] = 1
+        try:
+            if not self:
+                return '%s()' % (self.__class__.__name__,)
+            return '%s(%r)' % (self.__class__.__name__, self.items())
+        finally:
+            del _repr_running[call_key]
+
+    def __reduce__(self):
+        'Return state information for pickling'
+        items = [[k, self[k]] for k in self]
+        inst_dict = vars(self).copy()
+        for k in vars(OrderedDict()):
+            inst_dict.pop(k, None)
+        if inst_dict:
+            return (self.__class__, (items,), inst_dict)
+        return self.__class__, (items,)
+
+    def copy(self):
+        'od.copy() -> a shallow copy of od'
+        return self.__class__(self)
+
+    @classmethod
+    def fromkeys(cls, iterable, value=None):
+        '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
+        and values equal to v (which defaults to None).
+
+        '''
+        d = cls()
+        for key in iterable:
+            d[key] = value
+        return d
+
+    def __eq__(self, other):
+        '''od.__eq__(y) <==> od==y.  Comparison to another OD is order-sensitive
+        while comparison to a regular mapping is order-insensitive.
+
+        '''
+        if isinstance(other, OrderedDict):
+            return len(self)==len(other) and self.items() == other.items()
+        return dict.__eq__(self, other)
+
+    def __ne__(self, other):
+        return not self == other
+
+    # -- the following methods are only used in Python 2.7 --
+
+    def viewkeys(self):
+        "od.viewkeys() -> a set-like object providing a view on od's keys"
+        return KeysView(self)
+
+    def viewvalues(self):
+        "od.viewvalues() -> an object providing a view on od's values"
+        return ValuesView(self)
+
+    def viewitems(self):
+        "od.viewitems() -> a set-like object providing a view on od's items"
+        return ItemsView(self)
diff --git a/rest-api-templates/res_stasis_http_resource.c.mustache b/rest-api-templates/res_stasis_http_resource.c.mustache
new file mode 100644 (file)
index 0000000..b02ab62
--- /dev/null
@@ -0,0 +1,116 @@
+{{#api_declaration}}
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * {{{copyright}}}
+ *
+ * {{{author}}}
+{{! Template Copyright
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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.
+ */
+
+{{! Template for rendering the res_ module for an HTTP resource. }}
+/*
+{{> do-not-edit}}
+ * This file is generated by a mustache template. Please see the original
+ * template in rest-api-templates/res_stasis_http_resource.c.mustache
+ */
+
+/*! \file
+ *
+ * \brief {{{description}}}
+ *
+ * \author {{{author}}}
+ */
+
+/*** MODULEINFO
+       <depend type="module">res_stasis_http</depend>
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/module.h"
+#include "stasis_http/resource_{{name}}.h"
+
+{{#apis}}
+{{#operations}}
+/*!
+ * \brief Parameter parsing callback for {{path}}.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void stasis_http_{{c_nickname}}_cb(
+    struct ast_variable *get_params, struct ast_variable *path_vars,
+    struct ast_variable *headers, struct stasis_http_response *response)
+{
+       struct ast_{{c_nickname}}_args args = {};
+{{#has_parameters}}
+       struct ast_variable *i;
+
+{{#has_query_parameters}}
+       for (i = get_params; i; i = i->next) {
+{{#query_parameters}}
+               if (strcmp(i->name, "{{name}}") == 0) {
+                       args.{{c_name}} = {{c_convert}}(i->value);
+               } else
+{{/query_parameters}}
+               {}
+       }
+{{/has_query_parameters}}
+{{#has_path_parameters}}
+       for (i = path_vars; i; i = i->next) {
+{{#path_parameters}}
+               if (strcmp(i->name, "{{name}}") == 0) {
+                       args.{{c_name}} = {{c_convert}}(i->value);
+               } else
+{{/path_parameters}}
+               {}
+       }
+{{/has_path_parameters}}
+{{/has_parameters}}
+       stasis_http_{{c_nickname}}(headers, &args, response);
+}
+{{/operations}}
+{{/apis}}
+
+{{! The rest_handler partial expands to the tree of stasis_rest_handlers }}
+{{#root_path}}
+{{> rest_handler}}
+{{/root_path}}
+
+static int load_module(void)
+{
+       return stasis_http_add_handler(&{{root_full_name}});
+}
+
+static int unload_module(void)
+{
+       stasis_http_remove_handler(&{{root_full_name}});
+       return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT,
+       "RESTful API module - {{{description}}}",
+       .load = load_module,
+       .unload = unload_module,
+       .nonoptreq = "res_stasis_http",
+       );
+{{/api_declaration}}
diff --git a/rest-api-templates/rest_handler.mustache b/rest-api-templates/rest_handler.mustache
new file mode 100644 (file)
index 0000000..a7dfc60
--- /dev/null
@@ -0,0 +1,38 @@
+{{! -*- C -*-
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@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.
+}}
+{{!
+ * Recursive partial template to render a rest_handler. Used in
+ * res_stasis_http_resource.c.mustache.
+}}
+{{#children}}
+{{> rest_handler}}
+{{/children}}
+/*! \brief REST handler for {{path}} */
+static struct stasis_rest_handlers {{full_name}} = {
+       .path_segment = "{{name}}",
+{{#is_wildcard}}
+       .is_wildcard = 1,
+{{/is_wildcard}}
+       .callbacks = {
+{{#operations}}
+               [{{c_http_method}}] = stasis_http_{{c_nickname}}_cb,
+{{/operations}}
+       },
+       .num_children = {{num_children}},
+       .children = { {{#children}}&{{full_name}},{{/children}} }
+};
diff --git a/rest-api-templates/stasis_http.make.mustache b/rest-api-templates/stasis_http.make.mustache
new file mode 100644 (file)
index 0000000..103eb2b
--- /dev/null
@@ -0,0 +1,26 @@
+{{! -*- Makefile -*- }}
+#
+# Asterisk -- A telephony toolkit for Linux.
+#
+# Generated Makefile for res_stasis_http dependencies.
+#
+# Copyright (C) 2013, Digium, Inc.
+#
+# This program is free software, distributed under the terms of
+# the GNU General Public License
+#
+
+#
+# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+# !!!!!                               DO NOT EDIT                        !!!!!
+# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+# This file is generated by a template. Please see the original template at
+# rest-api-templates/stasis_http.make.mustache
+#
+
+{{#apis}}
+res_stasis_http_{{name}}.so: stasis_http/resource_{{name}}.o
+
+stasis_http/resource_{{name}}.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_stasis_http_{{name}})
+
+{{/apis}}
diff --git a/rest-api-templates/stasis_http_resource.c.mustache b/rest-api-templates/stasis_http_resource.c.mustache
new file mode 100644 (file)
index 0000000..7a55355
--- /dev/null
@@ -0,0 +1,41 @@
+{{#api_declaration}}
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * {{{copyright}}}
+ *
+ * {{{author}}}
+ *
+ * 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 {{{resource_path}}} implementation- {{{description}}}
+ *
+ * \author {{{author}}}
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "resource_{{name}}.h"
+
+{{#apis}}
+{{#operations}}
+void stasis_http_{{c_nickname}}(struct ast_variable *headers, struct ast_{{c_nickname}}_args *args, struct stasis_http_response *response)
+{
+       ast_log(LOG_ERROR, "TODO: stasis_http_{{c_nickname}}\n");
+}
+{{/operations}}
+{{/apis}}
+{{/api_declaration}}
diff --git a/rest-api-templates/stasis_http_resource.h.mustache b/rest-api-templates/stasis_http_resource.h.mustache
new file mode 100644 (file)
index 0000000..6e7af16
--- /dev/null
@@ -0,0 +1,68 @@
+{{#api_declaration}}
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * {{{copyright}}}
+ *
+ * {{{author}}}
+ *
+ * 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 Generated file - declares stubs to be implemented in
+ * res/stasis_http/resource_{{name}}.c
+ *
+ * {{{description}}}
+ *
+ * \author {{{author}}}
+ */
+
+/*
+{{> do-not-edit}}
+ * This file is generated by a mustache template. Please see the original
+ * template in rest-api-templates/stasis_http_resource.h.mustache
+ */
+
+#ifndef _ASTERISK_RESOURCE_{{name_caps}}_H
+#define _ASTERISK_RESOURCE_{{name_caps}}_H
+
+#include "asterisk/stasis_http.h"
+
+{{#apis}}
+{{#operations}}
+/*! \brief Argument struct for stasis_http_{{c_nickname}}() */
+struct ast_{{c_nickname}}_args {
+{{#parameters}}
+{{#description}}
+       /*! \brief {{{description}}} */
+{{/description}}
+       {{c_data_type}}{{c_space}}{{c_name}};
+{{/parameters}}
+};
+/*!
+ * \brief {{summary}}
+{{#notes}}
+ *
+ * {{{notes}}}
+{{/notes}}
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void stasis_http_{{c_nickname}}(struct ast_variable *headers, struct ast_{{c_nickname}}_args *args, struct stasis_http_response *response);
+{{/operations}}
+{{/apis}}
+
+#endif /* _ASTERISK_RESOURCE_{{name_caps}}_H */
+{{/api_declaration}}
diff --git a/rest-api-templates/swagger_model.py b/rest-api-templates/swagger_model.py
new file mode 100644 (file)
index 0000000..c42bb70
--- /dev/null
@@ -0,0 +1,482 @@
+
+# Asterisk -- An open source telephony toolkit.
+#
+# Copyright (C) 2013, Digium, Inc.
+#
+# David M. Lee, II <dlee@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.
+#
+
+"""Swagger data model objects.
+
+These objects should map directly to the Swagger api-docs, without a lot of
+additional fields. In the process of translation, it should also validate the
+model for consistency against the Swagger spec (i.e., fail if fields are
+missing, or have incorrect values).
+
+See https://github.com/wordnik/swagger-core/wiki/API-Declaration for the spec.
+"""
+
+import json
+import os.path
+import pprint
+import sys
+import traceback
+
+try:
+    from collections import OrderedDict
+except ImportError:
+    from odict import OrderedDict
+
+
+SWAGGER_VERSION = "1.1"
+
+
+class SwaggerError(Exception):
+    """Raised when an error is encountered mapping the JSON objects into the
+    model.
+    """
+
+    def __init__(self, msg, context, cause=None):
+        """Ctor.
+
+        @param msg: String message for the error.
+        @param context: Array of strings for current context in the API.
+        @param cause: Optional exception that caused this one.
+        """
+        super(Exception, self).__init__(msg, context, cause)
+
+
+class SwaggerPostProcessor(object):
+    """Post processing interface for model objects. This processor can add
+    fields to model objects for additional information to use in the
+    templates.
+    """
+    def process_api(self, resource_api, context):
+        """Post process a ResourceApi object.
+
+        @param resource_api: ResourceApi object.
+        @param contect: Current context in the API.
+        """
+        pass
+
+    def process_operation(self, operation, context):
+        """Post process a Operation object.
+
+        @param operation: Operation object.
+        @param contect: Current context in the API.
+        """
+        pass
+
+    def process_parameter(self, parameter, context):
+        """Post process a Parameter object.
+
+        @param parameter: Parameter object.
+        @param contect: Current context in the API.
+        """
+        pass
+
+
+class Stringify(object):
+    """Simple mix-in to make the repr of the model classes more meaningful.
+    """
+    def __repr__(self):
+        return "%s(%s)" % (self.__class__, pprint.saferepr(self.__dict__))
+
+
+class AllowableRange(Stringify):
+    """Model of a allowableValues of type RANGE
+
+    See https://github.com/wordnik/swagger-core/wiki/datatypes#complex-types
+    """
+    def __init__(self, min_value, max_value):
+        self.min_value = min_value
+        self.max_value = max_value
+
+
+class AllowableList(Stringify):
+    """Model of a allowableValues of type LIST
+
+    See https://github.com/wordnik/swagger-core/wiki/datatypes#complex-types
+    """
+    def __init__(self, values):
+        self.values = values
+
+
+def load_allowable_values(json, context):
+    """Parse a JSON allowableValues object.
+
+    This returns None, AllowableList or AllowableRange, depending on the
+    valueType in the JSON. If the valueType is not recognized, a SwaggerError
+    is raised.
+    """
+    if not json:
+        return None
+
+    if not 'valueType' in json:
+        raise SwaggerError("Missing valueType field", context)
+
+    value_type = json['valueType']
+
+    if value_type == 'RANGE':
+        if not 'min' in json:
+            raise SwaggerError("Missing field min", context)
+        if not 'max' in json:
+            raise SwaggerError("Missing field max", context)
+        return AllowableRange(json['min'], json['max'])
+    if value_type == 'LIST':
+        if not 'values' in json:
+            raise SwaggerError("Missing field values", context)
+        return AllowableList(json['values'])
+    raise SwaggerError("Unkown valueType %s" % value_type, context)
+
+
+class Parameter(Stringify):
+    """Model of an operation's parameter.
+
+    See https://github.com/wordnik/swagger-core/wiki/parameters
+    """
+
+    required_fields = ['name', 'paramType', 'dataType']
+
+    def __init__(self):
+        self.param_type = None
+        self.name = None
+        self.description = None
+        self.data_type = None
+        self.required = None
+        self.allowable_values = None
+        self.allow_multiple = None
+
+    def load(self, parameter_json, processor, context):
+        context = add_context(context, parameter_json, 'name')
+        validate_required_fields(parameter_json, self.required_fields, context)
+        self.name = parameter_json.get('name')
+        self.param_type = parameter_json.get('paramType')
+        self.description = parameter_json.get('description') or ''
+        self.data_type = parameter_json.get('dataType')
+        self.required = parameter_json.get('required') or False
+        self.allowable_values = load_allowable_values(
+            parameter_json.get('allowableValues'), context)
+        self.allow_multiple = parameter_json.get('allowMultiple') or False
+        processor.process_parameter(self, context)
+        return self
+
+    def is_type(self, other_type):
+        return self.param_type == other_type
+
+
+class ErrorResponse(Stringify):
+    """Model of an error response.
+
+    See https://github.com/wordnik/swagger-core/wiki/errors
+    """
+
+    required_fields = ['code', 'reason']
+
+    def __init__(self):
+        self.code = None
+        self.reason = None
+
+    def load(self, err_json, processor, context):
+        context = add_context(context, err_json, 'code')
+        validate_required_fields(err_json, self.required_fields, context)
+        self.code = err_json.get('code')
+        self.reason = err_json.get('reason')
+        return self
+
+
+class Operation(Stringify):
+    """Model of an operation on an API
+
+    See https://github.com/wordnik/swagger-core/wiki/API-Declaration#apis
+    """
+
+    required_fields = ['httpMethod', 'nickname', 'responseClass', 'summary']
+
+    def __init__(self):
+        self.http_method = None
+        self.nickname = None
+        self.response_class = None
+        self.parameters = []
+        self.summary = None
+        self.notes = None
+        self.error_responses = []
+
+    def load(self, op_json, processor, context):
+        context = add_context(context, op_json, 'nickname')
+        validate_required_fields(op_json, self.required_fields, context)
+        self.http_method = op_json.get('httpMethod')
+        self.nickname = op_json.get('nickname')
+        self.response_class = op_json.get('responseClass')
+        params_json = op_json.get('parameters') or []
+        self.parameters = [
+            Parameter().load(j, processor, context) for j in params_json]
+        self.query_parameters = [
+            p for p in self.parameters if p.is_type('query')]
+        self.has_query_parameters = self.query_parameters and True
+        self.path_parameters = [
+            p for p in self.parameters if p.is_type('path')]
+        self.has_path_parameters = self.path_parameters and True
+        self.header_parameters = [
+            p for p in self.parameters if p.is_type('header')]
+        self.has_header_parameters = self.header_parameters and True
+        self.has_parameters = self.has_query_parameters or \
+            self.has_path_parameters or self.has_header_parameters
+        self.summary = op_json.get('summary')
+        self.notes = op_json.get('notes')
+        err_json = op_json.get('errorResponses') or []
+        self.error_responses = [
+            ErrorResponse().load(j, processor, context) for j in err_json]
+        processor.process_operation(self, context)
+        return self
+
+
+class Api(Stringify):
+    """Model of a single API in an API declaration.
+
+    See https://github.com/wordnik/swagger-core/wiki/API-Declaration
+    """
+
+    required_fields = ['path', 'operations']
+
+    def __init__(self,):
+        self.path = None
+        self.description = None
+        self.operations = []
+
+    def load(self, api_json, processor, context):
+        context = add_context(context, api_json, 'path')
+        validate_required_fields(api_json, self.required_fields, context)
+        self.path = api_json.get('path')
+        self.description = api_json.get('description')
+        op_json = api_json.get('operations')
+        self.operations = [
+            Operation().load(j, processor, context) for j in op_json]
+        return self
+
+
+class Property(Stringify):
+    """Model of a Swagger property.
+
+    See https://github.com/wordnik/swagger-core/wiki/datatypes
+    """
+
+    required_fields = ['type']
+
+    def __init__(self, name):
+        self.name = name
+        self.type = None
+        self.description = None
+        self.required = None
+
+    def load(self, property_json, processor, context):
+        validate_required_fields(property_json, self.required_fields, context)
+        self.type = property_json.get('type')
+        self.description = property_json.get('description') or ''
+        self.required = property_json.get('required') or False
+        return self
+
+
+class Model(Stringify):
+    """Model of a Swagger model.
+
+    See https://github.com/wordnik/swagger-core/wiki/datatypes
+    """
+
+    def __init__(self):
+        self.id = None
+        self.properties = None
+
+    def load(self, model_json, processor, context):
+        context = add_context(context, model_json, 'id')
+        self.id = model_json.get('id')
+        props = model_json.get('properties').items() or []
+        self.properties = [
+            Property(k).load(j, processor, context) for (k, j) in props]
+        return self
+
+
+class ApiDeclaration(Stringify):
+    """Model class for an API Declaration.
+
+    See https://github.com/wordnik/swagger-core/wiki/API-Declaration
+    """
+
+    required_fields = [
+        'swaggerVersion', '_author', '_copyright', 'apiVersion', 'basePath',
+        'resourcePath', 'apis', 'models'
+    ]
+
+    def __init__(self):
+        self.swagger_version = None
+        self.author = None
+        self.copyright = None
+        self.api_version = None
+        self.base_path = None
+        self.resource_path = None
+        self.apis = []
+        self.models = []
+
+    def load_file(self, api_declaration_file, processor, context=[]):
+        context = context + [api_declaration_file]
+        try:
+            return self.__load_file(api_declaration_file, processor, context)
+        except SwaggerError:
+            raise
+        except Exception as e:
+            print >> sys.stderr, "Error: ", traceback.format_exc()
+            raise SwaggerError(
+                "Error loading %s" % api_declaration_file, context, e)
+
+    def __load_file(self, api_declaration_file, processor, context):
+        with open(api_declaration_file) as fp:
+            self.load(json.load(fp), processor, context)
+
+        expected_resource_path = '/api-docs/' + \
+            os.path.basename(api_declaration_file) \
+            .replace(".json", ".{format}")
+
+        if self.resource_path != expected_resource_path:
+            print "%s != %s" % (self.resource_path, expected_resource_path)
+            raise SwaggerError("resourcePath has incorrect value", context)
+
+        return self
+
+    def load(self, api_decl_json, processor, context):
+        """Loads a resource from a single Swagger resource.json file.
+        """
+        # If the version doesn't match, all bets are off.
+        self.swagger_version = api_decl_json.get('swaggerVersion')
+        if self.swagger_version != SWAGGER_VERSION:
+            raise SwaggerError(
+                "Unsupported Swagger version %s" % swagger_version, context)
+
+        validate_required_fields(api_decl_json, self.required_fields, context)
+
+        self.author = api_decl_json.get('_author')
+        self.copyright = api_decl_json.get('_copyright')
+        self.api_version = api_decl_json.get('apiVersion')
+        self.base_path = api_decl_json.get('basePath')
+        self.resource_path = api_decl_json.get('resourcePath')
+        api_json = api_decl_json.get('apis') or []
+        self.apis = [
+            Api().load(j, processor, context) for j in api_json]
+        models = api_decl_json.get('models').items() or []
+        self.models = OrderedDict(
+            (k, Model().load(j, processor, context)) for (k, j) in models)
+
+        for (name, model) in self.models.items():
+            c = list(context).append('model = %s' % name)
+            if name != model.id:
+                raise SwaggerError("Model id doesn't match name", c)
+        return self
+
+
+class ResourceApi(Stringify):
+    """Model of an API listing in the resources.json file.
+    """
+
+    required_fields = ['path', 'description']
+
+    def __init__(self):
+        self.path = None
+        self.description = None
+        self.api_declaration = None
+
+    def load(self, api_json, processor, context):
+        context = add_context(context, api_json, 'path')
+        validate_required_fields(api_json, self.required_fields, context)
+        self.path = api_json['path']
+        self.description = api_json['description']
+
+        if not self.path or self.path[0] != '/':
+            raise SwaggerError("Path must start with /", context)
+        processor.process_api(self, context)
+        return self
+
+    def load_api_declaration(self, base_dir, processor):
+        self.file = (base_dir + self.path).replace('{format}', 'json')
+        self.api_declaration = ApiDeclaration().load_file(self.file, processor)
+        processor.process_api(self, [self.file])
+
+
+class ResourceListing(Stringify):
+    """Model of Swagger's resources.json file.
+    """
+
+    required_fields = ['apiVersion', 'basePath', 'apis']
+
+    def __init__(self):
+        self.swagger_version = None
+        self.api_version = None
+        self.base_path = None
+        self.apis = None
+
+    def load_file(self, resource_file, processor):
+        context = [resource_file]
+        try:
+            return self.__load_file(resource_file, processor, context)
+        except SwaggerError:
+            raise
+        except Exception as e:
+            print >> sys.stderr, "Error: ", traceback.format_exc()
+            raise SwaggerError(
+                "Error loading %s" % resource_file, context, e)
+
+    def __load_file(self, resource_file, processor, context):
+        with open(resource_file) as fp:
+            return self.load(json.load(fp), processor, context)
+
+    def load(self, resources_json, processor, context):
+        # If the version doesn't match, all bets are off.
+        self.swagger_version = resources_json.get('swaggerVersion')
+        if self.swagger_version != SWAGGER_VERSION:
+            raise SwaggerError(
+                "Unsupported Swagger version %s" % swagger_version, context)
+
+        validate_required_fields(resources_json, self.required_fields, context)
+        self.api_version = resources_json['apiVersion']
+        self.base_path = resources_json['basePath']
+        apis_json = resources_json['apis']
+        self.apis = [
+            ResourceApi().load(j, processor, context) for j in apis_json]
+        return self
+
+
+def validate_required_fields(json, required_fields, context):
+    """Checks a JSON object for a set of required fields.
+
+    If any required field is missing, a SwaggerError is raised.
+
+    @param json: JSON object to check.
+    @param required_fields: List of required fields.
+    @param context: Current context in the API.
+    """
+    missing_fields = [f for f in required_fields if not f in json]
+
+    if missing_fields:
+        raise SwaggerError(
+            "Missing fields: %s" % ', '.join(missing_fields), context)
+
+
+def add_context(context, json, id_field):
+    """Returns a new context with a new item added to it.
+
+    @param context: Old context.
+    @param json: Current JSON object.
+    @param id_field: Field identifying this object.
+    @return New context with additional item.
+    """
+    if not id_field in json:
+        raise SwaggerError("Missing id_field: %s" % id_field, context)
+    return context + ['%s=%s' % (id_field, str(json[id_field]))]
diff --git a/rest-api-templates/transform.py b/rest-api-templates/transform.py
new file mode 100644 (file)
index 0000000..d0ef3c4
--- /dev/null
@@ -0,0 +1,53 @@
+#
+# Asterisk -- An open source telephony toolkit.
+#
+# Copyright (C) 2013, Digium, Inc.
+#
+# David M. Lee, II <dlee@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.
+#
+
+import os.path
+import pystache
+
+
+class Transform(object):
+    """Transformation for template to code.
+    """
+    def __init__(self, template_file, dest_file_template_str, overwrite=True):
+        """Ctor.
+
+        @param template_file: Filename of the mustache template.
+        @param dest_file_template_str: Destination file name. This is a
+            mustache template, so each resource can write to a unique file.
+        @param overwrite: If True, destination file is ovewritten if it exists.
+        """
+        template_str = unicode(open(template_file, "r").read())
+        self.template = pystache.parse(template_str)
+        dest_file_template_str = unicode(dest_file_template_str)
+        self.dest_file_template = pystache.parse(dest_file_template_str)
+        self.overwrite = overwrite
+
+    def render(self, renderer, model, dest_dir):
+        """Render a model according to this transformation.
+
+        @param render: Pystache renderer.
+        @param model: Model object to render.
+        @param dest_dir: Destination directory to write generated code.
+        """
+        dest_file = pystache.render(self.dest_file_template, model)
+        dest_file = os.path.join(dest_dir, dest_file)
+        if os.path.exists(dest_file) and not self.overwrite:
+            return
+        print "Rendering %s" % dest_file
+        with open(dest_file, "w") as out:
+            out.write(renderer.render(self.template, model))
diff --git a/rest-api/README.txt b/rest-api/README.txt
new file mode 100644 (file)
index 0000000..893bf87
--- /dev/null
@@ -0,0 +1,9 @@
+<!-- Written in -*- Markdown -*- -->
+
+This directory contains the specification for the Asterisk RESTful
+API. The API is documented using Swagger[1]. This is used to not only
+generate executable documentation pages for the API, but also to
+generate a lot of the boilerplate necessary for implementing the API
+with Asterisk's HTTP server.
+
+ [1]: http://swagger.wordnik.com/
diff --git a/rest-api/api-docs/asterisk.json b/rest-api/api-docs/asterisk.json
new file mode 100644 (file)
index 0000000..ef6c7b8
--- /dev/null
@@ -0,0 +1,47 @@
+{
+       "_copyright": "Copyright (C) 2012 - 2013, Digium, Inc.",
+       "_author": "David M. Lee, II <dlee@digium.com>",
+       "_svn_revision": "$Revision$",
+       "apiVersion": "0.0.1",
+       "swaggerVersion": "1.1",
+       "basePath": "http://localhost:8088/stasis",
+       "resourcePath": "/api-docs/asterisk.{format}",
+       "apis": [
+               {
+                       "path": "/asterisk/info",
+                       "description": "Asterisk system information (similar to core show settings)",
+                       "operations": [
+                               {
+                                       "httpMethod": "GET",
+                                       "summary": "Gets Asterisk system information.",
+                                       "nickname": "getAsteriskInfo",
+                                       "responseClass": "AsteriskInfo",
+                                       "parameters": [
+                                               {
+                                                       "name": "only",
+                                                       "description": "Filter information returned",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": true,
+                                                       "dataType": "string",
+                                                       "allowableValues": {
+                                                               "valueType": "LIST",
+                                                               "values": [
+                                                                       "version",
+                                                                       "modules",
+                                                                       "uptime"
+                                                               ]
+                                                       }
+                                               }
+                                       ]
+                               }
+                       ]
+               }
+       ],
+       "models": {
+               "AsteriskInfo": {
+                       "id": "AsteriskInfo",
+                       "properties": {}
+               }
+       }
+}
diff --git a/rest-api/api-docs/bridges.json b/rest-api/api-docs/bridges.json
new file mode 100644 (file)
index 0000000..fd0971a
--- /dev/null
@@ -0,0 +1,257 @@
+{
+       "_copyright": "Copyright (C) 2012 - 2013, Digium, Inc.",
+       "_author": "David M. Lee, II <dlee@digium.com>",
+       "_svn_revision": "$Revision$",
+       "apiVersion": "0.0.1",
+       "swaggerVersion": "1.1",
+       "basePath": "http://localhost:8088/stasis",
+       "resourcePath": "/api-docs/bridges.{format}",
+       "apis": [
+               {
+                       "path": "/bridges",
+                       "description": "Active bridges",
+                       "operations": [
+                               {
+                                       "httpMethod": "GET",
+                                       "summary": "List active bridges.",
+                                       "nickname": "getBridges",
+                                       "responseClass": "List[Bridge]"
+                               },
+                               {
+                                       "httpMethod": "POST",
+                                       "summary": "Create a new bridge.",
+                                       "notes": "This bridge persists until it has been shut down, or Asterisk has been shut down.",
+                                       "nickname": "newBridge",
+                                       "responseClass": "Bridge",
+                                       "parameters": [
+                                               {
+                                                       "name": "type",
+                                                       "description": "Type of bridge to create.",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string",
+                                                       "allowedValues": {
+                                                               "type": "LIST",
+                                                               "values": [
+                                                                       "two-party",
+                                                                       "multi-party",
+                                                                       "holding"
+                                                               ]
+                                                       }
+                                               }
+                                       ]
+                               }
+                       ]
+               },
+               {
+                       "path": "/bridges/{bridgeId}",
+                       "description": "Individual bridge",
+                       "operations": [
+                               {
+                                       "httpMethod": "GET",
+                                       "summary": "Get bridge details.",
+                                       "nickname": "getBridge",
+                                       "responseClass": "Bridge",
+                                       "parameters": [
+                                               {
+                                                       "name": "bridgeId",
+                                                       "description": "Bridge's id",
+                                                       "paramType": "path",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               }
+                                       ]
+                               },
+                               {
+                                       "httpMethod": "DELETE",
+                                       "summary": "Shut down a bridge bridge.",
+                                       "notes": "If any channels are in this bridge, they will be removed and resume whatever they were doing beforehand.",
+                                       "nickname": "deleteBridge",
+                                       "responseClass": "void",
+                                       "parameters": [
+                                               {
+                                                       "name": "bridgeId",
+                                                       "description": "Bridge's id",
+                                                       "paramType": "path",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               }
+                                       ]
+                               }
+                       ]
+               },
+               {
+                       "path": "/bridges/{bridgeId}/addChannel",
+                       "description": "Add a channel to a bridge",
+                       "operations": [
+                               {
+                                       "httpMethod": "POST",
+                                       "summary": "Add a channel to a bridge.",
+                                       "nickname": "addChannelToBridge",
+                                       "responseClass": "void",
+                                       "parameters": [
+                                               {
+                                                       "name": "bridgeId",
+                                                       "description": "Bridge's id",
+                                                       "paramType": "path",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "channel",
+                                                       "description": "Channel's id",
+                                                       "paramType": "query",
+                                                       "required": true,
+                                                       "allowMultiple": true,
+                                                       "dataType": "string"
+                                               }
+                                       ]
+                               }
+                       ]
+               },
+               {
+                       "path": "/bridges/{bridgeId}/removeChannel",
+                       "description": "Remove a channel from a bridge",
+                       "operations": [
+                               {
+                                       "httpMethod": "POST",
+                                       "summary": "Remove a channel from a bridge.",
+                                       "nickname": "removeChannelFromBridge",
+                                       "responseClass": "void",
+                                       "parameters": [
+                                               {
+                                                       "name": "bridgeId",
+                                                       "description": "Bridge's id",
+                                                       "paramType": "path",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "channel",
+                                                       "description": "Channel's id",
+                                                       "paramType": "query",
+                                                       "required": true,
+                                                       "allowMultiple": true,
+                                                       "dataType": "string"
+                                               }
+                                       ]
+                               }
+                       ]
+               },
+               {
+                       "path": "/bridges/{bridgeId}/record",
+                       "description": "Record audio on a bridge",
+                       "operations": [
+                               {
+                                       "httpMethod": "POST",
+                                       "summary": "Start a recording.",
+                                       "notes": "This records the mixed audio from all channels participating in this bridge.",
+                                       "nickname": "recordBridge",
+                                       "responseClass": "LiveRecording",
+                                       "parameters": [
+                                               {
+                                                       "name": "bridgeId",
+                                                       "description": "Bridge's id",
+                                                       "paramType": "path",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "name",
+                                                       "description": "Recording's filename",
+                                                       "paramType": "query",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "maxDurationSeconds",
+                                                       "description": "Maximum duration of the recording, in seconds. 0 for no limit.",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "int",
+                                                       "defaultValue": 0
+                                               },
+                                               {
+                                                       "name": "maxSilenceSeconds",
+                                                       "description": "Maximum duration of silence, in seconds. 0 for no limit.",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "int",
+                                                       "defaultValue": 0
+                                               },
+                                               {
+                                                       "name": "append",
+                                                       "description": "If true, and recording already exists, append to recording.",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "boolean",
+                                                       "defaultValue": false
+                                               },
+                                               {
+                                                       "name": "beep",
+                                                       "description": "Play beep when recording begins",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "boolean",
+                                                       "defaultValue": false
+                                               },
+                                               {
+                                                       "name": "terminateOn",
+                                                       "description": "DTMF input to terminate recording.",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string",
+                                                       "defaultValue": "none",
+                                                       "allowableValues": {
+                                                               "valueType": "LIST",
+                                                               "values": [
+                                                                       "none",
+                                                                       "any",
+                                                                       "*",
+                                                                       "#"
+                                                               ]
+                                                       }
+                                               }
+                                       ]
+                               }
+                       ]
+               }
+       ],
+       "models": {
+               "Bridge": {
+                       "id": "Bridge",
+                       "properties": {
+                               "bridgeType": {
+                                       "type": "string",
+                                       "description": "Type of bridge technology",
+                                       "required": true,
+                                       "allowedValues": {
+                                               "type": "LIST",
+                                               "values": [
+                                                       "two-party",
+                                                       "multi-party",
+                                                       "holding"
+                                               ]
+                                       }
+                               },
+                               "channels": {
+                                       "type": "List[string]",
+                                       "description": "Id's of channels participating in this bridge",
+                                       "required": true
+                               }
+                       }
+               }
+       }
+}
diff --git a/rest-api/api-docs/channels.json b/rest-api/api-docs/channels.json
new file mode 100644 (file)
index 0000000..c2d77b2
--- /dev/null
@@ -0,0 +1,645 @@
+{
+       "_copyright": "Copyright (C) 2012 - 2013, Digium, Inc.",
+       "_author": "David M. Lee, II <dlee@digium.com>",
+       "_svn_revision": "$Revision$",
+       "apiVersion": "0.0.1",
+       "swaggerVersion": "1.1",
+       "basePath": "http://localhost:8088/stasis",
+       "resourcePath": "/api-docs/channels.{format}",
+       "apis": [
+               {
+                       "path": "/channels",
+                       "description": "Active channels",
+                       "operations": [
+                               {
+                                       "httpMethod": "GET",
+                                       "summary": "List active channels.",
+                                       "nickname": "getChannels",
+                                       "responseClass": "List[Channel]"
+                               },
+                               {
+                                       "httpMethod": "POST",
+                                       "summary": "Create a new channel (originate).",
+                                       "nickname": "originate",
+                                       "responseClass": "Originated",
+                                       "parameters": [
+                                               {
+                                                       "name": "endpoint",
+                                                       "description": "Endpoint to call. If not specified, originate is routed via dialplan",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "extension",
+                                                       "description": "Extension to dial",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "context",
+                                                       "description": "When routing via dialplan, the context use. If omitted, uses 'default'",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               }
+                                       ]
+                               }
+                       ]
+               },
+               {
+                       "path": "/channels/{channelId}",
+                       "description": "Active channel",
+                       "operations": [
+                               {
+                                       "httpMethod": "GET",
+                                       "summary": "Channel details.",
+                                       "nickname": "getChannel",
+                                       "responseClass": "Channel",
+                                       "parameters": [
+                                               {
+                                                       "name": "channelId",
+                                                       "description": "Channel's id",
+                                                       "paramType": "path",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               }
+                                       ],
+                                       "errorResponses": [
+                                               {
+                                                       "code": 404,
+                                                       "reason": "Channel not found"
+                                               }
+                                       ]
+                               },
+                               {
+                                       "httpMethod": "DELETE",
+                                       "summary": "Delete (i.e. hangup) a channel.",
+                                       "nickname": "deleteChannel",
+                                       "responseClass": "void",
+                                       "parameters": [
+                                               {
+                                                       "name": "channelId",
+                                                       "description": "Channel's id",
+                                                       "paramType": "path",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               }
+                                       ],
+                                       "errorResponses": [
+                                               {
+                                                       "code": 404,
+                                                       "reason": "Channel not found"
+                                               }
+                                       ]
+                               }
+                       ]
+               },
+               {
+                       "path": "/channels/{channelId}/dial",
+                       "description": "Create a new channel (originate) and bridge to this channel",
+                       "operations": [
+                               {
+                                       "httpMethod": "POST",
+                                       "summary": "Create a new channel (originate) and bridge to this channel.",
+                                       "nickname": "dial",
+                                       "responseClass": "Dialed",
+                                       "parameters": [
+                                               {
+                                                       "name": "channelId",
+                                                       "description": "Channel's id",
+                                                       "paramType": "path",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "endpoint",
+                                                       "description": "Endpoint to call. If not specified, dial is routed via dialplan",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "extension",
+                                                       "description": "Extension to dial",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "context",
+                                                       "description": "When routing via dialplan, the context use. If omitted, uses 'default'",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               }
+                                       ],
+                                       "errorResponses": [
+                                               {
+                                                       "code": 404,
+                                                       "reason": "Channel not found"
+                                               },
+                                               {
+                                                       "code": 409,
+                                                       "reason": "Channel not in a Stasis application"
+                                               }
+                                       ]
+                               }
+                       ]
+               },
+               {
+                       "path": "/channels/{channelId}/continue",
+                       "description": "Exit application; continue execution in the dialplan",
+                       "operations": [
+                               {
+                                       "httpMethod": "POST",
+                                       "summary": "Exit application; continue execution in the dialplan.",
+                                       "nickname": "continueInDialplan",
+                                       "responseClass": "void",
+                                       "parameters": [
+                                               {
+                                                       "name": "channelId",
+                                                       "description": "Channel's id",
+                                                       "paramType": "path",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               }
+                                       ],
+                                       "errorResponses": [
+                                               {
+                                                       "code": 404,
+                                                       "reason": "Channel not found"
+                                               },
+                                               {
+                                                       "code": 409,
+                                                       "reason": "Channel not in a Stasis application"
+                                               }
+                                       ]
+                               }
+                       ]
+               },
+               {
+                       "path": "/channels/{channelId}/answer",
+                       "description": "Answer a channel",
+                       "operations": [
+                               {
+                                       "httpMethod": "POST",
+                                       "summary": "Answer a channel.",
+                                       "nickname": "answerChannel",
+                                       "responseClass": "void",
+                                       "parameters": [
+                                               {
+                                                       "name": "channelId",
+                                                       "description": "Channel's id",
+                                                       "paramType": "path",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               }
+                                       ],
+                                       "errorResponses": [
+                                               {
+                                                       "code": 404,
+                                                       "reason": "Channel not found"
+                                               }
+                                       ]
+                               }
+                       ]
+               },
+               {
+                       "path": "/channels/{channelId}/mute",
+                       "description": "Mute a channel",
+                       "operations": [
+                               {
+                                       "httpMethod": "POST",
+                                       "summary": "Mute a channel.",
+                                       "nickname": "muteChannel",
+                                       "responseClass": "void",
+                                       "parameters": [
+                                               {
+                                                       "name": "channelId",
+                                                       "description": "Channel's id",
+                                                       "paramType": "path",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "direction",
+                                                       "description": "Direction in which to mute audio",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string",
+                                                       "defaultValue": "both",
+                                                       "allowableValues": {
+                                                               "valueType": "LIST",
+                                                               "values": [
+                                                                       "both",
+                                                                       "in",
+                                                                       "out"
+                                                               ]
+                                                       }
+                                               }
+                                       ],
+                                       "errorResponses": [
+                                               {
+                                                       "code": 404,
+                                                       "reason": "Channel not found"
+                                               },
+                                               {
+                                                       "code": 409,
+                                                       "reason": "Channel not in a Stasis application"
+                                               }
+                                       ]
+                               }
+                       ]
+               },
+               {
+                       "path": "/channels/{channelId}/unmute",
+                       "description": "Unmute a channel",
+                       "operations": [
+                               {
+                                       "httpMethod": "POST",
+                                       "summary": "Unmute a channel.",
+                                       "nickname": "unmuteChannel",
+                                       "responseClass": "void",
+                                       "parameters": [
+                                               {
+                                                       "name": "channelId",
+                                                       "description": "Channel's id",
+                                                       "paramType": "path",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "direction",
+                                                       "description": "Direction in which to unmute audio",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string",
+                                                       "defaultValue": "both",
+                                                       "allowableValues": {
+                                                               "valueType": "LIST",
+                                                               "values": [
+                                                                       "both",
+                                                                       "in",
+                                                                       "out"
+                                                               ]
+                                                       }
+                                               }
+                                       ],
+                                       "errorResponses": [
+                                               {
+                                                       "code": 404,
+                                                       "reason": "Channel not found"
+                                               },
+                                               {
+                                                       "code": 409,
+                                                       "reason": "Channel not in a Stasis application"
+                                               }
+                                       ]
+                               }
+                       ]
+               },
+               {
+                       "path": "/channels/{channelId}/hold",
+                       "description": "Put a channel on hold",
+                       "operations": [
+                               {
+                                       "httpMethod": "POST",
+                                       "summary": "Hold a channel.",
+                                       "nickname": "holdChannel",
+                                       "responseClass": "void",
+                                       "parameters": [
+                                               {
+                                                       "name": "channelId",
+                                                       "description": "Channel's id",
+                                                       "paramType": "path",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               }
+                                       ],
+                                       "errorResponses": [
+                                               {
+                                                       "code": 404,
+                                                       "reason": "Channel not found"
+                                               },
+                                               {
+                                                       "code": 409,
+                                                       "reason": "Channel not in a Stasis application"
+                                               }
+                                       ]
+                               }
+                       ]
+               },
+               {
+                       "path": "/channels/{channelId}/unhold",
+                       "description": "Remove a channel from hold",
+                       "operations": [
+                               {
+                                       "httpMethod": "POST",
+                                       "summary": "Remove a channel from hold.",
+                                       "nickname": "unholdChannel",
+                                       "responseClass": "void",
+                                       "parameters": [
+                                               {
+                                                       "name": "channelId",
+                                                       "description": "Channel's id",
+                                                       "paramType": "path",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               }
+                                       ],
+                                       "errorResponses": [
+                                               {
+                                                       "code": 404,
+                                                       "reason": "Channel not found"
+                                               },
+                                               {
+                                                       "code": 409,
+                                                       "reason": "Channel not in a Stasis application"
+                                               }
+                                       ]
+                               }
+                       ]
+               },
+               {
+                       "path": "/channels/{channelId}/play",
+                       "description": "Play media to a channel",
+                       "operations": [
+                               {
+                                       "httpMethod": "POST",
+                                       "summary": "Start playback of media.",
+                                       "notes": "The media URI may be any of a number of URI's. You may use http: and https: URI's, as well as sound: and recording: URI's. This operation creates a playback resource that can be used to control the playback of media (pause, rewind, fast forward, etc.)",
+                                       "nickname": "playOnChannel",
+                                       "responseClass": "Playback",
+                                       "parameters": [
+                                               {
+                                                       "name": "channelId",
+                                                       "description": "Channel's id",
+                                                       "paramType": "path",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "media",
+                                                       "description": "Media's URI to play.",
+                                                       "paramType": "query",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               }
+                                       ],
+                                       "errorResponses": [
+                                               {
+                                                       "code": 404,
+                                                       "reason": "Channel not found"
+                                               },
+                                               {
+                                                       "code": 409,
+                                                       "reason": "Channel not in a Stasis application"
+                                               }
+                                       ]
+                               }
+                       ]
+               },
+               {
+                       "path": "/channels/{channelId}/record",
+                       "description": "Record audio from a channel",
+                       "operations": [
+                               {
+                                       "httpMethod": "POST",
+                                       "summary": "Start a recording.",
+                                       "notes": "Record audio from a channel. Note that this will not capture audio sent to the channel. The bridge itself has a record feature if that's what you want.",
+                                       "nickname": "recordChannel",
+                                       "responseClass": "void",
+                                       "parameters": [
+                                               {
+                                                       "name": "channelId",
+                                                       "description": "Channel's id",
+                                                       "paramType": "path",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "name",
+                                                       "description": "Recording's filename",
+                                                       "paramType": "query",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "format",
+                                                       "description": "Format to encode audio in",
+                                                       "paramType": "query",
+                                                       "required": true,
+                                                       "allowMultiple": true,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "maxDurationSeconds",
+                                                       "description": "Maximum duration of the recording, in seconds. 0 for no limit",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "int",
+                                                       "defaultValue": 0
+                                               },
+                                               {
+                                                       "name": "maxSilenceSeconds",
+                                                       "description": "Maximum duration of silence, in seconds. 0 for no limit",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "int",
+                                                       "defaultValue": 0
+                                               },
+                                               {
+                                                       "name": "append",
+                                                       "description": "If true, and recording already exists, append to recording",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "boolean",
+                                                       "defaultValue": false
+                                               },
+                                               {
+                                                       "name": "beep",
+                                                       "description": "Play beep when recording begins",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "boolean",
+                                                       "defaultValue": false
+                                               },
+                                               {
+                                                       "name": "terminateOn",
+                                                       "description": "DTMF input to terminate recording",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string",
+