From 1a4c734a9157c938a41b5cd6ecacb6fe5e2b63f8 Mon Sep 17 00:00:00 2001 From: Rob Shearman Date: Mon, 15 Feb 2021 12:56:51 +0000 Subject: [PATCH 1/2] Support for printing JSON bare values Add support for printing top-level leaf nodes as bare values, which is triggered through the use of a new LYD_BARETOPLEAF flag. --- src/printer_data.h | 5 +++++ src/printer_json.c | 26 +++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/printer_data.h b/src/printer_data.h index fb2a5fb50..3c3e120d5 100644 --- a/src/printer_data.h +++ b/src/printer_data.h @@ -96,6 +96,11 @@ struct ly_out; are not explicitly present in the original data tree despite their value is equal to their default value. There is the same limitation regarding the presence of ietf-netconf-with-defaults module in libyang context. */ +#define LYD_PRINT_FRAGMENT 0x100 /**< Print the data tree as a fragment. For + JSON this means that apparent + top-level nodes do not have their + namespace added. */ +#define LYD_PRINT_BARETOPLEAF 0x200 /**< Print top-level leaf node as a bare value (JSON-only). */ /** * @} */ diff --git a/src/printer_json.c b/src/printer_json.c index 66ae1548e..d3141b6d8 100644 --- a/src/printer_json.c +++ b/src/printer_json.c @@ -270,8 +270,13 @@ json_print_string(struct ly_out *out, const char *text) static LY_ERR json_print_member(struct jsonpr_ctx *pctx, const struct lyd_node *node, ly_bool is_attr) { + if ((LEVEL == 1) && pctx->options & LYD_PRINT_BARETOPLEAF) { + return LY_SUCCESS; + } + PRINT_COMMA; - if ((LEVEL == 1) || json_nscmp(node, pctx->parent)) { + if (((LEVEL == 1) || json_nscmp(node, pctx->parent)) && + !(pctx->options & LYD_PRINT_FRAGMENT)) { /* print "namespace" */ ly_print_(pctx->out, "%*s\"%s%s:%s\":%s", INDENT, is_attr ? "@" : "", node_prefix(node), node->schema->name, DO_FORMAT ? " " : ""); @@ -298,6 +303,10 @@ json_print_member2(struct jsonpr_ctx *pctx, const struct lyd_node *parent, LY_VA { const char *module_name = NULL, *name_str; + if ((LEVEL == 1) && pctx->options & LYD_PRINT_BARETOPLEAF) { + return LY_SUCCESS; + } + PRINT_COMMA; /* determine prefix string */ @@ -999,6 +1008,7 @@ json_print_data(struct ly_out *out, const struct lyd_node *root, uint32_t option const struct lyd_node *node; struct jsonpr_ctx pctx = {0}; const char *delimiter = (options & LYD_PRINT_SHRINK) ? "" : "\n"; + int is_obj = 1; if (!root) { ly_print_(out, "{}%s", delimiter); @@ -1006,6 +1016,12 @@ json_print_data(struct ly_out *out, const struct lyd_node *root, uint32_t option return LY_SUCCESS; } + if (!(options & LYD_PRINT_WITHSIBLINGS) && + options & LYD_PRINT_BARETOPLEAF && + (root->schema->nodetype == LYS_LEAF)) { + is_obj = 0; + } + pctx.out = out; pctx.parent = NULL; pctx.level = 1; @@ -1014,7 +1030,9 @@ json_print_data(struct ly_out *out, const struct lyd_node *root, uint32_t option pctx.ctx = LYD_CTX(root); /* start */ - ly_print_(pctx.out, "{%s", delimiter); + if (is_obj) { + ly_print_(pctx.out, "{%s", delimiter); + } /* content */ LY_LIST_FOR(root, node) { @@ -1026,7 +1044,9 @@ json_print_data(struct ly_out *out, const struct lyd_node *root, uint32_t option } /* end */ - ly_print_(out, "%s}%s", delimiter, delimiter); + if (is_obj) { + ly_print_(pctx.out, "%s}%s", delimiter, delimiter); + } assert(!pctx.open.count); ly_set_erase(&pctx.open, NULL); From 1925634886447c7d518dd1340af85f3ddac2bda6 Mon Sep 17 00:00:00 2001 From: Rob Shearman Date: Mon, 15 Feb 2021 12:57:52 +0000 Subject: [PATCH 2/2] Support for parsing JSON bare leaf values Add support for parsing bare values for top-level (from the parser's point of view) leaf nodes, using a new LYD_OPT_BARETOPLEAF flag. The parsing of leaf nodes is too tied in with the allocation of a new node and parsing of the node name and namespace to easily trigger it from that case, so instead the case is check and handled explicitly in the top-level parser loop. --- src/parser_data.h | 3 ++ src/parser_json.c | 63 ++++++++++++++++++++++------ tests/utests/data/test_parser_json.c | 33 +++++++++++++++ 3 files changed, 87 insertions(+), 12 deletions(-) diff --git a/src/parser_data.h b/src/parser_data.h index feedb1407..e10d6af3c 100644 --- a/src/parser_data.h +++ b/src/parser_data.h @@ -172,6 +172,9 @@ struct ly_in; when parsing validated data to skip some validation tasks and modify some validation behavior (auto-deletion of cases). */ +#define LYD_PARSE_BARETOPLEAF 0x2000000 /**< Fragments for top-level leaf values are expected to be bare and not + have a top-level object or node name. */ + #define LYD_PARSE_OPTS_MASK 0xFFFF0000 /**< Mask for all the LYD_PARSE_ options. */ /** @} dataparseroptions */ diff --git a/src/parser_json.c b/src/parser_json.c index cd1e5d1d9..5597f654c 100644 --- a/src/parser_json.c +++ b/src/parser_json.c @@ -1817,7 +1817,7 @@ lydjson_subtree_r(struct lyd_json_ctx *lydctx, struct lyd_node *parent, struct l * @return LY_ERR value. */ static LY_ERR -lyd_parse_json_init(const struct ly_ctx *ctx, struct ly_in *in, uint32_t parse_opts, uint32_t val_opts, +lyd_parse_json_init(const struct ly_ctx *ctx, struct lyd_node *parent, struct ly_in *in, uint32_t parse_opts, uint32_t val_opts, struct lyd_json_ctx **lydctx_p) { LY_ERR ret = LY_SUCCESS; @@ -1837,7 +1837,14 @@ lyd_parse_json_init(const struct ly_ctx *ctx, struct ly_in *in, uint32_t parse_o status = lyjson_ctx_status(lydctx->jsonctx); /* parse_opts & LYD_PARSE_SUBTREE not implemented */ - if (status != LYJSON_OBJECT) { + if (parse_opts & LYD_PARSE_BARETOPLEAF && parent && (parent->schema->nodetype == LYS_LEAF)) { + if ((status != LYJSON_STRING) && (status != LYJSON_OBJECT)) { + LOGVAL(ctx, LYVE_SYNTAX_JSON, "Expected top-level JSON object or bare value, but %s found.", lyjson_token2str(status)); + *lydctx_p = NULL; + lyd_json_ctx_free((struct lyd_ctx *)lydctx); + return LY_EVALID; + } + } else if (status != LYJSON_OBJECT) { /* expecting top-level object */ LOGVAL(ctx, LYVE_SYNTAX_JSON, "Expected top-level JSON object, but %s found.", lyjson_token2str(status)); *lydctx_p = NULL; @@ -1857,8 +1864,9 @@ lyd_parse_json(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, st LY_ERR r, rc = LY_SUCCESS; struct lyd_json_ctx *lydctx = NULL; enum LYJSON_PARSER_STATUS status; + uint32_t log_location_items = 0; - rc = lyd_parse_json_init(ctx, in, parse_opts, val_opts, &lydctx); + rc = lyd_parse_json_init(ctx, parent, in, parse_opts, val_opts, &lydctx); LY_CHECK_GOTO(rc, cleanup); lydctx->int_opts = int_opts; @@ -1867,17 +1875,46 @@ lyd_parse_json(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, st /* find the operation node if it exists already */ LY_CHECK_GOTO(rc = lyd_parser_find_operation(parent, int_opts, &lydctx->op_node), cleanup); - /* read subtree(s) */ - do { - r = lydjson_subtree_r(lydctx, parent, first_p, parsed); + if (lyjson_ctx_status(lydctx->jsonctx) == LYJSON_OBJECT) { + /* read subtree(s) */ + do { + r = lydjson_subtree_r(lydctx, parent, first_p, parsed); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + + status = lyjson_ctx_status(lydctx->jsonctx); + + if (!(int_opts & LYD_INTOPT_WITH_SIBLINGS)) { + break; + } + } while (status == LYJSON_OBJECT_NEXT); + assert((status == LYJSON_END) || (status == LYJSON_OBJECT_CLOSED)); + } else { + struct lyd_node_term *t; + struct lysc_type *type; + struct lyd_value val; + + t = (struct lyd_node_term *)parent; + type = ((struct lysc_node_leaf *)parent->schema)->type; + + LOG_LOCSET(parent->schema, parent, NULL, NULL); + log_location_items++; + + r = lyd_value_store(LYD_CTX(parent), &val, type, lydctx->jsonctx->value, lydctx->jsonctx->value_len, 0, NULL, + LY_VALUE_JSON, NULL, LYD_HINT_DATA, parent->schema, NULL); LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); - status = lyjson_ctx_status(lydctx->jsonctx); + type->plugin->free(LYD_CTX(parent), &t->value); + t->value = val; - if (!(int_opts & LYD_INTOPT_WITH_SIBLINGS)) { - break; + r = lyjson_ctx_next(lydctx->jsonctx, &status); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + + if (status != LYJSON_END) { + LOGVAL(ctx, LYVE_SYNTAX_JSON, "Expected end-of-input, but %s found.", lyjson_token2str(status)); + rc = LY_EVALID; + goto cleanup; } - } while (status == LYJSON_OBJECT_NEXT); + } if ((int_opts & LYD_INTOPT_NO_SIBLINGS) && lydctx->jsonctx->in->current[0] && (status != LYJSON_OBJECT_CLOSED)) { LOGVAL(ctx, LYVE_SYNTAX, "Unexpected sibling node."); @@ -1891,7 +1928,7 @@ lyd_parse_json(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, st } /* finish linking metadata */ - r = lydjson_metadata_finish(lydctx, parent ? lyd_node_child_p(parent) : first_p); + r = lydjson_metadata_finish(lydctx, parent ? &parent : first_p); LY_CHECK_ERR_GOTO(r, rc = r, cleanup); if (parse_opts & LYD_PARSE_SUBTREE) { @@ -1921,6 +1958,8 @@ lyd_parse_json(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, st lyjson_ctx_free(lydctx->jsonctx); lydctx->jsonctx = NULL; } + + LOG_LOCBACK(log_location_items, log_location_items, 0, 0); return rc; } @@ -2008,7 +2047,7 @@ lyd_parse_json_restconf(const struct ly_ctx *ctx, const struct lysc_ext_instance assert(!(parse_opts & LYD_PARSE_SUBTREE)); /* init context */ - rc = lyd_parse_json_init(ctx, in, parse_opts, val_opts, &lydctx); + rc = lyd_parse_json_init(ctx, parent, in, parse_opts, val_opts, &lydctx); LY_CHECK_GOTO(rc, cleanup); lydctx->ext = ext; diff --git a/tests/utests/data/test_parser_json.c b/tests/utests/data/test_parser_json.c index 8feed9c09..8bd770cfa 100644 --- a/tests/utests/data/test_parser_json.c +++ b/tests/utests/data/test_parser_json.c @@ -914,6 +914,38 @@ test_metadata(void **state) CHECK_LOG_CTX("Invalid non-number-encoded int8 value \"value\".", "Path \"/a:c/x/@a:hint\", line number 1."); } +static void +test_baretopleaf(void **state) +{ + struct lyd_node *root; + struct lyd_node *node; + struct lyd_node *tree; + struct lyd_node_term *leaf; + const char *data; + struct ly_in *in = NULL; + int ret; + + ret = lyd_new_path2(NULL, UTEST_LYCTX, "/a:foo", NULL, 0, 0, 0, &root, &node); + if (ret) { + fail_msg("Print err 0x%d; MSG: %s", ret, ly_err_last(UTEST_LYCTX)->msg); + } + + data = "\"foo value\""; + if ((ret = ly_in_new_memory(data, &in))) { + fail_msg("Print err 0x%d; MSG: %s", ret, ly_err_last(UTEST_LYCTX)->msg); + } + + if ((ret = lyd_parse_data(UTEST_LYCTX, node, in, LYD_JSON, LYD_PARSE_BARETOPLEAF | LYD_PARSE_ONLY, 0, &tree))) { + fail_msg("Print err 0x%d; MSG: %s", ret, ly_err_last(UTEST_LYCTX)->msg); + } + leaf = (struct lyd_node_term *)node; + CHECK_LYD_VALUE(leaf->value, STRING, "foo value"); + + CHECK_LYD_STRING(node, LYD_PRINT_SHRINK | LYD_PRINT_BARETOPLEAF, data); + lyd_free_all(root); + ly_in_free(in, 0); +} + int main(void) { @@ -933,6 +965,7 @@ main(void) UTEST(test_restconf_notification, setup), UTEST(test_restconf_reply, setup), UTEST(test_metadata, setup), + UTEST(test_baretopleaf, setup), }; return cmocka_run_group_tests(tests, NULL, NULL);