Add vtable and methods for to_json and to_ami for Stasis messages
authorDavid M. Lee <dlee@digium.com>
Tue, 11 Jun 2013 15:46:35 +0000 (15:46 +0000)
committerDavid M. Lee <dlee@digium.com>
Tue, 11 Jun 2013 15:46:35 +0000 (15:46 +0000)
When a Stasis message type is defined in a loadable module, handling
those messages for AMI and res_stasis events can be cumbersome.

This patch adds a vtable to stasis_message_type, with to_ami and
to_json virtual functions. These allow messages to be handled
abstractly without putting module-specific code in core.

As an example, the VarSet AMI event was refactored to use the to_ami
virtual function.

(closes issue ASTERISK-21817)
Review: https://reviewboard.asterisk.org/r/2579/

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

include/asterisk/stasis.h
main/manager.c
main/manager_channels.c
main/parking.c
main/stasis_channels.c
main/stasis_message.c
tests/test_stasis.c
tests/test_stasis_channels.c

index 6a55d09..bd303eb 100644 (file)
  * certain loads.
  */
 
+#include "asterisk/json.h"
+#include "asterisk/manager.h"
 #include "asterisk/utils.h"
 
 /*! @{ */
 struct stasis_message_type;
 
 /*!
- * \brief Register a new message type.
+ * \brief Opaque type for a Stasis message.
+ * \since 12
+ */
+struct stasis_message;
+
+/*!
+ * \brief Virtual table providing methods for messages.
+ * \since 12
+ */
+struct stasis_message_vtable {
+       /*!
+        * \brief Build the JSON representation of the message.
+        *
+        * May be \c NULL, or may return \c NULL, to indicate no representation.
+        * The returned object should be ast_json_unref()'ed.
+        *
+        * \param message Message to convert to JSON string.
+        * \return Newly allocated JSON message.
+        * \return \c NULL on error.
+        * \return \c NULL if JSON format is not supported.
+        */
+       struct ast_json *(*to_json)(struct stasis_message *message);
+
+       /*!
+        * \brief Build the AMI representation of the message.
+        *
+        * May be \c NULL, or may return \c NULL, to indicate no representation.
+        * The returned object should be ao2_cleankup()'ed.
+        *
+        * \param message Message to convert to AMI string.
+        * \return Newly allocated \ref ast_manager_event_blob.
+        * \return \c NULL on error.
+        * \return \c NULL if AMI format is not supported.
+        */
+       struct ast_manager_event_blob *(*to_ami)(
+               struct stasis_message *message);
+};
+
+/*!
+ * \brief Create a new message type.
  *
  * \ref stasis_message_type is an AO2 object, so ao2_cleanup() when you're done
  * with it.
  *
  * \param name Name of the new type.
+ * \param vtable Virtual table of message methods. May be \c NULL.
  * \return Pointer to the new type.
  * \return \c NULL on error.
  * \since 12
  */
-struct stasis_message_type *stasis_message_type_create(const char *name);
+struct stasis_message_type *stasis_message_type_create(const char *name,
+       struct stasis_message_vtable *vtable);
 
 /*!
  * \brief Gets the name of a given message type
@@ -187,12 +230,6 @@ struct stasis_message_type *stasis_message_type_create(const char *name);
 const char *stasis_message_type_name(const struct stasis_message_type *type);
 
 /*!
- * \brief Opaque type for a Stasis message.
- * \since 12
- */
-struct stasis_message;
-
-/*!
  * \brief Create a new message.
  *
  * This message is an \c ao2 object, and must be ao2_cleanup()'ed when you are done
@@ -234,6 +271,32 @@ void *stasis_message_data(const struct stasis_message *msg);
  */
 const struct timeval *stasis_message_timestamp(const struct stasis_message *msg);
 
+/*!
+ * \brief Build the JSON representation of the message.
+ *
+ * May return \c NULL, to indicate no representation. The returned object should
+ * be ast_json_unref()'ed.
+ *
+ * \param message Message to convert to JSON string.
+ * \return Newly allocated string with JSON message.
+ * \return \c NULL on error.
+ * \return \c NULL if JSON format is not supported.
+ */
+struct ast_json *stasis_message_to_json(struct stasis_message *message);
+
+/*!
+ * \brief Build the AMI representation of the message.
+ *
+ * May return \c NULL, to indicate no representation. The returned object should
+ * be ao2_cleanup()'ed.
+ *
+ * \param message Message to convert to AMI.
+ * \return \c NULL on error.
+ * \return \c NULL if AMI format is not supported.
+ */
+struct ast_manager_event_blob *stasis_message_to_ami(
+       struct stasis_message *message);
+
 /*! @} */
 
 /*! @{ */
@@ -635,20 +698,37 @@ void stasis_log_bad_type_access(const char *name);
 /*!
  * \brief Boiler-plate removing macro for defining message types.
  *
+ * \code
+ *     STASIS_MESSAGE_TYPE_DEFN(ast_foo_type,
+ *             .to_ami = foo_to_ami,
+ *             .to_json = foo_to_json,
+ *             );
+ * \endcode
+ *
  * \param name Name of message type.
- * \since 12
- */
-#define STASIS_MESSAGE_TYPE_DEFN(name)                         \
-       static struct stasis_message_type *_priv_ ## name;      \
-       struct stasis_message_type *name(void) {                \
-               if (_priv_ ## name == NULL) {                   \
-                       stasis_log_bad_type_access(#name);      \
-               }                                               \
-               return _priv_ ## name;                          \
+ * \param ... Virtual table methods for messages of this type.
+ * \since 12
+ */
+#define STASIS_MESSAGE_TYPE_DEFN(name, ...)                            \
+       static struct stasis_message_vtable _priv_ ## name ## _v = {    \
+               __VA_ARGS__                                             \
+       };                                                              \
+       static struct stasis_message_type *_priv_ ## name;              \
+       struct stasis_message_type *name(void) {                        \
+               if (_priv_ ## name == NULL) {                           \
+                       stasis_log_bad_type_access(#name);              \
+               }                                                       \
+               return _priv_ ## name;                                  \
        }
 
 /*!
- * \brief Boiler-plate removing macro for initializing message types.
+* \brief Boiler-plate removing macro for initializing message types.
+ *
+ * \code
+ *     if (STASIS_MESSAGE_TYPE_INIT(ast_foo_type) != 0) {
+ *             return -1;
+ *     }
+ * \endcode
  *
  * \param name Name of message type.
  * \return 0 if initialization is successful.
@@ -658,8 +738,9 @@ void stasis_log_bad_type_access(const char *name);
 #define STASIS_MESSAGE_TYPE_INIT(name)                                 \
        ({                                                              \
                ast_assert(_priv_ ## name == NULL);                     \
-               _priv_ ## name = stasis_message_type_create(#name);     \
-                       _priv_ ## name ? 0 : -1;                        \
+               _priv_ ## name = stasis_message_type_create(#name,      \
+                       &_priv_ ## name ## _v);                         \
+               _priv_ ## name ? 0 : -1;                                \
        })
 
 /*!
index b8e55de..eb01cc5 100644 (file)
@@ -1313,6 +1313,23 @@ struct ast_str *ast_manager_str_from_json_object(struct ast_json *blob, key_excl
        return output_str;
 }
 
+static void manager_default_msg_cb(void *data, struct stasis_subscription *sub,
+                                   struct stasis_topic *topic,
+                                   struct stasis_message *message)
+{
+       RAII_VAR(struct ast_manager_event_blob *, ev, NULL, ao2_cleanup);
+
+       ev = stasis_message_to_ami(message);
+
+       if (ev == NULL) {
+               /* Not and AMI message; disregard */
+               return;
+       }
+
+       manager_event(ev->event_flags, ev->manager_event, "%s",
+               ev->extra_fields);
+}
+
 static void manager_generic_msg_cb(void *data, struct stasis_subscription *sub,
                                    struct stasis_topic *topic,
                                    struct stasis_message *message)
@@ -7686,7 +7703,12 @@ static void manager_shutdown(void)
  */
 static int manager_subscriptions_init(void)
 {
-       STASIS_MESSAGE_TYPE_INIT(ast_manager_get_generic_type);
+       int res;
+
+       res = STASIS_MESSAGE_TYPE_INIT(ast_manager_get_generic_type);
+       if (res != 0) {
+               return -1;
+       }
        manager_topic = stasis_topic_create("manager_topic");
        if (!manager_topic) {
                return -1;
@@ -7696,10 +7718,13 @@ static int manager_subscriptions_init(void)
                return -1;
        }
 
-       if (stasis_message_router_add(stasis_router,
-                                        ast_manager_get_generic_type(),
-                                        manager_generic_msg_cb,
-                                        NULL)) {
+       res |= stasis_message_router_set_default(stasis_router,
+               manager_default_msg_cb, NULL);
+
+       res |= stasis_message_router_add(stasis_router,
+               ast_manager_get_generic_type(), manager_generic_msg_cb, NULL);
+
+       if (res != 0) {
                return -1;
        }
        return 0;
index b6eeb60..277dc87 100644 (file)
@@ -710,49 +710,6 @@ static void channel_snapshot_update(void *data, struct stasis_subscription *sub,
        }
 }
 
-static void channel_varset_cb(void *data, struct stasis_subscription *sub,
-       struct stasis_topic *topic, struct stasis_message *message)
-{
-       struct ast_channel_blob *obj = stasis_message_data(message);
-       RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free);
-       const char *variable = ast_json_string_get(ast_json_object_get(obj->blob, "variable"));
-       const char *value = ast_json_string_get(ast_json_object_get(obj->blob, "value"));
-
-       if (obj->snapshot) {
-               channel_event_string = ast_manager_build_channel_state_string(obj->snapshot);
-       } else {
-               channel_event_string = ast_str_create(35);
-               ast_str_set(&channel_event_string, 0,
-                           "Channel: none\r\n"
-                           "Uniqueid: none\r\n");
-       }
-
-       if (!channel_event_string) {
-               return;
-       }
-
-       /*** DOCUMENTATION
-               <managerEventInstance>
-                       <synopsis>Raised when a variable is set to a particular value.</synopsis>
-                       <syntax>
-                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
-                               <parameter name="Variable">
-                                       <para>The variable being set.</para>
-                               </parameter>
-                               <parameter name="Value">
-                                       <para>The new value of the variable.</para>
-                               </parameter>
-                       </syntax>
-               </managerEventInstance>
-       ***/
-       manager_event(EVENT_FLAG_DIALPLAN, "VarSet",
-                     "%s"
-                     "Variable: %s\r\n"
-                     "Value: %s\r\n",
-                     ast_str_buffer(channel_event_string),
-                     variable, value);
-}
-
 static int userevent_exclusion_cb(const char *key)
 {
        if (!strcmp("type", key)) {
@@ -1280,11 +1237,6 @@ int manager_channels_init(void)
                                         NULL);
 
        ret |= stasis_message_router_add(message_router,
-                                        ast_channel_varset_type(),
-                                        channel_varset_cb,
-                                        NULL);
-
-       ret |= stasis_message_router_add(message_router,
                                         ast_channel_user_event_type(),
                                         channel_user_event_cb,
                                         NULL);
index 2a5b72e..a7cd0ba 100644 (file)
@@ -35,7 +35,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/channel.h"
 
 /*! \brief Message type for parked calls */
-static struct stasis_message_type *parked_call_type;
+STASIS_MESSAGE_TYPE_DEFN(ast_parked_call_type);
 
 /*! \brief Topic for parking lots */
 static struct stasis_topic *parking_topic;
@@ -48,15 +48,14 @@ static ast_bridge_channel_park_fn ast_bridge_channel_park_func = NULL;
 
 void ast_parking_stasis_init(void)
 {
-       parked_call_type = stasis_message_type_create("ast_parked_call");
+       STASIS_MESSAGE_TYPE_INIT(ast_parked_call_type);
        parking_topic = stasis_topic_create("ast_parking");
 }
 
 void ast_parking_stasis_disable(void)
 {
-       ao2_cleanup(parked_call_type);
+       STASIS_MESSAGE_TYPE_CLEANUP(ast_parked_call_type);
        ao2_cleanup(parking_topic);
-       parked_call_type = NULL;
        parking_topic = NULL;
 }
 
@@ -65,11 +64,6 @@ struct stasis_topic *ast_parking_topic(void)
        return parking_topic;
 }
 
-struct stasis_message_type *ast_parked_call_type(void)
-{
-       return parked_call_type;
-}
-
 /*! \brief Destructor for parked_call_payload objects */
 static void parked_call_payload_destructor(void *obj)
 {
index 65e9f91..8327384 100644 (file)
@@ -36,29 +36,24 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/astobj2.h"
 #include "asterisk/stasis_channels.h"
 
-#define NUM_MULTI_CHANNEL_BLOB_BUCKETS 7
+/*** DOCUMENTATION
+       <managerEvent language="en_US" name="VarSet">
+               <managerEventInstance class="EVENT_FLAG_DIALPLAN">
+                       <synopsis>Raised when a variable is set to a particular value.</synopsis>
+                       <syntax>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
+                               <parameter name="Variable">
+                                       <para>The variable being set.</para>
+                               </parameter>
+                               <parameter name="Value">
+                                       <para>The new value of the variable.</para>
+                               </parameter>
+                       </syntax>
+               </managerEventInstance>
+       </managerEvent>
+***/
 
-/*!
- * @{ \brief Define channel message types.
- */
-STASIS_MESSAGE_TYPE_DEFN(ast_channel_snapshot_type);
-STASIS_MESSAGE_TYPE_DEFN(ast_channel_dial_type);
-STASIS_MESSAGE_TYPE_DEFN(ast_channel_varset_type);
-STASIS_MESSAGE_TYPE_DEFN(ast_channel_user_event_type);
-STASIS_MESSAGE_TYPE_DEFN(ast_channel_hangup_request_type);
-STASIS_MESSAGE_TYPE_DEFN(ast_channel_dtmf_begin_type);
-STASIS_MESSAGE_TYPE_DEFN(ast_channel_dtmf_end_type);
-STASIS_MESSAGE_TYPE_DEFN(ast_channel_hold_type);
-STASIS_MESSAGE_TYPE_DEFN(ast_channel_unhold_type);
-STASIS_MESSAGE_TYPE_DEFN(ast_channel_chanspy_start_type);
-STASIS_MESSAGE_TYPE_DEFN(ast_channel_chanspy_stop_type);
-STASIS_MESSAGE_TYPE_DEFN(ast_channel_fax_type);
-STASIS_MESSAGE_TYPE_DEFN(ast_channel_hangup_handler_type);
-STASIS_MESSAGE_TYPE_DEFN(ast_channel_moh_start_type);
-STASIS_MESSAGE_TYPE_DEFN(ast_channel_moh_stop_type);
-STASIS_MESSAGE_TYPE_DEFN(ast_channel_monitor_start_type);
-STASIS_MESSAGE_TYPE_DEFN(ast_channel_monitor_stop_type);
-/*! @} */
+#define NUM_MULTI_CHANNEL_BLOB_BUCKETS 7
 
 /*! \brief Topic for all channels */
 struct stasis_topic *channel_topic_all;
@@ -540,6 +535,36 @@ void ast_channel_publish_varset(struct ast_channel *chan, const char *name, cons
        ast_channel_publish_blob(chan, ast_channel_varset_type(), blob);
 }
 
+static struct ast_manager_event_blob *varset_to_ami(struct stasis_message *msg)
+{
+       RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free);
+       struct ast_channel_blob *obj = stasis_message_data(msg);
+       const char *variable =
+               ast_json_string_get(ast_json_object_get(obj->blob, "variable"));
+       const char *value =
+               ast_json_string_get(ast_json_object_get(obj->blob, "value"));
+
+       if (obj->snapshot) {
+               channel_event_string =
+                       ast_manager_build_channel_state_string(obj->snapshot);
+       } else {
+               channel_event_string = ast_str_create(35);
+               ast_str_set(&channel_event_string, 0,
+                           "Channel: none\r\n"
+                           "Uniqueid: none\r\n");
+       }
+
+       if (!channel_event_string) {
+               return NULL;
+       }
+
+       return ast_manager_event_blob_create(EVENT_FLAG_DIALPLAN, "VarSet",
+               "%s"
+               "Variable: %s\r\n"
+               "Value: %s\r\n",
+               ast_str_buffer(channel_event_string), variable, value);
+}
+
 void ast_publish_channel_state(struct ast_channel *chan)
 {
        RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
@@ -626,6 +651,31 @@ int ast_channel_snapshot_caller_id_equal(
                strcmp(old_snapshot->caller_name, new_snapshot->caller_name) == 0;
 }
 
+/*!
+ * @{ \brief Define channel message types.
+ */
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_snapshot_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_dial_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_varset_type,
+       .to_ami = varset_to_ami,
+       );
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_user_event_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_hangup_request_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_dtmf_begin_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_dtmf_end_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_hold_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_unhold_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_chanspy_start_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_chanspy_stop_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_fax_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_hangup_handler_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_moh_start_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_moh_stop_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_monitor_start_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_monitor_stop_type);
+
+/*! @} */
+
 static void stasis_channels_cleanup(void)
 {
        channel_topic_all_cached = stasis_caching_unsubscribe_and_join(channel_topic_all_cached);
index 8d2373f..b25d1f2 100644 (file)
@@ -37,9 +37,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
 /*! \internal */
 struct stasis_message_type {
+       struct stasis_message_vtable *vtable;
        char *name;
 };
 
+static struct stasis_message_vtable null_vtable = {};
+
 static void message_type_dtor(void *obj)
 {
        struct stasis_message_type *type = obj;
@@ -47,7 +50,8 @@ static void message_type_dtor(void *obj)
        type->name = NULL;
 }
 
-struct stasis_message_type *stasis_message_type_create(const char *name)
+struct stasis_message_type *stasis_message_type_create(const char *name,
+       struct stasis_message_vtable *vtable)
 {
        RAII_VAR(struct stasis_message_type *, type, NULL, ao2_cleanup);
 
@@ -55,11 +59,16 @@ struct stasis_message_type *stasis_message_type_create(const char *name)
        if (!type) {
                return NULL;
        }
+       if (!vtable) {
+               /* Null object pattern, FTW! */
+               vtable = &null_vtable;
+       }
 
        type->name = ast_strdup(name);
        if (!type->name) {
                return NULL;
        }
+       type->vtable = vtable;
 
        ao2_ref(type, +1);
        return type;
@@ -133,3 +142,26 @@ const struct timeval *stasis_message_timestamp(const struct stasis_message *msg)
        }
        return &msg->timestamp;
 }
+
+#define INVOKE_VIRTUAL(fn, ...)                                \
+       ({                                              \
+               if (msg == NULL) {                      \
+                       return NULL;                    \
+               }                                       \
+               ast_assert(msg->type != NULL);          \
+               ast_assert(msg->type->vtable != NULL);  \
+               if (msg->type->vtable->fn == NULL) {    \
+                       return NULL;                    \
+               }                                       \
+               msg->type->vtable->fn(__VA_ARGS__);     \
+       })
+
+struct ast_manager_event_blob *stasis_message_to_ami(struct stasis_message *msg)
+{
+       return INVOKE_VIRTUAL(to_ami, msg);
+}
+
+struct ast_json *stasis_message_to_json(struct stasis_message *msg)
+{
+       return INVOKE_VIRTUAL(to_json, msg);
+}
index 915226d..0dc9182 100644 (file)
@@ -41,6 +41,34 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
 static const char *test_category = "/stasis/core/";
 
+static struct ast_json *fake_json(struct stasis_message *message)
+{
+       const char *text = stasis_message_data(message);
+
+       return ast_json_string_create(text);
+}
+
+static struct ast_manager_event_blob *fake_ami(struct stasis_message *message)
+{
+       RAII_VAR(struct ast_manager_event_blob *, res, NULL, ao2_cleanup);
+       const char *text = stasis_message_data(message);
+
+       res = ast_manager_event_blob_create(EVENT_FLAG_TEST, "FakeMI",
+               "Message: %s", text);
+
+       if (res == NULL) {
+               return NULL;
+       }
+
+       ao2_ref(res, +1);
+       return res;
+}
+
+static struct stasis_message_vtable fake_vtable = {
+       .to_json = fake_json,
+       .to_ami = fake_ami
+};
+
 AST_TEST_DEFINE(message_type)
 {
        RAII_VAR(struct stasis_message_type *, uut, NULL, ao2_cleanup);
@@ -56,8 +84,8 @@ AST_TEST_DEFINE(message_type)
                break;
        }
 
-       ast_test_validate(test, NULL == stasis_message_type_create(NULL));
-       uut = stasis_message_type_create("SomeMessage");
+       ast_test_validate(test, NULL == stasis_message_type_create(NULL, NULL));
+       uut = stasis_message_type_create("SomeMessage", NULL);
        ast_test_validate(test, 0 == strcmp(stasis_message_type_name(uut), "SomeMessage"));
 
        return AST_TEST_PASS;
@@ -84,7 +112,7 @@ AST_TEST_DEFINE(message)
        }
 
 
-       type = stasis_message_type_create("SomeMessage");
+       type = stasis_message_type_create("SomeMessage", NULL);
 
        ast_test_validate(test, NULL == stasis_message_create(NULL, NULL));
        ast_test_validate(test, NULL == stasis_message_create(type, NULL));
@@ -332,7 +360,7 @@ AST_TEST_DEFINE(publish)
 
        test_data = ao2_alloc(1, NULL);
        ast_test_validate(test, NULL != test_data);
-       test_message_type = stasis_message_type_create("TestMessage");
+       test_message_type = stasis_message_type_create("TestMessage", NULL);
        test_message = stasis_message_create(test_message_type, test_data);
 
        stasis_publish(topic, test_message);
@@ -380,7 +408,7 @@ AST_TEST_DEFINE(unsubscribe_stops_messages)
 
        test_data = ao2_alloc(1, NULL);
        ast_test_validate(test, NULL != test_data);
-       test_message_type = stasis_message_type_create("TestMessage");
+       test_message_type = stasis_message_type_create("TestMessage", NULL);
        test_message = stasis_message_create(test_message_type, test_data);
 
        stasis_publish(topic, test_message);
@@ -444,7 +472,7 @@ AST_TEST_DEFINE(forward)
 
        test_data = ao2_alloc(1, NULL);
        ast_test_validate(test, NULL != test_data);
-       test_message_type = stasis_message_type_create("TestMessage");
+       test_message_type = stasis_message_type_create("TestMessage", NULL);
        test_message = stasis_message_create(test_message_type, test_data);
 
        stasis_publish(topic, test_message);
@@ -494,7 +522,7 @@ AST_TEST_DEFINE(interleaving)
                break;
        }
 
-       test_message_type = stasis_message_type_create("test");
+       test_message_type = stasis_message_type_create("test", NULL);
        ast_test_validate(test, NULL != test_message_type);
 
        test_data = ao2_alloc(1, NULL);
@@ -604,7 +632,7 @@ AST_TEST_DEFINE(cache_passthrough)
                break;
        }
 
-       non_cache_type = stasis_message_type_create("NonCacheable");
+       non_cache_type = stasis_message_type_create("NonCacheable", NULL);
        ast_test_validate(test, NULL != non_cache_type);
        topic = stasis_topic_create("SomeTopic");
        ast_test_validate(test, NULL != topic);
@@ -657,7 +685,7 @@ AST_TEST_DEFINE(cache)
                break;
        }
 
-       cache_type = stasis_message_type_create("Cacheable");
+       cache_type = stasis_message_type_create("Cacheable", NULL);
        ast_test_validate(test, NULL != cache_type);
        topic = stasis_topic_create("SomeTopic");
        ast_test_validate(test, NULL != topic);
@@ -759,7 +787,7 @@ AST_TEST_DEFINE(cache_dump)
                break;
        }
 
-       cache_type = stasis_message_type_create("Cacheable");
+       cache_type = stasis_message_type_create("Cacheable", NULL);
        ast_test_validate(test, NULL != cache_type);
        topic = stasis_topic_create("SomeTopic");
        ast_test_validate(test, NULL != topic);
@@ -866,7 +894,7 @@ AST_TEST_DEFINE(route_conflicts)
        consumer2 = consumer_create(1);
        ast_test_validate(test, NULL != consumer2);
 
-       test_message_type = stasis_message_type_create("TestMessage");
+       test_message_type = stasis_message_type_create("TestMessage", NULL);
        ast_test_validate(test, NULL != test_message_type);
 
        uut = stasis_message_router_create(topic);
@@ -920,11 +948,11 @@ AST_TEST_DEFINE(router)
        consumer3 = consumer_create(1);
        ast_test_validate(test, NULL != consumer3);
 
-       test_message_type1 = stasis_message_type_create("TestMessage1");
+       test_message_type1 = stasis_message_type_create("TestMessage1", NULL);
        ast_test_validate(test, NULL != test_message_type1);
-       test_message_type2 = stasis_message_type_create("TestMessage2");
+       test_message_type2 = stasis_message_type_create("TestMessage2", NULL);
        ast_test_validate(test, NULL != test_message_type2);
-       test_message_type3 = stasis_message_type_create("TestMessage3");
+       test_message_type3 = stasis_message_type_create("TestMessage3", NULL);
        ast_test_validate(test, NULL != test_message_type3);
 
        uut = stasis_message_router_create(topic);
@@ -978,6 +1006,147 @@ AST_TEST_DEFINE(router)
        return AST_TEST_PASS;
 }
 
+AST_TEST_DEFINE(no_to_json)
+{
+       RAII_VAR(struct stasis_message_type *, type, NULL, ao2_cleanup);
+       RAII_VAR(struct stasis_message *, uut, NULL, ao2_cleanup);
+       RAII_VAR(char *, data, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_json *, actual, NULL, ast_json_unref);
+       char *expected = "SomeData";
+
+       switch (cmd) {
+       case TEST_INIT:
+               info->name = __func__;
+               info->category = test_category;
+               info->summary = "Test message to_json function";
+               info->description = "Test message to_json function";
+               return AST_TEST_NOT_RUN;
+       case TEST_EXECUTE:
+               break;
+       }
+
+       /* Test NULL */
+       actual = stasis_message_to_json(NULL);
+       ast_test_validate(test, NULL == actual);
+
+       /* Test message with NULL to_json function */
+       type = stasis_message_type_create("SomeMessage", NULL);
+
+       data = ao2_alloc(strlen(expected) + 1, NULL);
+       strcpy(data, expected);
+       uut = stasis_message_create(type, data);
+       ast_test_validate(test, NULL != uut);
+
+       actual = stasis_message_to_json(uut);
+       ast_test_validate(test, NULL == actual);
+
+       return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(to_json)
+{
+       RAII_VAR(struct stasis_message_type *, type, NULL, ao2_cleanup);
+       RAII_VAR(struct stasis_message *, uut, NULL, ao2_cleanup);
+       RAII_VAR(char *, data, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_json *, actual, NULL, ast_json_unref);
+       const char *expected_text = "SomeData";
+       RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref);
+
+       switch (cmd) {
+       case TEST_INIT:
+               info->name = __func__;
+               info->category = test_category;
+               info->summary = "Test message to_json function when NULL";
+               info->description = "Test message to_json function when NULL";
+               return AST_TEST_NOT_RUN;
+       case TEST_EXECUTE:
+               break;
+       }
+
+       type = stasis_message_type_create("SomeMessage", &fake_vtable);
+
+       data = ao2_alloc(strlen(expected_text) + 1, NULL);
+       strcpy(data, expected_text);
+       uut = stasis_message_create(type, data);
+       ast_test_validate(test, NULL != uut);
+
+       expected = ast_json_string_create(expected_text);
+       actual = stasis_message_to_json(uut);
+       ast_test_validate(test, ast_json_equal(expected, actual));
+
+       return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(no_to_ami)
+{
+       RAII_VAR(struct stasis_message_type *, type, NULL, ao2_cleanup);
+       RAII_VAR(struct stasis_message *, uut, NULL, ao2_cleanup);
+       RAII_VAR(char *, data, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_manager_event_blob *, actual, NULL, ao2_cleanup);
+       char *expected = "SomeData";
+
+       switch (cmd) {
+       case TEST_INIT:
+               info->name = __func__;
+               info->category = test_category;
+               info->summary = "Test message to_ami function when NULL";
+               info->description = "Test message to_ami function when NULL";
+               return AST_TEST_NOT_RUN;
+       case TEST_EXECUTE:
+               break;
+       }
+
+       /* Test NULL */
+       actual = stasis_message_to_ami(NULL);
+       ast_test_validate(test, NULL == actual);
+
+       /* Test message with NULL to_ami function */
+       type = stasis_message_type_create("SomeMessage", NULL);
+
+       data = ao2_alloc(strlen(expected) + 1, NULL);
+       strcpy(data, expected);
+       uut = stasis_message_create(type, data);
+       ast_test_validate(test, NULL != uut);
+
+       actual = stasis_message_to_ami(uut);
+       ast_test_validate(test, NULL == actual);
+
+       return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(to_ami)
+{
+       RAII_VAR(struct stasis_message_type *, type, NULL, ao2_cleanup);
+       RAII_VAR(struct stasis_message *, uut, NULL, ao2_cleanup);
+       RAII_VAR(char *, data, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_manager_event_blob *, actual, NULL, ao2_cleanup);
+       const char *expected_text = "SomeData";
+       const char *expected = "Message: SomeData";
+
+       switch (cmd) {
+       case TEST_INIT:
+               info->name = __func__;
+               info->category = test_category;
+               info->summary = "Test message to_ami function";
+               info->description = "Test message to_ami function";
+               return AST_TEST_NOT_RUN;
+       case TEST_EXECUTE:
+               break;
+       }
+
+       type = stasis_message_type_create("SomeMessage", &fake_vtable);
+
+       data = ao2_alloc(strlen(expected_text) + 1, NULL);
+       strcpy(data, expected_text);
+       uut = stasis_message_create(type, data);
+       ast_test_validate(test, NULL != uut);
+
+       actual = stasis_message_to_ami(uut);
+       ast_test_validate(test, strcmp(expected, actual->extra_fields) == 0);
+
+       return AST_TEST_PASS;
+}
+
 static int unload_module(void)
 {
        AST_TEST_UNREGISTER(message_type);
@@ -992,6 +1161,10 @@ static int unload_module(void)
        AST_TEST_UNREGISTER(route_conflicts);
        AST_TEST_UNREGISTER(router);
        AST_TEST_UNREGISTER(interleaving);
+       AST_TEST_UNREGISTER(no_to_json);
+       AST_TEST_UNREGISTER(to_json);
+       AST_TEST_UNREGISTER(no_to_ami);
+       AST_TEST_UNREGISTER(to_ami);
        return 0;
 }
 
@@ -1009,6 +1182,10 @@ static int load_module(void)
        AST_TEST_REGISTER(route_conflicts);
        AST_TEST_REGISTER(router);
        AST_TEST_REGISTER(interleaving);
+       AST_TEST_REGISTER(no_to_json);
+       AST_TEST_REGISTER(to_json);
+       AST_TEST_REGISTER(no_to_ami);
+       AST_TEST_REGISTER(to_ami);
        return AST_MODULE_LOAD_SUCCESS;
 }
 
index 512df43..214d773 100644 (file)
@@ -71,7 +71,7 @@ AST_TEST_DEFINE(channel_blob_create)
                break;
        }
 
-       type = stasis_message_type_create("test-type");
+       type = stasis_message_type_create("test-type", NULL);
        chan = ast_channel_alloc(0, AST_STATE_DOWN, "100", "Alice", "100", "100", "default", NULL, 0, "TEST/Alice");
        json = ast_json_pack("{s: s}",
                     "foo", "bar");
@@ -123,7 +123,7 @@ AST_TEST_DEFINE(null_blob)
                break;
        }
 
-       type = stasis_message_type_create("test-type");
+       type = stasis_message_type_create("test-type", NULL);
        chan = ast_channel_alloc(0, AST_STATE_DOWN, "100", "Alice", "100", "100", "default", NULL, 0, "TEST/Alice");
        json = ast_json_pack("{s: s}",
                     "foo", "bar");