aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJonathan Buchanan <jonathan.russ.buchanan@gmail.com>2020-07-21 03:14:41 -0400
committerJonathan Buchanan <jonathan.russ.buchanan@gmail.com>2020-07-21 03:14:41 -0400
commitfecfa277279c4f93a844de7cad1e75749d2c4f9f (patch)
tree2905dc77ae8a1286e3e4daa0d7220a5a5b418ec9 /src
parent78cc094d475c57914f36d5dfbf29c77c7c1fbb3f (diff)
add parser for json path
Diffstat (limited to 'src')
-rw-r--r--src/include/taler_json_lib.h32
-rw-r--r--src/json/json.c143
-rw-r--r--src/json/test_json.c144
3 files changed, 319 insertions, 0 deletions
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
@@ -184,6 +184,38 @@ TALER_JSON_contract_part_forget (json_t *json,
/**
+ * 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
@@ -460,6 +460,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))