From 37c35677e07c4e6e0bd324fe8f37ddea09c83086 Mon Sep 17 00:00:00 2001 From: mechanik20051988 <mechanik20051988@tarantool.org> Date: Tue, 16 Nov 2021 20:03:47 +0300 Subject: [PATCH] uri: implement ability to parse URIs passed in different ways Previously, URI can be passed as a string, which contains one URI or several URIs separated by commas. Now URIs can be passed in different ways: as before, as a table which contains URI and it's parameters in "param" table, as a table which contains URI strings and URI tables. Also there are different ways to specify properties for URI: in a string which contains URI, after '?' delimiter, in a table which contains URI in "params" table, in "default_params" table if it is default parameters for all URIs. For this purposes new method `parse_many` was implemented in tarantool `uri` library. Also `parse` method was updated to make possible the same as new `parse_many` method but only for single URI. ```lua uri = require('uri') -- Single URI, passed as before uri.parse_many("/tmp/unix.sock") -- Single URI, with query paramters uri.parse_many("/tmp/unix.sock?q1=v1&q2=v2") -- Several URIs with parameters in one string, separated by commas uri.parse_many("/tmp/unix.sock_1?q=v, /tmp/unix.sock_2?q=v") -- Single URI passed in table, with additional parameters, passed -- in "params" table. This parameters overwrite parameters from -- URI string (q1 = "v2" in example below). uri.parse_many({"/tmp/unix.sock?q1=v1", params = {q1 = "v2"}}) -- For parse it's also works now uri.parse({"/tmp/unix.sock?q1=v1", params = {q1 = "v2"}}) -- Several URIs passed in table with default parameters, passed -- in "default_params" table, which are used for parameters, which -- not specified for URI (q3 parameter with "v3" value corresponds -- to all URIs, and used if there is no such parameter in URI). uri.parse_many({ "/tmp/unix.sock_1?q1=v1", { uri = "/tmp/unix.sock_2", params = { q2 = "v2" } }, default_params = { q3 = "v3" } }) ``` --- extra/exports | 2 - src/CMakeLists.txt | 1 + src/lib/uri/uri.c | 55 +- src/lib/uri/uri.h | 12 + src/lua/init.c | 2 + src/lua/uri.c | 347 +++++++++++ src/lua/uri.h | 22 + src/lua/uri.lua | 41 +- .../test/static-build/exports.test.lua | 2 - test/app-tap/uri.test.lua | 549 +++++++++++++++++- 10 files changed, 998 insertions(+), 35 deletions(-) create mode 100644 src/lua/uri.c create mode 100644 src/lua/uri.h diff --git a/extra/exports b/extra/exports index 8fa79fabd4..dbb243f2ff 100644 --- a/extra/exports +++ b/extra/exports @@ -463,10 +463,8 @@ tt_uuid_from_string tt_uuid_is_equal tt_uuid_is_nil tt_uuid_to_string -uri_create uri_destroy uri_format -uri_set_create uri_set_destroy uuid_nil uuid_unpack diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3e385e7b9a..f9415290ea 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -148,6 +148,7 @@ set (server_sources lua/string.c lua/swim.c lua/decimal.c + lua/uri.c ${lua_sources} ${PROJECT_SOURCE_DIR}/third_party/lua-yaml/lyaml.cc ${PROJECT_SOURCE_DIR}/third_party/lua-yaml/b64.c diff --git a/src/lib/uri/uri.c b/src/lib/uri/uri.c index fa3cfdf755..e4ad60935d 100644 --- a/src/lib/uri/uri.c +++ b/src/lib/uri/uri.c @@ -66,11 +66,20 @@ uri_param_create(struct uri_param *param, const char *name) param->value_count = 0; } -/** - * Appends @a value to @a uri parameter with given @a name, - * creating one if it doesn't exist. - */ -static void +void +uri_remove_param(struct uri *uri, const char *name) +{ + struct uri_param *param = uri_find_param(uri, name); + if (param == NULL) + return; + int idx = param - uri->params; + uri_param_destroy(param); + for (int i = idx; i < uri->param_count - 1; i++) + uri->params[i] = uri->params[i + 1]; + uri->param_count--; +} + +void uri_add_param(struct uri *uri, const char *name, const char *value) { struct uri_param *param = uri_find_param(uri, name); @@ -170,6 +179,38 @@ uri_create(struct uri *uri, const char *str) return 0; } +static int +uri_format_param(char *str, int len, const struct uri_param *param) +{ + int total = 0; + if (param->value_count == 0) { + SNPRINT(total, snprintf, str, len, "%s", param->name); + return total; + } + for (int i = 0; i < param->value_count; i++) { + SNPRINT(total, snprintf, str, len, "%s=%s", + param->name, param->values[i]); + if (i != param->value_count - 1) + SNPRINT(total, snprintf, str, len, "&"); + } + return total; +} + +static int +uri_format_params(char *str, int len, const struct uri *uri) +{ + if (uri->param_count == 0) + return 0; + int total = 0; + SNPRINT(total, snprintf, str, len, "?"); + for (int i = 0; i < uri->param_count; i++) { + SNPRINT(total, uri_format_param, str, len, &uri->params[i]); + if (i != uri->param_count - 1) + SNPRINT(total, snprintf, str, len, "&"); + } + return total; +} + int uri_format(char *str, int len, const struct uri *uri, bool write_password) { @@ -193,8 +234,8 @@ uri_format(char *str, int len, const struct uri *uri, bool write_password) if (uri->path != NULL) { SNPRINT(total, snprintf, str, len, "%s", uri->path); } - if (uri->query != NULL) { - SNPRINT(total, snprintf, str, len, "?%s", uri->query); + if (uri->params != NULL) { + SNPRINT(total, uri_format_params, str, len, uri); } if (uri->fragment != NULL) { SNPRINT(total, snprintf, str, len, "#%s", uri->fragment); diff --git a/src/lib/uri/uri.h b/src/lib/uri/uri.h index c568d84b9f..bdbc458e58 100644 --- a/src/lib/uri/uri.h +++ b/src/lib/uri/uri.h @@ -42,6 +42,18 @@ struct uri_set { #define URI_MAXHOST NI_MAXHOST #define URI_MAXSERVICE _POSIX_PATH_MAX /* _POSIX_PATH_MAX always > NI_MAXSERV */ +/** + * Appends @a value to @a uri parameter with given @a name, + * creating one if it doesn't exist. + */ +void +uri_add_param(struct uri *uri, const char *name, const char *value); + +/** + * Remove @a uri parameter and all its values. + */ +void +uri_remove_param(struct uri *uri, const char *name); /** * Creates new @a uri structure according to passed @a str. diff --git a/src/lua/init.c b/src/lua/init.c index dbb1a0175e..a662ad0500 100644 --- a/src/lua/init.c +++ b/src/lua/init.c @@ -62,6 +62,7 @@ #include "lua/utf8.h" #include "lua/swim.h" #include "lua/decimal.h" +#include "lua/uri.h" #include "digest.h" #include "errinj.h" #include <small/ibuf.h> @@ -669,6 +670,7 @@ tarantool_lua_init(const char *tarantool_bin, int argc, char **argv) lua_call(L, 0, 0); lua_register(L, "tonumber64", lbox_tonumber64); + tarantool_lua_uri_init(L); tarantool_lua_utf8_init(L); tarantool_lua_utils_init(L); tarantool_lua_fiber_init(L); diff --git a/src/lua/uri.c b/src/lua/uri.c new file mode 100644 index 0000000000..156b847c3a --- /dev/null +++ b/src/lua/uri.c @@ -0,0 +1,347 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file. + */ +#include "lua/uri.h" + +#include "lua/utils.h" +#include "uri/uri.h" +#include "diag.h" + +/** + * Add or overwrite (depends on @a overwrite) URI parameter to @a uri. + * Parameter value is located at the top of the lua stack, parameter name is + * in the position next to it. Allowed types for URI parameter values + * are LUA_TSTRING, LUA_TNUMBER and LUA_TTABLE. URI parameter name should + * be a string. + */ +static int +uri_add_param_from_lua(struct uri *uri, struct lua_State *L, bool overwrite) +{ + if (lua_type(L, -2) != LUA_TSTRING) { + diag_set(IllegalParams, "Incorrect type for URI " + "parameter name: should be a string"); + return -1; + } + const char *name = lua_tostring(L, -2); + if (overwrite) { + uri_remove_param(uri, name); + } else if (uri_param_count(uri, name) != 0) { + return 0; + } + int rc = 0; + switch (lua_type(L, -1)) { + case LUA_TSTRING: + case LUA_TNUMBER: + uri_add_param(uri, name, lua_tostring(L, -1)); + break; + case LUA_TTABLE: + for (unsigned i = 0; i < lua_objlen(L, -1) && rc == 0; i++) { + lua_rawgeti(L, -1, i + 1); + const char *value = lua_tostring(L, -1); + if (value != NULL) { + uri_add_param(uri, name, value); + } else { + diag_set(IllegalParams, "Incorrect type for " + "URI parameter value: should " + "be string or number"); + rc = -1; + } + lua_pop(L, 1); + } + break; + default: + diag_set(IllegalParams, "Incorrect type for URI " + "parameter value: should be string, number or table"); + rc = -1; + } + return rc; +} + +/** + * Add or overwrite (depends on @a overwrite) URI parameters in @a uri. + * Table with parameters or nil value should be located at the top of the lua + * stack. + */ +static int +uri_add_params_from_lua(struct uri *uri, struct lua_State *L, bool overwrite) +{ + if (lua_type(L, -1) == LUA_TNIL) { + return 0; + } else if (lua_type(L, -1) != LUA_TTABLE) { + diag_set(IllegalParams, "Incorrect type for URI " + "parameters: should be a table"); + return -1; + } + int rc = 0; + lua_pushnil(L); + while (lua_next(L, -2) != 0 && rc == 0) { + rc = uri_add_param_from_lua(uri, L, overwrite); + assert(rc == 0 || !diag_is_empty(diag_get())); + lua_pop(L, 1); + } + return rc; +} + +/** + * Returns the type of the field, which located at the given valid + * @a index in the table which located at the given valid @a table_idx. + */ +static int +field_type(struct lua_State *L, int table_idx, int index) +{ + assert(lua_type(L, table_idx) == LUA_TTABLE); + lua_rawgeti(L, table_idx, index); + int rc = lua_type(L, -1); + lua_pop(L, 1); + return rc; +} + +/** + * Check if there is a field with the name @a name in the table, + * which located at the given valid @a idx, which should be a + * positive value. + */ +static bool +is_field_present(struct lua_State *L, int idx, const char *name) +{ + assert(idx > 0); + assert(lua_type(L, idx) == LUA_TTABLE); + lua_pushstring(L, name); + lua_rawget(L, idx); + bool field_is_present = (lua_type(L, -1) != LUA_TNIL); + lua_pop(L, 1); + return field_is_present; +} + +/** + * Create @a uri from the table, which located at the given valid @a idx, + * which should be a positive value. + */ +static int +uri_create_from_lua_table(struct lua_State *L, int idx, struct uri *uri) +{ + assert(idx > 0); + assert(lua_type(L, idx) == LUA_TTABLE); + /* There should be exactly one URI in the table */ + int size = lua_objlen(L, idx); + int uri_count = size + is_field_present(L, idx, "uri"); + if (uri_count != 1) { + diag_set(IllegalParams, "Invalid URI table: " + "expected {uri = string, params = table} " + "or {string, params = table}"); + return -1; + } + /* Table "default_params" is not allowed for single URI */ + if (is_field_present(L, idx, "default_params")) { + diag_set(IllegalParams, "Default URI parameters are " + "not allowed for single URI"); + return -1; + } + int rc = 0; + if (size == 1) { + lua_rawgeti(L, idx, 1); + } else { + lua_pushstring(L, "uri"); + lua_rawget(L, idx); + } + const char *uristr = lua_tostring(L, -1); + if (uristr != NULL) { + rc = uri_create(uri, uristr); + if (rc != 0) { + diag_set(IllegalParams, "Incorrect URI: expected " + "host:service or /unix.socket"); + } + } else { + diag_set(IllegalParams, "Incorrect type for URI in nested " + "table: should be string, number"); + rc = -1; + } + lua_pop(L, 1); + if (rc != 0) + return rc; + lua_pushstring(L, "params"); + lua_rawget(L, idx); + rc = uri_add_params_from_lua(uri, L, true); + lua_pop(L, 1); + if (rc != 0) + uri_destroy(uri); + return rc; + +} + +/** + * Create @a uri from the value at the given valid @a idx. + */ +static int +luaT_uri_create(struct lua_State *L, int idx, struct uri *uri) +{ + int rc = 0; + if (idx < 0) + idx = lua_gettop(L) + idx + 1; + assert(idx > 0); + if (lua_isstring(L, idx)) { + rc = uri_create(uri, lua_tostring(L, idx)); + if (rc != 0) { + diag_set(IllegalParams, "Incorrect URI: " + "expected host:service or " + "/unix.socket"); + } + } else if (lua_istable(L, idx)) { + rc = uri_create_from_lua_table(L, idx, uri); + } else if (lua_isnil(L, idx)) { + uri_create(uri, NULL); + } else { + diag_set(IllegalParams, "Incorrect type for URI: " + "should be string, number or table"); + rc = -1; + } + assert(rc == 0 || !diag_is_empty(diag_get())); + return rc; +} + +/** + * Create @a uri_set from the table, which located at the given valid @a idx, + * which should be a positive value. + */ +static int +uri_set_create_from_lua_table(struct lua_State *L, int idx, + struct uri_set *uri_set) +{ + int rc = 0; + assert(idx > 0); + assert(lua_type(L, idx) == LUA_TTABLE); + int size = lua_objlen(L, idx); + struct uri uri; + + uri_set_create(uri_set, NULL); + if (is_field_present(L, idx, "uri") || + (size == 1 && field_type(L, idx, 1) != LUA_TTABLE)) { + rc = luaT_uri_create(L, idx, &uri); + if (rc == 0) { + uri_set_add(uri_set, &uri); + uri_destroy(&uri); + } + return rc; + } else if (size == 0) { + return 0; + } + + /* + * All numeric keys corresponds to URIs in string or table + * format. + */ + for (int i = 0; i < size && rc == 0; i++) { + lua_rawgeti(L, idx, i + 1); + rc = luaT_uri_create(L, -1, &uri); + if (rc == 0) { + uri_set_add(uri_set, &uri); + uri_destroy(&uri); + } + lua_pop(L, 1); + } + if (rc != 0) + goto fail; + + /* + * Here we are only in case when it is an URI array, so it + * shouldn't be "params" field here. + */ + if (is_field_present(L, idx, "params")) { + diag_set(IllegalParams, "URI parameters are " + "not allowed for multiple URIs"); + goto fail; + } + + lua_pushstring(L, "default_params"); + lua_rawget(L, idx); + if (!lua_isnil(L, -1)) { + for (int i = 0; i < uri_set->uri_count && rc == 0; i++) { + struct uri *uri = &uri_set->uris[i]; + rc = uri_add_params_from_lua(uri, L, false); + assert(rc == 0 || !diag_is_empty(diag_get())); + } + } + lua_pop(L, 1); + if (rc != 0) + goto fail; + return 0; +fail: + uri_set_destroy(uri_set); + return -1; +} + +/** + * Create @a uri_set from the value at the given valid @a idx. + */ +static int +luaT_uri_set_create(struct lua_State *L, int idx, struct uri_set *uri_set) +{ + int rc = 0; + if (idx < 0) + idx = lua_gettop(L) + idx + 1; + assert(idx > 0); + if (lua_isstring(L, idx)) { + rc = uri_set_create(uri_set, lua_tostring(L, idx)); + if (rc != 0) { + diag_set(IllegalParams, "Incorrect URI: " + "expected host:service or " + "/unix.socket"); + } + } else if (lua_istable(L, idx)) { + rc = uri_set_create_from_lua_table(L, idx, uri_set); + } else if (lua_isnil(L, idx)) { + uri_set_create(uri_set, NULL); + } else { + diag_set(IllegalParams, "Incorrect type for URI: " + "should be string, number or table"); + rc = -1; + } + assert(rc == 0 || !diag_is_empty(diag_get())); + return rc; +} + +static int +luaT_uri_create_internal(lua_State *L) +{ + struct uri *uri = (struct uri *)lua_topointer(L, 1); + if (uri == NULL) + luaL_error(L, "Usage: uri.internal.uri_create(string|table)"); + if (luaT_uri_create(L, 2, uri) != 0) + luaT_error(L); + return 0; +} + +static int +luaT_uri_set_create_internal(lua_State *L) +{ + struct uri_set *uri_set = (struct uri_set *)lua_topointer(L, 1); + if (uri_set == NULL) + luaL_error(L, "Usage: uri.internal.uri_set_create(string|table)"); + if (luaT_uri_set_create(L, 2, uri_set) != 0) + luaT_error(L); + return 0; +} + +void +tarantool_lua_uri_init(struct lua_State *L) +{ + static const struct luaL_Reg uri_methods[] = { + {NULL, NULL} + }; + luaL_register_module(L, "uri", uri_methods); + + /* internal table */ + lua_pushliteral(L, "internal"); + lua_newtable(L); + static const struct luaL_Reg uri_internal_methods[] = { + {"uri_create", luaT_uri_create_internal}, + {"uri_set_create", luaT_uri_set_create_internal}, + {NULL, NULL} + }; + luaL_register(L, NULL, uri_internal_methods); + lua_settable(L, -3); + + lua_pop(L, 1); +}; diff --git a/src/lua/uri.h b/src/lua/uri.h new file mode 100644 index 0000000000..307e4de287 --- /dev/null +++ b/src/lua/uri.h @@ -0,0 +1,22 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file. + */ +#pragma once + +#if defined(__cplusplus) +extern "C" { +#endif /* defined(__cplusplus) */ + +struct lua_State; + +/** +* Initialize box.uri system +*/ +void +tarantool_lua_uri_init(struct lua_State *L); + +#if defined(__cplusplus) +} /* extern "C" */ +#endif /* defined(__cplusplus) */ diff --git a/src/lua/uri.lua b/src/lua/uri.lua index 98d5aa7cee..f2f7365bfe 100644 --- a/src/lua/uri.lua +++ b/src/lua/uri.lua @@ -2,6 +2,7 @@ local ffi = require('ffi') local buffer = require('buffer') +local uri = require('uri') ffi.cdef[[ struct uri_param { @@ -36,15 +37,9 @@ struct uri_set { struct uri *uris; }; -int -uri_create(struct uri *uri, const char *str); - void uri_destroy(struct uri *uri); -int -uri_set_create(struct uri_set *uri_set, const char *str); - void uri_set_destroy(struct uri_set *uri_set); @@ -93,12 +88,13 @@ end local function parse(str) if str == nil then - error("Usage: uri.parse(string)") + error("Usage: uri.parse(string|table)") end local uribuf = uri_stash_take() - if builtin.uri_create(uribuf, str) ~= 0 then + local status, errmsg = pcall(uri.internal.uri_create, uribuf, str) + if not status then uri_stash_put(uribuf) - return nil + return nil, errmsg end local result = parse_uribuf(uribuf) builtin.uri_destroy(uribuf) @@ -108,12 +104,13 @@ end local function parse_many(str) if str == nil then - error("Usage: uri.parse_many(string)") + error("Usage: uri.parse_many(string|table)") end local uri_set_buf = uri_set_stash_take() - if builtin.uri_set_create(uri_set_buf, str) ~= 0 then + local status, errmsg = pcall(uri.internal.uri_set_create, uri_set_buf, str) + if not status then uri_set_stash_put(uri_set_buf) - return nil + return nil, errmsg end local result = {} for i = 0, uri_set_buf.uri_count - 1 do @@ -124,6 +121,25 @@ local function parse_many(str) return result end +local function fill_uribuf_params(uribuf, uri) + uribuf.param_count = 0 + for _, _ in pairs(uri.params) do + uribuf.param_count = uribuf.param_count + 1 + end + uribuf.params = ffi.new("struct uri_param[?]", uribuf.param_count) + local i = 0 + for param_name, param in pairs(uri.params) do + uribuf.params[i].value_count = #param + uribuf.params[i].name = param_name + uribuf.params[i].values = + ffi.new("const char *[?]", uribuf.params[i].value_count) + for j = 1, uribuf.params[i].value_count do + uribuf.params[i].values[j - 1] = param[j] + end + i = i + 1 + end +end + local function format(uri, write_password) local uribuf = uri_stash_take() uribuf.scheme = uri.scheme @@ -134,6 +150,7 @@ local function format(uri, write_password) uribuf.path = uri.path uribuf.query = uri.query uribuf.fragment = uri.fragment + fill_uribuf_params(uribuf, uri) local ibuf = cord_ibuf_take() local str = ibuf:alloc(1024) local len = builtin.uri_format(str, 1024, uribuf, write_password and 1 or 0) diff --git a/static-build/test/static-build/exports.test.lua b/static-build/test/static-build/exports.test.lua index f0a0870e4a..2b23062cf3 100755 --- a/static-build/test/static-build/exports.test.lua +++ b/static-build/test/static-build/exports.test.lua @@ -63,10 +63,8 @@ local check_symbols = { 'tt_uuid_to_string', 'log_level', 'log_format', - 'uri_create', 'uri_destroy', 'uri_format', - 'uri_set_create', 'uri_set_destroy', 'PMurHash32', 'PMurHash32_Process', diff --git a/test/app-tap/uri.test.lua b/test/app-tap/uri.test.lua index 9830587e9d..9bc480511b 100755 --- a/test/app-tap/uri.test.lua +++ b/test/app-tap/uri.test.lua @@ -6,7 +6,7 @@ local uri = require('uri') local function test_parse(test) -- Tests for uri.parse() Lua bindings. -- Parser itself is tested by test/unit/uri_parser unit test. - test:plan(54) + test:plan(56) local u @@ -85,24 +85,77 @@ local function test_parse(test) test:is(u.unix, '/tmp/unix.sock', 'unix') test:is(u.path, '/path1/path2/path3', 'path') - u = uri.parse("") + local error, expected_errmsg + + expected_errmsg = "Incorrect URI: expected host:service or /unix.socket" + u, error = uri.parse("") test:isnil(u, "invalid uri", u) - u = uri.parse("://") + test:is(tostring(error), expected_errmsg, "error message") + u, error = uri.parse("://") test:isnil(u, "invalid uri", u) + test:is(tostring(error), expected_errmsg, "error message") end local function test_format(test) - test:plan(3) + test:plan(12) local u = uri.parse("user:password@localhost") test:is(uri.format(u), "user@localhost", "password removed") test:is(uri.format(u, false), "user@localhost", "password removed") test:is(uri.format(u, true), "user:password@localhost", "password kept") + + -- URI with empty query + u = uri.parse({"/tmp/unix.sock?"}) + test:is(uri.format(u), "unix/:/tmp/unix.sock", "URI format") + + -- URI with one empty parameter + u = uri.parse({"/tmp/unix.sock?q1"}) + test:is(uri.format(u), "unix/:/tmp/unix.sock?q1", "URI format") + + -- All parameters passed in "params" table. + u = uri.parse({"/tmp/unix.sock", params = {q1 = "v1", q2 = "v2"}}) + test:is(uri.format(u), "unix/:/tmp/unix.sock?q1=v1&q2=v2", "URI format") + + -- Empty parameter in URI string. + u = uri.parse({"/tmp/unix.sock?q1", params = {q2 = "v2", q3 = "v3"}}) + test:is(uri.format(u), "unix/:/tmp/unix.sock?q1&q2=v2&q3=v3", "URI format") + + -- Parameter without value in URI string. + u = uri.parse({"/tmp/unix.sock?q1=", params = {q2 = "v2", q3 = "v3"}}) + test:is(uri.format(u), "unix/:/tmp/unix.sock?q1=&q2=v2&q3=v3", "URI format") + + -- Some parameters passed in URI string and some different + -- parameters passed in "params" table. + u = uri.parse({"/tmp/unix.sock?q1=v1", params = {q2 = "v2"}}) + test:is(uri.format(u), "unix/:/tmp/unix.sock?q1=v1&q2=v2", "URI format") + + -- Same as previous but each parameter has several values. + u = uri.parse({ + "/tmp/unix.sock?q1=v11&q1=v12", + params = {q2 = {"v21", "v22"}} + }) + test:is(uri.format(u), "unix/:/tmp/unix.sock?q1=v11&q1=v12&q2=v21&q2=v22", + "URI format") + + -- One of parameters in "param" table has empty value + u = uri.parse({ + "/tmp/unix.sock?q1=v11&q1=v12", + params = {q2 = {""}} + }) + test:is(uri.format(u), "unix/:/tmp/unix.sock?q1=v11&q1=v12&q2=", "URI format") + + -- Parameter from "params" table overwrite + -- parameter from URI string. + u = uri.parse({ + "/tmp/unix.sock?q1=v11&q1=v12", + params = {q1 = {"v13", "v14"}, q2 = "v2"} + }) + test:is(uri.format(u), "unix/:/tmp/unix.sock?q1=v13&q1=v14&q2=v2", "URI format") end local function test_parse_uri_query_params(test) -- Tests for uri.parse() Lua bindings (URI with query parameters). -- Parser itself is tested by test/unit/uri unit test. - test:plan(33) + test:plan(55) local u @@ -146,12 +199,57 @@ local function test_parse_uri_query_params(test) test:is(u.params["q1"][2], "", "param value") test:is(type(u.params["q2"]), "table", "name") test:is(#u.params["q2"], 0, "value count") + + -- Parse URI passed in table format, parameter values + -- from "params" table, overwrite values from string. + u = uri.parse({ + "/tmp/unix.sock?q1=v11", + params = { q1 = "v12", q2 = "v21" } + }) + test:is(u.host, 'unix/', 'host') + test:is(u.service, '/tmp/unix.sock', 'service') + test:is(u.unix, '/tmp/unix.sock', 'unix') + test:is(type(u.params["q1"]), "table", "name") + test:is(#u.params["q1"], 1, "value count") + test:is(u.params["q1"][1], "v12", "param value") + test:is(type(u.params["q2"]), "table", "name") + test:is(#u.params["q2"], 1, "value count") + test:is(u.params["q2"][1], "v21", "param value") + + -- Same as previous but "uri=" syntax + u = uri.parse({ + uri = "/tmp/unix.sock?q1=v11", + params = { q1 = "v12", q2 = "v21" } + }) + test:is(u.host, 'unix/', 'host') + test:is(u.service, '/tmp/unix.sock', 'service') + test:is(u.unix, '/tmp/unix.sock', 'unix') + test:is(type(u.params["q1"]), "table", "name") + test:is(#u.params["q1"], 1, "value count") + test:is(u.params["q1"][1], "v12", "param value") + test:is(type(u.params["q2"]), "table", "name") + test:is(#u.params["q2"], 1, "value count") + test:is(u.params["q2"][1], "v21", "param value") + + local error, expected_errmsg + + -- "defult_params" is not allowed for single URI. + expected_errmsg = "Default URI parameters are not allowed for single URI" + u, error = uri.parse({ "/tmp/unix.sock", default_params = {q = "v"} }) + test:isnil(u, "invalid uri", u) + test:is(tostring(error), expected_errmsg, "error message") + -- Multiple URIs is not allowed in `parse` method, + -- use `parse_many` instead. + expected_errmsg = "Incorrect URI: expected host:service or /unix.socket" + u, error = uri.parse({ "/tmp/unix.sock, /tmp/unix.sock"}) + test:isnil(u, "invalid uri", u) + test:is(tostring(error), expected_errmsg, "error message") end local function test_parse_uri_set_with_query_params(test) -- Tests for uri.parse_many() Lua bindings (several URIs with query parameters). -- Parser itself is tested by test/unit/uri unit test. - test:plan(52) + test:plan(57) local uri_set @@ -198,31 +296,458 @@ local function test_parse_uri_set_with_query_params(test) uri_set = uri.parse_many("") test:is(#uri_set, 0, "uri_set") + local error, expected_errmsg + -- Check that uri.parse_many return nil for invalid URIs string uri_set = uri.parse_many("tmp/unix.sock, ://") test:isnil(uri_set, "invalid uri", uri_set) -- Extra ',' is not allowed - uri_set = uri.parse_many("/tmp/unix.sock,,/tmp/unix.sock") + expected_errmsg = "Incorrect URI: expected host:service or /unix.socket" + uri_set , error= uri.parse_many("/tmp/unix.sock,,/tmp/unix.sock") test:isnil(uri_set, "invalid uri", uri_set) - uri_set = uri.parse_many("/tmp/unix.sock, ,/tmp/unix.sock") + test:is(tostring(error), expected_errmsg, "error message") + uri_set, error = uri.parse_many("/tmp/unix.sock, ,/tmp/unix.sock") test:isnil(uri_set, "invalid uri", uri_set) - uri_set = uri.parse_many("/tmp/unix.sock,, /tmp/unix.sock") + test:is(tostring(error), expected_errmsg, "error message") + uri_set, error = uri.parse_many("/tmp/unix.sock,, /tmp/unix.sock") test:isnil(uri_set, "invalid uri", uri_set) - uri_set = uri.parse_many("/tmp/unix.sock ,,/tmp/unix.sock") + test:is(tostring(error), expected_errmsg, "error message") + uri_set, error = uri.parse_many("/tmp/unix.sock ,,/tmp/unix.sock") test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") -- Check that we can't parse string with multiple URIs, -- using old method. - local u = uri.parse("/tmp/unix.sock, /tmp/unix.sock") + expected_errmsg = "Incorrect URI: expected host:service or /unix.socket" + local u, error = uri.parse("/tmp/unix.sock, /tmp/unix.sock") test:isnil(u, "invalid uri", u) + test:is(tostring(error), expected_errmsg, "error message") +end + +local function test_parse_uri_set_from_lua_table(test) + -- Tests for uri.parse_many() Lua bindings. + -- (Several URIs with parameters, passed in different ways). + test:plan(134) + + local uri_set + + -- Array with one string address and one parameter + uri_set = uri.parse_many({"/tmp/unix.sock?q1=v1"}) + test:is(#uri_set, 1, "uri count") + test:is(uri_set[1].host, 'unix/', 'host') + test:is(uri_set[1].service, '/tmp/unix.sock', 'service') + test:is(uri_set[1].unix, '/tmp/unix.sock', 'unix') + test:is(type(uri_set[1].params["q1"]), "table", "name") + test:is(#uri_set[1].params["q1"], 1, "value count") + test:is(uri_set[1].params["q1"][1], "v1", "param value") + + -- Array with one string address and several parameters. + -- One of them passed in URI string, one separately as a string, + -- one separately as a table with two values. + uri_set = uri.parse_many({ + "/tmp/unix.sock?q1=v1", + params = {q2 = "v2", q3 = {"v31", "v32"}} + }) + test:is(#uri_set, 1, "uri count") + test:is(uri_set[1].host, 'unix/', 'host') + test:is(uri_set[1].service, '/tmp/unix.sock', 'service') + test:is(uri_set[1].unix, '/tmp/unix.sock', 'unix') + test:is(type(uri_set[1].params["q1"]), "table", "name") + test:is(#uri_set[1].params["q1"], 1, "value count") + test:is(uri_set[1].params["q1"][1], "v1", "param value") + test:is(type(uri_set[1].params["q2"]), "table", "name") + test:is(#uri_set[1].params["q2"], 1, "value count") + test:is(uri_set[1].params["q2"][1], "v2", "param value") + test:is(type(uri_set[1].params["q3"]), "table", "name") + test:is(#uri_set[1].params["q3"], 2, "value count") + test:is(uri_set[1].params["q3"][1], "v31", "param value") + test:is(uri_set[1].params["q3"][2], "v32", "param value") + + -- Same as previous but use "uri=" syntax to save URI value. + uri_set = uri.parse_many({ + uri = "/tmp/unix.sock?q1=v1", + params = {q2 = "v2", q3 = {"v31", "v32"}} + }) + test:is(#uri_set, 1, "uri count") + test:is(uri_set[1].host, 'unix/', 'host') + test:is(uri_set[1].service, '/tmp/unix.sock', 'service') + test:is(uri_set[1].unix, '/tmp/unix.sock', 'unix') + test:is(type(uri_set[1].params["q1"]), "table", "name") + test:is(#uri_set[1].params["q1"], 1, "value count") + test:is(uri_set[1].params["q1"][1], "v1", "param value") + test:is(type(uri_set[1].params["q2"]), "table", "name") + test:is(#uri_set[1].params["q2"], 1, "value count") + test:is(uri_set[1].params["q2"][1], "v2", "param value") + test:is(type(uri_set[1].params["q3"]), "table", "name") + test:is(#uri_set[1].params["q3"], 2, "value count") + test:is(uri_set[1].params["q3"][1], "v31", "param value") + test:is(uri_set[1].params["q3"][2], "v32", "param value") + + -- Check that URI parameter value from "params" table + -- overwrite parameter value from the string. + uri_set = uri.parse_many({ + "/tmp/unix.sock?q1=v1", + params = {q1 = {"v2", "v3"}} + }) + test:is(#uri_set, 1, "uri count") + test:is(uri_set[1].host, 'unix/', 'host') + test:is(uri_set[1].service, '/tmp/unix.sock', 'service') + test:is(uri_set[1].unix, '/tmp/unix.sock', 'unix') + test:is(type(uri_set[1].params["q1"]), "table", "name") + test:is(#uri_set[1].params["q1"], 2, "value count") + -- "v1" value was overwriten by values from "params" table + test:is(uri_set[1].params["q1"][1], "v2", "param value") + test:is(uri_set[1].params["q1"][2], "v3", "param value") + + -- Most common way: several URIs passed as array of strings + -- and objects with different parameters and default parameters. + uri_set = uri.parse_many({ + "/tmp/unix.sock?q1=v11", + { "/tmp/unix.sock?q2=v21", params = { q2 = "v22", q3 = "v31" } }, + { "/tmp/unix.sock", params = { q1 = 1, q2 = { 2, 3, 4}, q3 = 5 } }, + { uri = "/tmp/unix.sock?q2&q3=v31", params = { q3 = {"v33", "v34"} } }, + default_params = { + q1 = {"v12", "v13"}, q2 = {"v21", "v22"}, q3 = {"v32"}, q4 = 6 + } + }) + test:is(#uri_set, 4, "uri count") + -- First URI from uri_set + test:is(uri_set[1].host, 'unix/', 'host') + test:is(uri_set[1].service, '/tmp/unix.sock', 'service') + test:is(uri_set[1].unix, '/tmp/unix.sock', 'unix') + test:is(type(uri_set[1].params["q1"]), "table", "name") + test:is(#uri_set[1].params["q1"], 1, "value count") + -- As previously values from "default_params" table are + -- ignored if there is some values for this URI parameter + -- from URI string or "params" table. + test:is(uri_set[1].params["q1"][1], "v11", "param value") + test:is(type(uri_set[1].params["q2"]), "table", "name") + test:is(#uri_set[1].params["q2"], 2, "value count") + -- Values was added from "default_params" table. + test:is(uri_set[1].params["q2"][1], "v21", "param value") + test:is(uri_set[1].params["q2"][2], "v22", "param value") + test:is(type(uri_set[1].params["q3"]), "table", "name") + test:is(#uri_set[1].params["q3"], 1, "value count") + -- Value was added from "params" table. + test:is(uri_set[1].params["q3"][1], "v32", "param value") + test:is(type(uri_set[1].params["q4"]), "table", "name") + test:is(#uri_set[1].params["q4"], 1, "value count") + -- Numerical value, saved as a string. + test:is(uri_set[1].params["q4"][1], "6", "param value") + -- Second URI from uri_set + test:is(uri_set[2].host, 'unix/', 'host') + test:is(uri_set[2].service, '/tmp/unix.sock', 'service') + test:is(uri_set[2].unix, '/tmp/unix.sock', 'unix') + test:is(type(uri_set[2].params["q1"]), "table", "name") + test:is(#uri_set[2].params["q1"], 2, "value count") + -- Values from "default_params" table for "q1" parameter + -- are added, because there is no such parameter in URI + -- string and in "params" table. + test:is(uri_set[2].params["q1"][1], "v12", "param value") + test:is(uri_set[2].params["q1"][1], "v12", "param value") + test:is(type(uri_set[2].params["q2"]), "table", "name") + test:is(#uri_set[2].params["q2"], 1, "value count") + -- "q2" parameter value from URI string overwritten by + -- value from "params" table, values from "defaul_params" + -- table are ignored. + test:is(uri_set[2].params["q2"][1], "v22", "param value") + test:is(type(uri_set[2].params["q3"]), "table", "name") + test:is(#uri_set[2].params["q3"], 1, "value count") + -- "q3" parameter value added from from "params" table, + -- values from "defaul_params" table are ignored. + test:is(uri_set[2].params["q3"][1], "v31", "param value") + test:is(type(uri_set[2].params["q4"]), "table", "name") + test:is(#uri_set[2].params["q4"], 1, "value count") + -- Numerical value, saved as a string. + test:is(uri_set[2].params["q4"][1], "6", "param value") + -- Third URI from uri_set + -- All values are taken from "params" table, just check + -- how we parse numerical parameter values. + test:is(uri_set[3].host, 'unix/', 'host') + test:is(uri_set[3].service, '/tmp/unix.sock', 'service') + test:is(uri_set[3].unix, '/tmp/unix.sock', 'unix') + test:is(type(uri_set[3].params["q1"]), "table", "name") + test:is(#uri_set[3].params["q1"], 1, "value count") + test:is(uri_set[3].params["q1"][1], "1", "param value") + test:is(type(uri_set[3].params["q2"]), "table", "name") + test:is(#uri_set[3].params["q2"], 3, "value count") + test:is(uri_set[3].params["q2"][1], "2", "param value") + test:is(uri_set[3].params["q2"][2], "3", "param value") + test:is(uri_set[3].params["q2"][3], "4", "param value") + test:is(type(uri_set[3].params["q3"]), "table", "name") + test:is(#uri_set[3].params["q3"], 1, "value count") + test:is(uri_set[3].params["q3"][1], "5", "param value") + -- Fourth URI from uri_set + test:is(uri_set[4].host, 'unix/', 'host') + test:is(uri_set[4].service, '/tmp/unix.sock', 'service') + test:is(uri_set[4].unix, '/tmp/unix.sock', 'unix') + test:is(type(uri_set[4].params["q1"]), "table", "name") + test:is(#uri_set[4].params["q1"], 2, "value count") + -- As previous values was added from "default_params" table + test:is(uri_set[4].params["q1"][1], "v12", "param value") + test:is(uri_set[4].params["q1"][2], "v13", "param value") + test:is(type(uri_set[4].params["q2"]), "table", "name") + test:is(#uri_set[4].params["q2"], 2, "value count") + -- Values from "default_params" table for "q2" parameter + -- are added, because there is no values for this parameter + -- in URI string and there is no such paramater in "params + -- table. + test:is(uri_set[4].params["q2"][1], "v21", "param value") + test:is(uri_set[4].params["q2"][2], "v22", "param value") + test:is(type(uri_set[4].params["q3"]), "table", "name") + test:is(#uri_set[4].params["q3"], 2, "value count") + -- Value from URI string was overwritten by values from + -- "params" table. Values from "default_params" table are + -- ignored. + test:is(uri_set[4].params["q3"][1], "v33", "param value") + test:is(uri_set[4].params["q3"][2], "v34", "param value") + test:is(type(uri_set[4].params["q4"]), "table", "name") + test:is(#uri_set[4].params["q4"], 1, "value count") + -- Numerical value, saved as a string. + test:is(uri_set[4].params["q4"][1], "6", "param value") + + -- If some URI parameter is a table in "params" + -- or "default_params" table, all keys in this + -- table should be numerical, otherwise silently + -- ignored + uri_set = uri.parse_many({ + { "/tmp/unix.sock", params = {q1 = { x = "y"}} }, + "/tmp/unix.sock", + default_params = {q2 = {x = "y"}} + }) + test:is(#uri_set, 2, "uri count") + -- First URI from uri_set + test:is(uri_set[1].host, 'unix/', 'host') + test:is(uri_set[1].service, '/tmp/unix.sock', 'service') + test:is(uri_set[1].unix, '/tmp/unix.sock', 'unix') + test:is(type(uri_set[1].params["q1"]), "nil", "name") + test:is(type(uri_set[1].params["q2"]), "nil", "name") + test:is(uri_set[2].host, 'unix/', 'host') + test:is(uri_set[2].service, '/tmp/unix.sock', 'service') + test:is(uri_set[2].unix, '/tmp/unix.sock', 'unix') + test:is(type(uri_set[2].params["q1"]), "nil", "name") + test:is(type(uri_set[2].params["q2"]), "nil", "name") + + -- URI table without URI + uri_set = uri.parse_many({ + params = {q = "v"}, + default_params = {} + }) + test:is(#uri_set, 0, "uri count") + + -- URI table with one URI in table format + uri_set = uri.parse_many{{"/tmp/unix.sock"}, default_params = {q = "v"}} + test:is(#uri_set, 1, "uri count") + test:is(uri_set[1].host, 'unix/', 'host') + test:is(uri_set[1].service, '/tmp/unix.sock', 'service') + test:is(uri_set[1].unix, '/tmp/unix.sock', 'unix') + test:is(type(uri_set[1].params["q"]), "table", "name") + test:is(#uri_set[1].params["q"], 1, "value count") + test:is(uri_set[1].params["q"][1], "v", "param value") + + -- Same as previous but with "uri=" syntax + uri_set = uri.parse_many{{uri = "/tmp/unix.sock"}, default_params = {q = "v"}} + test:is(#uri_set, 1, "uri count") + test:is(uri_set[1].host, 'unix/', 'host') + test:is(uri_set[1].service, '/tmp/unix.sock', 'service') + test:is(uri_set[1].unix, '/tmp/unix.sock', 'unix') + test:is(type(uri_set[1].params["q"]), "table", "name") + test:is(#uri_set[1].params["q"], 1, "value count") + test:is(uri_set[1].params["q"][1], "v", "param value") +end + +local function test_parse_invalid_uri_set_from_lua_table(test) + -- Tests for uri.parse_many() Lua bindings. + -- (Several invalid URIs with parameters, passed in different ways). + test:plan(50) + + local uri_set, error, expected_errmsg + + -- Invalid type passed to "parse_many" + expected_errmsg = "Incorrect type for URI: should be string, number or table" + uri_set, error = uri.parse_many(function() end) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") + -- Invalid type of value for numerical key + expected_errmsg = "Incorrect type for URI: should be string, number or table" + uri_set, error = uri.parse_many({"/tmp/unix.sock", function() end}) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") + -- Invalid type of value for string keys + expected_errmsg = "Invalid URI table: expected " .. + "{uri = string, params = table}" .. " or " .. + "{string, params = table}" + uri_set, error = uri.parse_many({"/tmp/unix.sock", uri = function() end}) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") + expected_errmsg = "Incorrect type for URI parameters: should be a table" + uri_set, error = uri.parse_many({"/tmp/unix.sock", params = function() end}) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") + expected_errmsg = "Incorrect type for URI parameters: should be a table" + uri_set, error = uri.parse_many({"/tmp/unix.sock", params = ""}) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") + expected_errmsg = "Default URI parameters are not allowed for single URI" + uri_set, error = uri.parse_many({"/tmp/unix.sock", default_params = ""}) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") + expected_errmsg = "Default URI parameters are not allowed for single URI" + uri_set, error = uri.parse_many({"/tmp/unix.sock", default_params = ""}) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") + + -- Mix "uri=" and numerical keys is banned + expected_errmsg = "Invalid URI table: expected " .. + "{uri = string, params = table}" .. " or " .. + "{string, params = table}" + uri_set, error = uri.parse_many({"/tmp/unix.sock", uri = "/tmp/unix.sock"}) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") + -- Several URIs in one string is allowed only when the + -- passed as a single string. + expected_errmsg = "Incorrect URI: expected host:service or /unix.socket" + uri_set, error = uri.parse_many({"/tmp/unix.sock, /tmp/unix.sock"}) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") + -- "params" table is allowed only for single URI + expected_errmsg = "URI parameters are not allowed for multiple URIs" + uri_set, error = uri.parse_many({ + "/tmp/unix.sock", "/tmp/unix.sock", + params = {q1 = "v1"} + }) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") + -- "params" table is not allowed with nested tables + expected_errmsg = "URI parameters are not allowed for multiple URIs" + uri_set, error = uri.parse_many({ + {"/tmp/unix.sock"}, + params = {q1 = "v1"} + }) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") + -- "default_params" table is not allowed in nested URI tables + expected_errmsg = "Default URI parameters are not allowed for single URI" + uri_set, error = uri.parse_many({{"/tmp/unix.sock", default_params = {}}}) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") + -- "default_params" table is not allowed for single URI + expected_errmsg = "Default URI parameters are not allowed for single URI" + uri_set, error = uri.parse_many({"/tmp/unix.sock", default_params = {}}) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") + -- Only one URI is allowed in nested tables + expected_errmsg = "Invalid URI table: expected " .. + "{uri = string, params = table}" .. " or " .. + "{string, params = table}" + uri_set, error = uri.parse_many({ + {"/tmp/unix.sock", "/tmp/unix.sock"}, + default_params = {q = "v"} + }) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") + -- Nested URI tables is not allowed in nested tables + expected_errmsg = "Invalid URI table: expected ".. + "{uri = string, params = table}" .. " or " .. + "{string, params = table}" + uri_set, error = uri.parse_many({ + {"/tmp/unix.sock", {}} + }) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") + -- Nested URI table without URI is now allowed + expected_errmsg = "Invalid URI table: expected ".. + "{uri = string, params = table}" .. " or " .. + "{string, params = table}" + uri_set, error = uri.parse_many({ + "/tmp/unix.sock", + { params = {q = "v"} } + }) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") + -- Only string key types are allowed in "params" and + -- "default_params" table + expected_errmsg = "Incorrect type for URI parameter name: " .. + "should be a string" + uri_set, error = uri.parse_many({ + "/tmp/unix.sock", + params = {"v"}, + }) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") + expected_errmsg = "Default URI parameters are not allowed for single URI" + uri_set, error = uri.parse_many({ + "/tmp/unix.sock", + default_params = {"v"}, + }) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") + -- Invalid type of values in "params" and + -- "default_params" table + expected_errmsg = "Incorrect type for URI parameter value: " .. + "should be string, number or table" + uri_set, error = uri.parse_many({ + "/tmp/unix.sock", + params = {q = function() end}, + }) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") + expected_errmsg = "Default URI parameters are not allowed for single URI" + uri_set, error = uri.parse_many({ + "/tmp/unix.sock", + default_params = {q = function() end}, + }) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") + expected_errmsg = "Incorrect type for URI parameter value: ".. + "should be string or number" + uri_set, error = uri.parse_many({ + "/tmp/unix.sock", + params = {q = {function() end}}, + }) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") + expected_errmsg = "Default URI parameters are not allowed for single URI" + uri_set, error = uri.parse_many({ + "/tmp/unix.sock", + default_params = {q = {function() end}}, + }) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") + -- Invalid uri string in URIs table + expected_errmsg = "Incorrect URI: expected host:service or /unix.socket" + uri_set, error = uri.parse_many({ + "/tmp/unix.sock", + "://" + }) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") + -- Invalid uri in nested URI table + expected_errmsg = "Incorrect URI: expected host:service or /unix.socket" + uri_set, error = uri.parse_many({ + "/tmp/unix.sock", + {"://"} + }) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") + -- Same as previous but with "uri=" syntax + expected_errmsg = "Incorrect URI: expected host:service or /unix.socket" + uri_set, error = uri.parse_many({ + "/tmp/unix.sock", + {uri = "://"} + }) + test:isnil(uri_set, "invalid uri", uri_set) + test:is(tostring(error), expected_errmsg, "error message") end tap.test("uri", function(test) - test:plan(4) + test:plan(6) test:test("parse", test_parse) test:test("parse URI query params", test_parse_uri_query_params) test:test("parse URIs with query params", test_parse_uri_set_with_query_params) + test:test("parse URIs from lua table", test_parse_uri_set_from_lua_table) + test:test("parse invalid URIs from lua table", test_parse_invalid_uri_set_from_lua_table) test:test("format", test_format) end) -- GitLab