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()