[tarantool-patches] Re: [PATCH v3] json: add options to json.encode()

  • From: Vladislav Shpilevoy <v.shpilevoy@xxxxxxxxxxxxx>
  • To: roman.habibov1@xxxxxxxxx, "tarantool-patches@xxxxxxxxxxxxx" <tarantool-patches@xxxxxxxxxxxxx>
  • Date: Thu, 26 Jul 2018 15:33:22 +0300

The diff below again differs from the one on the branch.
Here what I see on the branch: 
https://github.com/tarantool/tarantool/commit/1476db8c67470f149e31f67255131802a7e02690

Its first diff is removal of a line from src/lua/utils.c. But diff
below firstly adds the line. Looks like you have sent reverted diff.

On 26/07/2018 15:29, roman.habibov1@xxxxxxxxx wrote:

Sorry again. I hurried.

diff --git a/src/lua/utils.c b/src/lua/utils.c
index 3b89719ef..2f0f4dcf8 100644
--- a/src/lua/utils.c
+++ b/src/lua/utils.c
@@ -186,6 +186,7 @@ luaL_setcdatagc(struct lua_State *L, int idx)
        lua_pop(L, 1);
  }
+
  #define OPTION(type, name, defvalue) { #name, \
        offsetof(struct luaL_serializer, name), type, defvalue}
  /**
@@ -213,44 +214,6 @@ static struct {
        { NULL, 0, 0, 0},
  };
-int *
-parse_option(lua_State *L, int i, struct luaL_serializer* cfg) {
-       lua_getfield(L, 2, OPTIONS[i].name);
-       if (lua_isnil(L, -1)) {
-               lua_pop(L, 1); /* key hasn't changed */
-               return NULL;
-       }
-       /*
-        * Update struct luaL_serializer using pointer to a
-        * configuration value (all values must be `int` for that).
-        */
-       int *pval = (int *) ((char *) cfg + OPTIONS[i].offset);
-       /* Update struct luaL_serializer structure */
-       switch (OPTIONS[i].type) {
-       case LUA_TBOOLEAN:
-               *pval = lua_toboolean(L, -1);
-               break;
-       case LUA_TNUMBER:
-               *pval = lua_tointeger(L, -1);
-               break;
-       default:
-               unreachable();
-       }
-       return pval;
-}
-
-int
-parse_options(lua_State *L, struct luaL_serializer *cfg) {
-    for (int i = 0; OPTIONS[i].name != NULL; i++) {
-        int *pval = parse_option(L, i, cfg);
-        /* Update struct luaL_serializer structure */
-        if (pval != NULL)
-            lua_pop(L, 1);
-    }
-    lua_pop(L, 1);
-    return 0;
-}
-
  /**
   * @brief serializer.cfg{} Lua binding for serializers.
   * serializer.cfg is a table that contains current configuration values from
@@ -269,22 +232,31 @@ luaL_serializer_cfg(lua_State *L)
        struct luaL_serializer *cfg = luaL_checkserializer(L);
        /* Iterate over all available options and checks keys in passed table */
        for (int i = 0; OPTIONS[i].name != NULL; i++) {
-               int* pval = parse_option(L, i, cfg);
+               lua_getfield(L, 2, OPTIONS[i].name);
+               if (lua_isnil(L, -1)) {
+                       lua_pop(L, 1); /* key hasn't changed */
+                       continue;
+               }
+               /*
+                * Update struct luaL_serializer using pointer to a
+                * configuration value (all values must be `int` for that).
+                */
+               int *pval = (int *) ((char *) cfg + OPTIONS[i].offset);
                /* Update struct luaL_serializer structure */
-               if (pval != NULL) {
-                       switch (OPTIONS[i].type) {
-                       case LUA_TBOOLEAN:
-                                       lua_pushboolean(L, *pval);
-                               break;
-                       case LUA_TNUMBER:
-                               lua_pushinteger(L, *pval);
-                               break;
-                       default:
-                               unreachable();
-                       }
+               switch (OPTIONS[i].type) {
+               case LUA_TBOOLEAN:
+                       *pval = lua_toboolean(L, -1);
+                       lua_pushboolean(L, *pval);
+                       break;
+               case LUA_TNUMBER:
+                       *pval = lua_tointeger(L, -1);
+                       lua_pushinteger(L, *pval);
+                       break;
+               default:
+                       unreachable();
+               }
                /* Save normalized value to serializer.cfg table */
                lua_setfield(L, 1, OPTIONS[i].name);
-               }
        }
        return 0;
  }
diff --git a/src/lua/utils.h b/src/lua/utils.h
index 21eb36856..6b057af3e 100644
--- a/src/lua/utils.h
+++ b/src/lua/utils.h
@@ -240,30 +240,6 @@ luaL_checkserializer(struct lua_State *L) {
                luaL_checkudata(L, lua_upvalueindex(1), LUAL_SERIALIZER);
  }
-/**
- * parse_option is a function that is used to configure one field
- * in luaL_serializer struct. Adds one lua table to the top of
- * Lua stack.
- * @param L lua stack
- * @param index of option in OPTIONS[]
- * @param serializer to inherit configuration
- * @return ponter to the value of option
- */
-int *
-parse_option(lua_State *l, int i, struct luaL_serializer* cfg);
-
-/**
- * parse_options is a function that is used to serialize lua table
- * of options to luaL_serializer struct. Removes the lua table from
- * the top of lua stack.
- * parse_options.
- * @param L lua stack
- * @param serializer to inherit configuration
- * @return 0
- */
-int
-parse_options(lua_State *l, struct luaL_serializer* cfg);
-
  /** A single value on the Lua stack. */
  struct luaL_field {
        union {
diff --git a/test/app-tap/json.test.lua b/test/app-tap/json.test.lua
index d76297ab5..3884b41e7 100755
--- a/test/app-tap/json.test.lua
+++ b/test/app-tap/json.test.lua
@@ -22,55 +22,7 @@ end
tap.test("json", function(test)
      local serializer = require('json')
-    test:plan(21)
-
--- gh-2888: check the possibility of using options in encode()/decode()
-
-    local sub = {a = 1, { b = {c = 1, d = {e = 1}}}}
-    serializer.cfg({encode_max_depth = 1})
-    test:ok(serializer.encode(sub) == '{"1":null,"a":1}',
-        'depth of encoding is 1 with .cfg')
-    serializer.cfg({encode_max_depth = 2})
-    test:ok(serializer.encode(sub) == '{"1":{"b":null},"a":1}',
-        'depth of encoding is 2 with .cfg')
-    serializer.cfg({encode_max_depth = 2})
-    test:ok(serializer.encode(sub, {encode_max_depth = 1}) == 
'{"1":null,"a":1}',
-        'depth of encoding is 1 with .encode')
-
-    local nan = 1/0
-    test:ok(serializer.encode({a = nan}) == '{"a":inf}',
-        'default "encode_invalid_numbers"')
-    serializer.cfg({encode_invalid_numbers = false})
-    test:ok(pcall(serializer.encode, {a = nan}) == false,
-        'expected error with NaN ecoding with .cfg')
-    serializer.cfg({encode_invalid_numbers = true})
-    test:ok(pcall(serializer.encode, {a = nan},
-        {encode_invalid_numbers = false}) == false,
-        'expected error with NaN ecoding with .encode')
-
-    local number = 0.12345
-    test:ok(serializer.encode({a = number}) == '{"a":0.12345}',
-        'precision more than 5')
-    serializer.cfg({encode_number_precision = 3})
-    test:ok(serializer.encode({a = number}) == '{"a":0.123}',
-        'precision is 3')
-    serializer.cfg({encode_number_precision = 14})
-    test:ok(serializer.encode({a = number},
-        {encode_number_precision = 3}) == '{"a":0.123}', 'precision is 3')
-
-    serializer.cfg({decode_invalid_numbers = false})
-    test:ok(pcall(serializer.decode, '{"a":inf}') == false,
-        'expected error with NaN decoding with .cfg')
-    serializer.cfg({decode_invalid_numbers = true})
-    test:ok(pcall(serializer.decode, '{"a":inf}',
-        {decode_invalid_numbers = false}) == false,
-        'expected error with NaN decoding with .decode')
-
-    test:ok(pcall(serializer.decode, '{"1":{"b":{"c":1,"d":null}},"a":1}',
-        {decode_max_depth = 2}) == false,
-        'error: too many nested data structures')
-
---
+    test:plan(9)
      test:test("unsigned", common.test_unsigned, serializer)
      test:test("signed", common.test_signed, serializer)
      test:test("double", common.test_double, serializer)
diff --git a/third_party/lua-cjson/lua_cjson.c 
b/third_party/lua-cjson/lua_cjson.c
index 29553fc4d..aa8217dfb 100644
--- a/third_party/lua-cjson/lua_cjson.c
+++ b/third_party/lua-cjson/lua_cjson.c
@@ -417,25 +417,22 @@ static void json_append_data(lua_State *l, struct 
luaL_serializer *cfg,
      }
  }
-static int json_encode(lua_State *l) {
-    luaL_argcheck(l, (lua_gettop(l) == 2) || (lua_gettop(l) == 1),
-        1, "expected 1 or 2 arguments");
+static int json_encode(lua_State *l)
+{
+    struct luaL_serializer *cfg = luaL_checkserializer(l);
+    char *json;
+    int len;
+
+    luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument");
/* Reuse existing buffer */
      strbuf_reset(&encode_buf);
-    struct luaL_serializer *cfg = luaL_checkserializer(l);
- if (lua_gettop(l) == 2) {
-        struct luaL_serializer user_cfg = *cfg;
-        parse_options(l, &user_cfg);
-        json_append_data(l, &user_cfg, 0, &encode_buf);
-    } else {
-        json_append_data(l, cfg, 0, &encode_buf);
-    }
+    json_append_data(l, cfg, 0, &encode_buf);
+    json = strbuf_string(&encode_buf, &len);
- int len;
-    char *json = strbuf_string(&encode_buf, &len);
      lua_pushlstring(l, json, len);
+
      return 1;
  }
@@ -980,17 +977,9 @@ static int json_decode(lua_State *l)
      json_token_t token;
      size_t json_len;
- luaL_argcheck(l, (lua_gettop(l) == 2) || (lua_gettop(l) == 1),
-        1, "expected 1 or 2 arguments");
-
-    if (lua_gettop(l) == 2) {
-        struct luaL_serializer user_cfg = *luaL_checkserializer(l);
-        parse_options(l, &user_cfg);
-        json.cfg = &user_cfg;
-    } else {
-        json.cfg = luaL_checkserializer(l);
-    }
+    luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument");
+ json.cfg = luaL_checkserializer(l);
      json.data = luaL_checklstring(l, 1, &json_len);
      json.current_depth = 0;
      json.ptr = json.data;

26.07.2018, 13:07, "Vladislav Shpilevoy" <v.shpilevoy@xxxxxxxxxxxxx>:
Hi!

1. Build error.

[ 66%] Building C object 
src/CMakeFiles/server.dir/__/third_party/lua-cjson/lua_cjson.c.o
/Users/v.shpilevoy/Work/Repositories/tarantool/src/lua/utils.c:250:13: error: 
use of undeclared identifier 'l'
      lua_pop(l, 1);
              ^
1 error generated.

2. The patch you sent below differs from the one on the
branch. For example, first diff lines on the branch:

         diff --git a/src/lua/utils.c b/src/lua/utils.c
         index 2f0f4dcf8..012758b7f 100644
         --- a/src/lua/utils.c
         +++ b/src/lua/utils.c
         @@ -186,7 +186,6 @@ luaL_setcdatagc(struct lua_State *L, int idx)
                  lua_pop(L, 1);
          }

         -
          #define OPTION(type, name, defvalue) { #name, \
                  offsetof(struct luaL_serializer, name), type, defvalue}
          /**
         @@ -214,6 +213,44 @@ static struct {

But below I see another diff.

On 26/07/2018 12:40, roman.habibov1@xxxxxxxxx wrote:
  Sorry. 
https://github.com/tarantool/tarantool/commit/768b875e498a17b3d5e404af79c1119b10a966e3

  diff --git a/src/lua/utils.c b/src/lua/utils.c
  index 0626bf76b..012758b7f 100644
  --- a/src/lua/utils.c
  +++ b/src/lua/utils.c
  @@ -186,19 +186,35 @@ luaL_setcdatagc(struct lua_State *L, int idx)
            lua_pop(L, 1);
    }

  +#define OPTION(type, name, defvalue) { #name, \
  + offsetof(struct luaL_serializer, name), type, defvalue}
    /**
  - * @brief serializer.cfg{} Lua binding for serializers.
  - * serializer.cfg is a table that contains current configuration values from
  - * luaL_serializer structure. serializer.cfg has overriden __call() method
  - * to change configuration keys in internal userdata (like box.cfg{}).
  - * Please note that direct change in serializer.cfg.key will not affect
  - * internal state of userdata.
  - * @param L lua stack
  - * @return 0
  + * Configuration options for serializers
  + * @sa struct luaL_serializer
     */
  -
  -/*Serialize one option. Returns ponter to the value of option.*/
  -int* parse_option(lua_State *L, int i, struct luaL_serializer* cfg) {
  +static struct {
  + const char *name;
  + size_t offset; /* offset in structure */
  + int type;
  + int defvalue;
  +} OPTIONS[] = {
  + OPTION(LUA_TBOOLEAN, encode_sparse_convert, 1),
  + OPTION(LUA_TNUMBER, encode_sparse_ratio, 2),
  + OPTION(LUA_TNUMBER, encode_sparse_safe, 10),
  + OPTION(LUA_TNUMBER, encode_max_depth, 32),
  + OPTION(LUA_TBOOLEAN, encode_invalid_numbers, 1),
  + OPTION(LUA_TNUMBER, encode_number_precision, 14),
  + OPTION(LUA_TBOOLEAN, encode_load_metatables, 1),
  + OPTION(LUA_TBOOLEAN, encode_use_tostring, 0),
  + OPTION(LUA_TBOOLEAN, encode_invalid_as_nil, 0),
  + OPTION(LUA_TBOOLEAN, decode_invalid_numbers, 1),
  + OPTION(LUA_TBOOLEAN, decode_save_metatables, 1),
  + OPTION(LUA_TNUMBER, decode_max_depth, 32),
  + { NULL, 0, 0, 0},
  +};
  +
  +int *
  +parse_option(lua_State *L, int i, struct luaL_serializer* cfg) {
            lua_getfield(L, 2, OPTIONS[i].name);
            if (lua_isnil(L, -1)) {
                    lua_pop(L, 1); /* key hasn't changed */
  @@ -223,6 +239,28 @@ int* parse_option(lua_State *L, int i, struct 
luaL_serializer* cfg) {
            return pval;
    }

  +int
  +parse_options(lua_State *L, struct luaL_serializer *cfg) {
  + for (int i = 0; OPTIONS[i].name != NULL; i++) {
  + int *pval = parse_option(L, i, cfg);
  + /* Update struct luaL_serializer structure */
  + if (pval != NULL)
  + lua_pop(L, 1);
  + }
  + lua_pop(l, 1);
  + return 0;
  +}
  +
  +/**
  + * @brief serializer.cfg{} Lua binding for serializers.
  + * serializer.cfg is a table that contains current configuration values from
  + * luaL_serializer structure. serializer.cfg has overriden __call() method
  + * to change configuration keys in internal userdata (like box.cfg{}).
  + * Please note that direct change in serializer.cfg.key will not affect
  + * internal state of userdata.
  + * @param L lua stack
  + * @return 0
  + */
    static int
    luaL_serializer_cfg(lua_State *L)
    {
  @@ -244,8 +282,8 @@ luaL_serializer_cfg(lua_State *L)
                            default:
                                    unreachable();
                            }
  - /* Save normalized value to serializer.cfg table */
  - lua_setfield(L, 1, OPTIONS[i].name);
  + /* Save normalized value to serializer.cfg table */
  + lua_setfield(L, 1, OPTIONS[i].name);
                    }
            }
            return 0;
  diff --git a/src/lua/utils.h b/src/lua/utils.h
  index 4a2c3eaac..21eb36856 100644
  --- a/src/lua/utils.h
  +++ b/src/lua/utils.h
  @@ -1,6 +1,5 @@
    #ifndef TARANTOOL_LUA_UTILS_H_INCLUDED
    #define TARANTOOL_LUA_UTILS_H_INCLUDED
  -#pragma GCC diagnostic ignored "-Wunused-variable"
    /*
     * Copyright 2010-2015, Tarantool AUTHORS, please see AUTHORS file.
     *
  @@ -241,34 +240,29 @@ luaL_checkserializer(struct lua_State *L) {
                    luaL_checkudata(L, lua_upvalueindex(1), LUAL_SERIALIZER);
    }

  -#define OPTION(type, name, defvalue) { #name, \
  - offsetof(struct luaL_serializer, name), type, defvalue}
    /**
  - * Configuration options for serializers
  - * @sa struct luaL_serializer
  + * parse_option is a function that is used to configure one field
  + * in luaL_serializer struct. Adds one lua table to the top of
  + * Lua stack.
  + * @param L lua stack
  + * @param index of option in OPTIONS[]
  + * @param serializer to inherit configuration
  + * @return ponter to the value of option
     */
  -static struct {
  - const char *name;
  - size_t offset; /* offset in structure */
  - int type;
  - int defvalue;
  -} OPTIONS[] = {
  - OPTION(LUA_TBOOLEAN, encode_sparse_convert, 1),
  - OPTION(LUA_TNUMBER, encode_sparse_ratio, 2),
  - OPTION(LUA_TNUMBER, encode_sparse_safe, 10),
  - OPTION(LUA_TNUMBER, encode_max_depth, 32),
  - OPTION(LUA_TBOOLEAN, encode_invalid_numbers, 1),
  - OPTION(LUA_TNUMBER, encode_number_precision, 14),
  - OPTION(LUA_TBOOLEAN, encode_load_metatables, 1),
  - OPTION(LUA_TBOOLEAN, encode_use_tostring, 0),
  - OPTION(LUA_TBOOLEAN, encode_invalid_as_nil, 0),
  - OPTION(LUA_TBOOLEAN, decode_invalid_numbers, 1),
  - OPTION(LUA_TBOOLEAN, decode_save_metatables, 1),
  - OPTION(LUA_TNUMBER, decode_max_depth, 32),
  - { NULL, 0, 0, 0},
  -};
  +int *
  +parse_option(lua_State *l, int i, struct luaL_serializer* cfg);

  -int* parse_option(lua_State *l, int i, struct luaL_serializer* cfg);
  +/**
  + * parse_options is a function that is used to serialize lua table
  + * of options to luaL_serializer struct. Removes the lua table from
  + * the top of lua stack.
  + * parse_options.
  + * @param L lua stack
  + * @param serializer to inherit configuration
  + * @return 0
  + */
  +int
  +parse_options(lua_State *l, struct luaL_serializer* cfg);

    /** A single value on the Lua stack. */
    struct luaL_field {
  diff --git a/test/app-tap/json.test.lua b/test/app-tap/json.test.lua
  index 050d769ea..d76297ab5 100755
  --- a/test/app-tap/json.test.lua
  +++ b/test/app-tap/json.test.lua
  @@ -22,42 +22,55 @@ end

    tap.test("json", function(test)
        local serializer = require('json')
  - test:plan(18)
  + test:plan(21)

  --- gh-2888 Added opions to encode().
  +-- gh-2888: check the possibility of using options in encode()/decode()

        local sub = {a = 1, { b = {c = 1, d = {e = 1}}}}
        serializer.cfg({encode_max_depth = 1})
        test:ok(serializer.encode(sub) == '{"1":null,"a":1}',
  - 'sub == {"1":null,"a":1}')
  + 'depth of encoding is 1 with .cfg')
        serializer.cfg({encode_max_depth = 2})
        test:ok(serializer.encode(sub) == '{"1":{"b":null},"a":1}',
  - 'sub == {"1":{"b":null},"a":1}')
  + 'depth of encoding is 2 with .cfg')
        serializer.cfg({encode_max_depth = 2})
  - test:ok(serializer.encode(sub,{encode_max_depth = 1})
  - == '{"1":null,"a":1}', 'sub == {"1":null,"a":1}')
  + test:ok(serializer.encode(sub, {encode_max_depth = 1}) == 
'{"1":null,"a":1}',
  + 'depth of encoding is 1 with .encode')

        local nan = 1/0
        test:ok(serializer.encode({a = nan}) == '{"a":inf}',
  - 'a = nan == {"a":inf}')
  + 'default "encode_invalid_numbers"')
        serializer.cfg({encode_invalid_numbers = false})
        test:ok(pcall(serializer.encode, {a = nan}) == false,
  - 'error when "encode_invalid_numbers = false" with NaN')
  + 'expected error with NaN ecoding with .cfg')
        serializer.cfg({encode_invalid_numbers = true})
        test:ok(pcall(serializer.encode, {a = nan},
  - {encode_invalid_numbers = false}) == false,
  - 'error when "encode_invalid_numbers = false" with NaN')
  + {encode_invalid_numbers = false}) == false,
  + 'expected error with NaN ecoding with .encode')

        local number = 0.12345
        test:ok(serializer.encode({a = number}) == '{"a":0.12345}',
  - 'precision more than 5')
  + 'precision more than 5')
        serializer.cfg({encode_number_precision = 3})
        test:ok(serializer.encode({a = number}) == '{"a":0.123}',
  - 'precision is 3')
  + 'precision is 3')
        serializer.cfg({encode_number_precision = 14})
        test:ok(serializer.encode({a = number},
  - {encode_number_precision = 3}) == '{"a":0.123}', 'precision is 3')
  + {encode_number_precision = 3}) == '{"a":0.123}', 'precision is 3')

  + serializer.cfg({decode_invalid_numbers = false})
  + test:ok(pcall(serializer.decode, '{"a":inf}') == false,
  + 'expected error with NaN decoding with .cfg')
  + serializer.cfg({decode_invalid_numbers = true})
  + test:ok(pcall(serializer.decode, '{"a":inf}',
  + {decode_invalid_numbers = false}) == false,
  + 'expected error with NaN decoding with .decode')
  +
  + test:ok(pcall(serializer.decode, '{"1":{"b":{"c":1,"d":null}},"a":1}',
  + {decode_max_depth = 2}) == false,
  + 'error: too many nested data structures')
  +
  +--
        test:test("unsigned", common.test_unsigned, serializer)
        test:test("signed", common.test_signed, serializer)
        test:test("double", common.test_double, serializer)
  diff --git a/third_party/lua-cjson/lua_cjson.c 
b/third_party/lua-cjson/lua_cjson.c
  index 861079f8a..29553fc4d 100644
  --- a/third_party/lua-cjson/lua_cjson.c
  +++ b/third_party/lua-cjson/lua_cjson.c
  @@ -417,21 +417,9 @@ static void json_append_data(lua_State *l, struct 
luaL_serializer *cfg,
        }
    }

  -/*Serialize Lua table of options to luaL_serializer struct.*/
  -static int parse_options(lua_State *l, struct luaL_serializer* cfg) {
  - for (int i = 0; OPTIONS[i].name != NULL; i++) {
  - int *pval = parse_option(l, i, cfg);
  - /* Update struct luaL_serializer structure */
  - if (pval != NULL)
  - lua_pop(l, 1);
  - }
  - lua_pop(l, 1);
  - return 0;
  -}
  -
    static int json_encode(lua_State *l) {
        luaL_argcheck(l, (lua_gettop(l) == 2) || (lua_gettop(l) == 1),
  - 1, "expected 1 or 2 arguments");
  + 1, "expected 1 or 2 arguments");

        /* Reuse existing buffer */
        strbuf_reset(&encode_buf);
  @@ -443,7 +431,7 @@ static int json_encode(lua_State *l) {
            json_append_data(l, &user_cfg, 0, &encode_buf);
        } else {
            json_append_data(l, cfg, 0, &encode_buf);
  -}
  + }

        int len;
        char *json = strbuf_string(&encode_buf, &len);
  @@ -993,7 +981,7 @@ static int json_decode(lua_State *l)
        size_t json_len;

        luaL_argcheck(l, (lua_gettop(l) == 2) || (lua_gettop(l) == 1),
  - 1, "expected 1 or 2 arguments");
  + 1, "expected 1 or 2 arguments");

        if (lua_gettop(l) == 2) {
            struct luaL_serializer user_cfg = *luaL_checkserializer(l);

  26.07.2018, 00:35, "Vladislav Shpilevoy" <v.shpilevoy@xxxxxxxxxxxxx>:
  Hi! Thanks for the fixes!

  1. Again, as I said on the previous review. Please, put a new
  patch version at the end of letter.

  2. On the branch I still see the old version. So looks like
  you forgot to push. Please, do it and resend the patch.

Other related posts: