From fecfa277279c4f93a844de7cad1e75749d2c4f9f Mon Sep 17 00:00:00 2001 From: Jonathan Buchanan Date: Tue, 21 Jul 2020 03:14:41 -0400 Subject: add parser for json path --- src/include/taler_json_lib.h | 32 ++++++++++ src/json/json.c | 143 ++++++++++++++++++++++++++++++++++++++++++ src/json/test_json.c | 144 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 319 insertions(+) diff --git a/src/include/taler_json_lib.h b/src/include/taler_json_lib.h index cfab56fd6..08b72b5b7 100644 --- a/src/include/taler_json_lib.h +++ b/src/include/taler_json_lib.h @@ -183,6 +183,38 @@ TALER_JSON_contract_part_forget (json_t *json, const char *field); +/** + * Called for each path found after expanding a path. + * + * @param cls the closure. + * @param object_id the name of the object that is pointed to. + * @param parent the parent of the object at @e object_id. + */ +typedef void +(*TALER_JSON_ExpandPathCallback) ( + void *cls, + const char *object_id, + json_t *parent); + + +/** + * Expands a path for a json object. May call the callback several times + * if the path contains a wildcard. + * + * @param json the json object the path references. + * @param path the path to expand. Must begin with "$." and follow dot notation, + * and may include array indices and wildcards. + * @param cb the callback. + * @param cb_cls closure for the callback. + * @return GNUNET_OK on success, GNUNET_SYSERR if @e path is invalid. + */ +int +TALER_JSON_expand_path (json_t *json, + const char *path, + TALER_JSON_ExpandPathCallback cb, + void *cb_cls); + + /** * Extract the Taler error code from the given @a json object. * Note that #TALER_EC_NONE is returned if no "code" is present. diff --git a/src/json/json.c b/src/json/json.c index 9ee1628e9..bcae6de11 100644 --- a/src/json/json.c +++ b/src/json/json.c @@ -459,6 +459,149 @@ TALER_JSON_contract_part_forget (json_t *json, } +/** + * Parse a json path. + * + * @param obj the object that the path is relative to. + * @param prev the parent of @e obj. + * @param path the path to parse. + * @param cb the callback to call, if we get to the end of @e path. + * @param cb_cls the closure for the callback. + * @return GNUNET_OK on success, GNUNET_SYSERR if @e path is malformed. + */ +static int +parse_path (json_t *obj, + json_t *prev, + const char *path, + TALER_JSON_ExpandPathCallback cb, + void *cb_cls) +{ + char *id = GNUNET_strdup (path); + char *next_id = strchr (id, + '.'); + char *next_path; + char *bracket; + json_t *parent = obj; + json_t *next_obj = NULL; + + if (NULL != next_id) + { + bracket = strchr (next_id, + '['); + *next_id = '\0'; + next_id++; + next_path = GNUNET_strdup (next_id); + char *next_dot = strchr (next_id, + '.'); + if (NULL != next_dot) + *next_dot = '\0'; + } + else + { + cb (cb_cls, + id, + prev); + return GNUNET_OK; + } + + /* If this is the first time this is called, make sure id is "$" */ + if ((NULL == prev) && + (0 != strcmp (id, + "$"))) + return GNUNET_SYSERR; + + /* Check for bracketed indices */ + if (NULL != bracket) + { + char *end_bracket = strchr (bracket, + ']'); + if (NULL == end_bracket) + return GNUNET_SYSERR; + *end_bracket = '\0'; + + *bracket = '\0'; + bracket++; + + parent = json_object_get (obj, + next_id); + if (0 == strcmp (bracket, + "*")) + { + size_t index; + json_t *value; + int ret = GNUNET_OK; + json_array_foreach (parent, index, value) { + ret = parse_path (value, + obj, + next_path, + cb, + cb_cls); + if (GNUNET_OK != ret) + { + GNUNET_free (id); + return ret; + } + } + } + else + { + unsigned int index; + if (1 != sscanf (bracket, + "%u", + &index)) + return GNUNET_SYSERR; + next_obj = json_array_get (parent, + index); + } + } + else + { + /* No brackets, so just fetch the object by name */ + next_obj = json_object_get (obj, + next_id); + } + + if (NULL != next_obj) + { + return parse_path (next_obj, + parent, + next_path, + cb, + cb_cls); + } + + GNUNET_free (id); + GNUNET_free (next_path); + + return GNUNET_OK; +} + + +/** + * Expands a path for a json object. May call the callback several times + * if the path contains a wildcard. + * + * @param json the json object the path references. + * @param path the path to expand. Must begin with "$." and follow dot notation, + * and may include array indices and wildcards. + * @param cb the callback. + * @param cb_cls closure for the callback. + * @return GNUNET_OK on success, GNUNET_SYSERR if @e path is invalid. + */ +int +TALER_JSON_expand_path (json_t *json, + const char *path, + TALER_JSON_ExpandPathCallback cb, + void *cb_cls) +{ + return parse_path (json, + NULL, + path, + cb, + cb_cls); +} + + /** * Extract the Taler error code from the given @a json object. * Note that #TALER_EC_NONE is returned if no "code" is present. diff --git a/src/json/test_json.c b/src/json/test_json.c index e876d95db..a99475205 100644 --- a/src/json/test_json.c +++ b/src/json/test_json.c @@ -56,6 +56,36 @@ test_amount (void) } +struct TestPath_Closure +{ + const char **object_ids; + + const json_t **parents; + + unsigned int results_length; + + int cmp_result; +}; + + +static void +path_cb (void *cls, + const char *object_id, + json_t *parent) +{ + struct TestPath_Closure *cmp = cls; + if (NULL == cmp) + return; + unsigned int i = cmp->results_length; + if ((0 != strcmp (cmp->object_ids[i], + object_id)) || + (1 != json_equal (cmp->parents[i], + parent))) + cmp->cmp_result = 1; + cmp->results_length += 1; +} + + static int test_contract () { @@ -64,6 +94,7 @@ test_contract () json_t *c1; json_t *c2; json_t *c3; + json_t *c4; c1 = json_pack ("{s:s, s:{s:s, s:{s:s}}}", "k1", "v1", @@ -113,6 +144,119 @@ test_contract () TALER_JSON_contract_hash (c3, &h2)); json_decref (c3); + c4 = json_pack ("{s:{s:s}, s:[{s:s}, {s:s}, {s:s}]}", + "abc1", + "xyz", "value", + "fruit", + "name", "banana", + "name", "apple", + "name", "orange"); + GNUNET_assert (NULL != c4); + GNUNET_assert (GNUNET_SYSERR == + TALER_JSON_expand_path (c4, + "%.xyz", + &path_cb, + NULL)); + GNUNET_assert (GNUNET_OK == + TALER_JSON_expand_path (c4, + "$.nonexistent_id", + &path_cb, + NULL)); + GNUNET_assert (GNUNET_SYSERR == + TALER_JSON_expand_path (c4, + "$.fruit[n]", + &path_cb, + NULL)); + + { + const char *object_ids[] = { "xyz" }; + const json_t *parents[] = { + json_object_get (c4, + "abc1") + }; + struct TestPath_Closure tp = { + .object_ids = object_ids, + .parents = parents, + .results_length = 0, + .cmp_result = 0 + }; + GNUNET_assert (GNUNET_OK == + TALER_JSON_expand_path (c4, + "$.abc1.xyz", + &path_cb, + &tp)); + GNUNET_assert (1 == tp.results_length); + GNUNET_assert (0 == tp.cmp_result); + } + { + const char *object_ids[] = { "name" }; + const json_t *parents[] = { + json_array_get (json_object_get (c4, + "fruit"), + 0) + }; + struct TestPath_Closure tp = { + .object_ids = object_ids, + .parents = parents, + .results_length = 0, + .cmp_result = 0 + }; + GNUNET_assert (GNUNET_OK == + TALER_JSON_expand_path (c4, + "$.fruit[0].name", + &path_cb, + &tp)); + GNUNET_assert (1 == tp.results_length); + GNUNET_assert (0 == tp.cmp_result); + } + { + const char *object_ids[] = { "name", "name", "name" }; + const json_t *parents[] = { + json_array_get (json_object_get (c4, + "fruit"), + 0), + json_array_get (json_object_get (c4, + "fruit"), + 1), + json_array_get (json_object_get (c4, + "fruit"), + 2) + }; + struct TestPath_Closure tp = { + .object_ids = object_ids, + .parents = parents, + .results_length = 0, + .cmp_result = 0 + }; + GNUNET_assert (GNUNET_OK == + TALER_JSON_expand_path (c4, + "$.fruit[*].name", + &path_cb, + &tp)); + GNUNET_assert (3 == tp.results_length); + GNUNET_assert (0 == tp.cmp_result); + } + { + const char *object_ids[] = { "fruit[0]" }; + const json_t *parents[] = { + json_object_get (c4, + "fruit") + }; + struct TestPath_Closure tp = { + .object_ids = object_ids, + .parents = parents, + .results_length = 0, + .cmp_result = 0 + }; + GNUNET_assert (GNUNET_OK == + TALER_JSON_expand_path (c4, + "$.fruit[0]", + &path_cb, + &tp)); + GNUNET_assert (1 == tp.results_length); + GNUNET_assert (0 == tp.cmp_result); + } + json_decref (c4); if (0 != GNUNET_memcmp (&h1, &h2)) -- cgit v1.2.3