]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
FS-11676 add JSON support to mod_lua
authorSeven Du <seven@signalwire.com>
Sat, 15 Dec 2018 11:03:44 +0000 (19:03 +0800)
committerAndrey Volk <andywolk@gmail.com>
Wed, 17 Jul 2019 15:46:09 +0000 (19:46 +0400)
configure.ac
src/mod/languages/mod_lua/Makefile.am
src/mod/languages/mod_lua/freeswitch.i
src/mod/languages/mod_lua/freeswitch_lua.cpp
src/mod/languages/mod_lua/freeswitch_lua.h
src/mod/languages/mod_lua/test/Makefile.am [new file with mode: 0644]
src/mod/languages/mod_lua/test/conf/freeswitch.xml [new file with mode: 0644]
src/mod/languages/mod_lua/test/test_json.lua [new file with mode: 0644]
src/mod/languages/mod_lua/test/test_mod_lua.c [new file with mode: 0644]

index 0d7607f403beee815bdd6be69ca2f1466adb7ce5..efb4da920a141e66199fb55e05435d53aa7cb1e6 100644 (file)
@@ -1978,6 +1978,7 @@ AC_CONFIG_FILES([Makefile
                src/mod/formats/mod_portaudio_stream/Makefile
                src/mod/languages/mod_java/Makefile
                src/mod/languages/mod_lua/Makefile
+               src/mod/languages/mod_lua/test/Makefile
                src/mod/languages/mod_managed/Makefile
                src/mod/languages/mod_perl/Makefile
                src/mod/languages/mod_python/Makefile
index 86065a5bc50a407c3606f6d1346698061ff3cc39..5b8e1ba37499f5bebb58dde93afa2ed0d9bd54f1 100644 (file)
@@ -23,3 +23,4 @@ mod_lua_wrap.cpp: mod_lua_extra.c
        echo "#include \"mod_lua_extra.c\"" >> mod_lua_wrap.cpp
        patch -s -p0 -i hack.diff
 
+SUBDIRS=. test
index 8f114fcb8875730b347ce15b209e9449d3dc74de..3485c8168cbcab9bb2f5d29383206e28ba5efd56 100644 (file)
 %}
 
 
+%typemap(in, checkfn="lua_istable") SWIGLUA_TABLE {
+  $1.L = L;
+  $1.idx = $input;
+}
+
+%typemap(typecheck) SWIGLUA_TABLE {
+  $1 = lua_istable(L, $input);
+}
 
+%typemap(out) cJSON * {
+  SWIG_arg += LUA::JSON::cJSON2LuaTable(L, result);
+  cJSON_Delete(result);
+}
 
 /* Lua function typemap */
 %typemap(in, checkfn = "lua_isfunction") SWIGLUA_FN {
 %newobject API::execute;
 %newobject API::executeString;
 %newobject CoreSession::playAndDetectSpeech;
+%newobject JSON;
+%newobject JSON::encode;
+%newobject JSON::decode;
+%newobject JSON::execute;
+%newobject JSON::execute2;
 
 %include "typemaps.i"
 %apply int *OUTPUT { int *len };
@@ -107,5 +124,20 @@ class Dbh {
     int load_extension(const char *extension);
 };
 
+class JSON {
+  private:
+  public:
+    JSON();
+    ~JSON();
+    cJSON *decode(const char *str);
+    char *encode(SWIGLUA_TABLE lua_table);
+    cJSON *execute(const char *);
+    cJSON *execute(SWIGLUA_TABLE table);
+    char *execute2(const char *);
+    char *execute2(SWIGLUA_TABLE table);
+    void encode_empty_table_as_object(bool flag);
+    void return_unformatted_json(bool flag);
+};
+
 }
 
index 2225cae07c121c4592b5a542322e4fb865bc444c..134906357e4fc0db406b31262bae6368c8267f76 100644 (file)
@@ -506,3 +506,234 @@ int Dbh::load_extension(const char *extension)
   switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "DBH NOT Connected.\n");
   return 0;
 }
+
+JSON::JSON()
+{
+       _encode_empty_table_as_object = true;
+}
+
+JSON::~JSON()
+{
+}
+
+void JSON::encode_empty_table_as_object(bool flag)
+{
+       _encode_empty_table_as_object = flag;
+}
+
+void JSON::return_unformatted_json(bool flag)
+{
+       _return_unformatted_json = flag;
+}
+
+cJSON *JSON::decode(const char *str)
+{
+       cJSON *json = cJSON_Parse(str);
+       return json;
+}
+
+#define ADDITEM(json, k, v) do { \
+       if (return_array > 0) { cJSON_AddItemToArray(json, v);} else { cJSON_AddItemToObject(json, k, v); } \
+} while (0)
+
+void JSON::LuaTable2cJSON(lua_State *L, int index, cJSON **json)
+{
+       int return_array = -1;
+
+    // Push another reference to the table on top of the stack (so we know
+    // where it is, and this function can work for negative, positive and
+    // pseudo indices
+    lua_pushvalue(L, index);
+    // stack now contains: -1 => table
+    lua_pushnil(L);
+    // stack now contains: -1 => nil; -2 => table
+    while (lua_next(L, -2)) {
+        // stack now contains: -1 => value; -2 => key; -3 => table
+        // copy the key so that lua_tostring does not modify the original
+        lua_pushvalue(L, -2);
+        // stack now contains: -1 => key; -2 => value; -3 => key; -4 => table
+
+        const char *key = lua_tostring(L, -1);
+        // switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "key: %s\n", key);
+
+               if (return_array < 0) {
+                       if (lua_isnumber(L, -1) && lua_tonumber(L, -1) == 1) {
+                               return_array = 1;
+                               *json = cJSON_CreateArray();
+                       } else {
+                               return_array = 0;
+                               *json = cJSON_CreateObject();
+                       }
+               }
+
+               switch_assert(*json);
+
+               if (lua_isnumber(L, -2)) {
+                       ADDITEM(*json, key, cJSON_CreateNumber(lua_tonumber(L, -2)));
+               } else if (lua_isstring(L, -2)) {
+                       ADDITEM(*json, key, cJSON_CreateString(lua_tostring(L, -2)));
+               } else if (lua_isboolean(L, -2)) {
+                       ADDITEM(*json, key, cJSON_CreateBool(lua_toboolean(L, -2)));
+               } else if (lua_isnil(L, -2)) {
+                       ADDITEM(*json, key, cJSON_CreateNull());
+               } else if (lua_isnone(L, -2)) {
+                       // ADDITEM(*json, key, cJSON_CreateNone());
+               } else if (lua_istable(L, -2)) {
+                       cJSON *child = NULL;
+                       LuaTable2cJSON(L, -2, &child);
+                       if (child) {
+                               ADDITEM(*json, key, child);
+                       } else { // empty table?
+                               ADDITEM(*json, key, _encode_empty_table_as_object ? cJSON_CreateObject() : cJSON_CreateArray());
+                       }
+               }
+
+        // pop value + copy of key, leaving original key
+        lua_pop(L, 2);
+        // stack now contains: -1 => key; -2 => table
+    }
+
+    // stack now contains: -1 => table (when lua_next returns 0 it pops the key
+    // but does not push anything.)
+    // Pop table
+    lua_pop(L, 1);
+    // Stack is now the same as it was on entry to this function
+}
+
+char *JSON::encode(SWIGLUA_TABLE lua_table)
+{
+       lua_State *L = lua_table.L;
+       cJSON *json = NULL;
+
+       luaL_checktype(L, lua_table.idx, LUA_TTABLE);
+       LuaTable2cJSON(L, -1, &json);
+
+       if (!json) {
+               json = _encode_empty_table_as_object ? cJSON_CreateObject() : cJSON_CreateArray();
+       }
+
+       char *s = _return_unformatted_json ? cJSON_PrintUnformatted(json) : cJSON_Print(json);
+       cJSON_Delete(json);
+       return s;
+}
+
+int JSON::cJSON2LuaTable(lua_State *L, cJSON *json) {
+       cJSON *current = NULL;
+
+       if (!json) return 0;
+
+       lua_newtable(L);
+
+       if (json->type == cJSON_Object) {
+               for (current = json->child; current; current = current->next) {
+                       // printf("type: %d %s\n", current->type, current->string);
+                       switch (current->type) {
+                               case cJSON_String:
+                                       lua_pushstring(L, current->valuestring);
+                                       lua_setfield(L, -2, current->string);
+                                       break;
+                               case cJSON_Number:
+                                       lua_pushnumber(L, current->valuedouble);
+                                       lua_setfield(L, -2, current->string);
+                                       break;
+                               case cJSON_True:
+                                       lua_pushboolean(L, 1);
+                                       lua_setfield(L, -2, current->string);
+                                       break;
+                               case cJSON_False:
+                                       lua_pushboolean(L, 0);
+                                       lua_setfield(L, -2, current->string);
+                                       break;
+                               case cJSON_Object:
+                                       JSON::cJSON2LuaTable(L, current);
+                                       lua_setfield(L, -2, current->string);
+                                       break;
+                               case cJSON_Array:
+                                       JSON::cJSON2LuaTable(L, current);
+                                       lua_setfield(L, -2, current->string);
+                                       break;
+                               default:
+                                       break;
+                       }
+               }
+       } else if (json->type == cJSON_Array) {
+               int i = 1;
+
+               for (current = json->child; current; current = current->next) {
+                       // printf("array type: %d %s\n", current->type, current->valuestring);
+                       switch (current->type) {
+                               case cJSON_String:
+                                       lua_pushinteger(L, i++);
+                                       lua_pushstring(L, current->valuestring);
+                                       lua_settable(L, -3);
+                                       break;
+                               case cJSON_Number:
+                                       lua_pushinteger(L, i++);
+                                       lua_pushnumber(L, current->valuedouble);
+                                       lua_settable(L, -3);
+                                       break;
+                               case cJSON_True:
+                                       lua_pushinteger(L, i++);
+                                       lua_pushboolean(L, 1);
+                                       lua_settable(L, -3);
+                                       break;
+                               case cJSON_False:
+                                       lua_pushinteger(L, i++);
+                                       lua_pushboolean(L, 0);
+                                       lua_settable(L, -3);
+                                       break;
+                               case cJSON_Object:
+                                       lua_pushinteger(L, i++);
+                                       JSON::cJSON2LuaTable(L, current);
+                                       lua_settable(L, -3);
+                                       break;
+                               default:
+                                       break;
+                       }
+               }
+       }
+
+       return 1;
+}
+
+cJSON *JSON::execute(const char *str)
+{
+       cJSON *cmd = cJSON_Parse(str);
+       cJSON *reply = NULL;
+
+       if (cmd) {
+               switch_json_api_execute(cmd, NULL, &reply);
+       }
+
+       cJSON_Delete(cmd);
+
+       return reply;
+}
+
+cJSON *JSON::execute(SWIGLUA_TABLE table)
+{
+       lua_State *L = table.L;
+       cJSON *json = NULL;
+       cJSON *reply = NULL;
+
+       luaL_checktype(L, table.idx, LUA_TTABLE);
+       LuaTable2cJSON(L, -1, &json);
+
+       switch_json_api_execute(json, NULL, &reply);
+       cJSON_Delete(json);
+       return reply;
+}
+
+char *JSON::execute2(const char *str)
+{
+       cJSON *reply = execute(str);
+
+       return _return_unformatted_json ? cJSON_PrintUnformatted(reply) : cJSON_Print(reply);
+}
+
+char *JSON::execute2(SWIGLUA_TABLE table)
+{
+       cJSON *reply = execute(table);
+
+       return _return_unformatted_json ? cJSON_PrintUnformatted(reply) : cJSON_Print(reply);
+}
index d205f6b46e9cb82bdc7c834fb4876ec1a7aef793..e7acf3c99ec0e5d7217432671444564f82669b33 100644 (file)
@@ -20,6 +20,13 @@ typedef struct{
 
 #define SWIGLUA_FN_GET(fn) {lua_pushvalue(fn.L,fn.idx);}
 
+typedef struct{
+  lua_State* L;
+  int idx;
+}SWIGLUA_TABLE;
+
+#define SWIGLUA_TABLE_GET(fn) {lua_pushvalue(fn.L,fn.idx);}
+
 
 namespace LUA {
        class Session:public CoreSession {
@@ -73,5 +80,25 @@ namespace LUA {
       void clear_error();
       int load_extension(const char *extension);
   };
+
+  class JSON {
+    private:
+                 bool _encode_empty_table_as_object;
+                 bool _return_unformatted_json;
+    public:
+      JSON();
+      ~JSON();
+      cJSON *decode(const char *);
+      char *encode(SWIGLUA_TABLE table);
+      cJSON *execute(const char *);
+      cJSON *execute(SWIGLUA_TABLE table);
+      char *execute2(const char *);
+      char *execute2(SWIGLUA_TABLE table);
+      void encode_empty_table_as_object(bool flag);
+      void return_unformatted_json(bool flag);
+      static int cJSON2LuaTable(lua_State *L, cJSON *json);
+      void LuaTable2cJSON(lua_State *L, int index, cJSON **json);
+  };
+
 }
 #endif
diff --git a/src/mod/languages/mod_lua/test/Makefile.am b/src/mod/languages/mod_lua/test/Makefile.am
new file mode 100644 (file)
index 0000000..683cb53
--- /dev/null
@@ -0,0 +1,4 @@
+bin_PROGRAMS = test_mod_lua
+AM_CFLAGS = $(SWITCH_AM_CFLAGS)
+AM_LDFLAGS = $(switch_builddir)/libfreeswitch.la -avoid-version -no-undefined $(SWITCH_AM_LDFLAGS)
+TESTS = $(bin_PROGRAMS)
diff --git a/src/mod/languages/mod_lua/test/conf/freeswitch.xml b/src/mod/languages/mod_lua/test/conf/freeswitch.xml
new file mode 100644 (file)
index 0000000..7613bea
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<document type="freeswitch/xml">
+
+  <section name="configuration" description="Various Configuration">
+    <configuration name="modules.conf" description="Modules">
+      <modules>
+        <load module="mod_console"/>
+        <load module="mod_commands"/>
+      </modules>
+    </configuration>
+
+    <configuration name="console.conf" description="Console Logger">
+      <mappings>
+        <map name="all" value="console,debug,info,notice,warning,err,crit,alert"/>
+      </mappings>
+      <settings>
+        <param name="colorize" value="true"/>
+        <param name="loglevel" value="debug"/>
+      </settings>
+    </configuration>
+
+    <configuration name="timezones.conf" description="Timezones">
+      <timezones>
+          <zone name="GMT" value="GMT0" />
+      </timezones>
+    </configuration>
+  </section>
+
+  <section name="dialplan" description="Regex/XML Dialplan">
+    <context name="default">
+      <extension name="sample">
+        <condition>
+          <action application="info"/>
+        </condition>
+      </extension>
+    </context>
+  </section>
+</document>
diff --git a/src/mod/languages/mod_lua/test/test_json.lua b/src/mod/languages/mod_lua/test/test_json.lua
new file mode 100644 (file)
index 0000000..b8f358f
--- /dev/null
@@ -0,0 +1,112 @@
+function serialize(o)
+       s = ""
+
+       if type(o) == "number" then
+               s = s .. o
+       elseif type(o) == "string" then
+               s = s .. string.format("%q", o)
+       elseif type(o) == "table" then
+               s = s .. "{\n"
+               for k, v in pairs(o) do
+                       s = s .. '  ' .. k .. ' = '
+                       s = s .. serialize(v)
+                       s = s .. ",\n"
+               end
+               s = s .. "}"
+       elseif type(o) == "boolean" then
+               if o then
+                       s = s .. "true"
+               else
+                       s = s .. "false"
+               end
+       else
+               s = s .. " [" .. type(o) .. "]"
+       end
+
+       return s
+end
+
+json = freeswitch.JSON()
+
+
+str = '{"a": "中文"}'
+x = json:decode(str)
+assert(x.a == '中文')
+
+str = '{"a": "1", "b": 2, "c": true, "d": false, "e": [], "f": {}, "g": [1, 2, "3"], "h": {"a": 1, "b": 2}}'
+x = json:decode(str)
+
+freeswitch.consoleLog("INFO", serialize(x) .. "\n")
+freeswitch.consoleLog("INFO", json:encode(x) .. '\n')
+
+assert(x.a == "1")
+assert(x.b == 2)
+
+x = json:decode('["a", "b", true, false, null]')
+freeswitch.consoleLog("INFO", serialize(x) .. "\n")
+
+assert(x[1] == "a")
+
+x = json:decode('[]')
+assert(x)
+x = json:decode('{}')
+assert(x)
+x = json:decode('blah')
+assert(x == nil)
+
+s = json:encode({hello = "blah", seven="7", aa = {bb = "cc", ee="ff", more = {deep = "yes"}}, last="last", empty={}})
+freeswitch.consoleLog("INFO", s .. "\n")
+
+s = json:encode({"a", "b", "c"})
+freeswitch.consoleLog("INFO", s .. "\n")
+
+s = json:encode({a = 1, b = 2, c = 3, d=true, e=false, f=nil})
+freeswitch.consoleLog("INFO", s .. "\n")
+
+json:return_unformatted_json(true);
+s = json:encode({})
+freeswitch.consoleLog("INFO", s .. "\n")
+assert(s == "{}")
+
+json:encode_empty_table_as_object(false);
+s = json:encode({})
+freeswitch.consoleLog("INFO", s .. "\n")
+assert(s == "[]")
+
+s = json:encode({[1] = "a"})
+freeswitch.consoleLog("INFO", s .. "\n")
+assert(s == '["a"]')
+
+s = json:encode({"a", "b", "c"})
+freeswitch.consoleLog("INFO", s .. "\n")
+assert(s == '["a","b","c"]')
+
+-- sparse
+s = json:encode({[3] = "c"})
+freeswitch.consoleLog("INFO", s .. "\n")
+assert(s == '{"3":"c"}')
+
+s = json:encode({{name = "seven"}, {name="nine"}})
+freeswitch.consoleLog("INFO", s .. "\n")
+assert(s == '[{"name":"seven"},{"name":"nine"}]')
+
+s = json:encode({{name = "中文"}, {["中文"]="也行"}})
+freeswitch.consoleLog("INFO", s .. "\n")
+assert(s == '[{"name":"中文"},{"中文":"也行"}]')
+
+json:encode_empty_table_as_object(true);
+cmd = {command="status", data={}}
+ret = json:execute(cmd)
+freeswitch.consoleLog("INFO", serialize(ret) .. "\n")
+
+ret = json:execute(json:encode(cmd))
+freeswitch.consoleLog("INFO", serialize(ret) .. "\n")
+
+ret = json:execute2(cmd)
+freeswitch.consoleLog("INFO", ret .. "\n")
+
+ret = json:execute2(json:encode(cmd))
+freeswitch.consoleLog("INFO", ret .. "\n")
+
+-- assert(false)
+stream:write("+OK")
diff --git a/src/mod/languages/mod_lua/test/test_mod_lua.c b/src/mod/languages/mod_lua/test/test_mod_lua.c
new file mode 100644 (file)
index 0000000..ed14973
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
+ * Copyright (C) 2005-2018, Anthony Minessale II <anthm@freeswitch.org>
+ *
+ * Version: MPL 1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
+ *
+ * The Initial Developer of the Original Code is
+ * Seven Du <dujinfang@gmail.com>
+ * Portions created by the Initial Developer are Copyright (C)
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ *
+ * test_mod_lua -- mod_lua test
+ *
+ */
+
+#include <test/switch_test.h>
+
+FST_CORE_BEGIN("conf")
+{
+       FST_MODULE_BEGIN(mod_lua, mod_lua_test)
+       {
+               FST_SETUP_BEGIN()
+               {
+                       fst_requires_module("mod_lua");
+               }
+               FST_SETUP_END()
+
+               FST_TEST_BEGIN(json_test)
+               {
+                       switch_stream_handle_t stream = { 0 };
+
+                       SWITCH_STANDARD_STREAM(stream);
+
+                       switch_api_execute("lua", "test_json.lua", NULL, &stream);
+
+                       if (stream.data) {
+                               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "LUA DATA: %s\n", stream.data);
+                               fst_check(strstr(stream.data, "+OK") == stream.data);
+                               free(stream.data);
+                       }
+               }
+               FST_TEST_END()
+
+               FST_TEARDOWN_BEGIN()
+               {
+               }
+               FST_TEARDOWN_END()
+       }
+       FST_MODULE_END()
+}
+FST_CORE_END()