diff --git a/configure.ac b/configure.ac
index 0d7607f403..efb4da920a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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
diff --git a/src/mod/languages/mod_lua/Makefile.am b/src/mod/languages/mod_lua/Makefile.am
index 86065a5bc5..5b8e1ba374 100644
--- a/src/mod/languages/mod_lua/Makefile.am
+++ b/src/mod/languages/mod_lua/Makefile.am
@@ -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
diff --git a/src/mod/languages/mod_lua/freeswitch.i b/src/mod/languages/mod_lua/freeswitch.i
index 8f114fcb88..3485c8168c 100644
--- a/src/mod/languages/mod_lua/freeswitch.i
+++ b/src/mod/languages/mod_lua/freeswitch.i
@@ -19,7 +19,19 @@
%}
+%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 {
@@ -43,6 +55,11 @@
%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);
+};
+
}
diff --git a/src/mod/languages/mod_lua/freeswitch_lua.cpp b/src/mod/languages/mod_lua/freeswitch_lua.cpp
index 2225cae07c..134906357e 100644
--- a/src/mod/languages/mod_lua/freeswitch_lua.cpp
+++ b/src/mod/languages/mod_lua/freeswitch_lua.cpp
@@ -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);
+}
diff --git a/src/mod/languages/mod_lua/freeswitch_lua.h b/src/mod/languages/mod_lua/freeswitch_lua.h
index d205f6b46e..e7acf3c99e 100644
--- a/src/mod/languages/mod_lua/freeswitch_lua.h
+++ b/src/mod/languages/mod_lua/freeswitch_lua.h
@@ -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
index 0000000000..683cb53966
--- /dev/null
+++ b/src/mod/languages/mod_lua/test/Makefile.am
@@ -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
index 0000000000..7613bea234
--- /dev/null
+++ b/src/mod/languages/mod_lua/test/conf/freeswitch.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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
index 0000000000..b8f358f816
--- /dev/null
+++ b/src/mod/languages/mod_lua/test/test_json.lua
@@ -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
index 0000000000..ed149731e2
--- /dev/null
+++ b/src/mod/languages/mod_lua/test/test_mod_lua.c
@@ -0,0 +1,66 @@
+/*
+ * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
+ * Copyright (C) 2005-2018, Anthony Minessale II
+ *
+ * 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
+ * Portions created by the Initial Developer are Copyright (C)
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ *
+ * test_mod_lua -- mod_lua test
+ *
+ */
+
+#include
+
+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()