diff options
Diffstat (limited to 'src/json')
| -rw-r--r-- | src/json/json.c | 143 | ||||
| -rw-r--r-- | src/json/test_json.c | 144 | 
2 files changed, 287 insertions, 0 deletions
diff --git a/src/json/json.c b/src/json/json.c index 9ee1628e..bcae6de1 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 e876d95d..a9947520 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))  | 
