diff --git a/src/parser_data.h b/src/parser_data.h index db99b3b60..2d0c1aa36 100644 --- a/src/parser_data.h +++ b/src/parser_data.h @@ -336,6 +336,24 @@ LIBYANG_API_DECL LY_ERR lyd_parse_data_path(const struct ly_ctx *ctx, const char LIBYANG_API_DECL LY_ERR lyd_parse_ext_data(const struct lysc_ext_instance *ext, struct lyd_node *parent, struct ly_in *in, LYD_FORMAT format, uint32_t parse_options, uint32_t validate_options, struct lyd_node **tree); +/** + * @brief Parse data from the input handler as a bare JSON value and connect it to the node parsed from the path. + * + * @param[in] ctx Context to connect with the tree being built here. + * @param[in] path Path to the node so the value in the input handler can be connected to it. + * @param[in] in Input handler with the value in the JSON format. + * @param[in] format Currently only LYD_JSON is supported. + * @param[in] new_val_options Options for new values of the parent node (node to attach the @p in to), see @ref newvaloptions. + * @param[in] parse_options Options for parser, see @ref dataparseroptions. + * @param[in] validate_options Options for the validation phase, see @ref datavalidationoptions. + * @param[out] tree Full parsed data tree, note that NULL can be a valid tree. + * @return LY_SUCCESS in case of successful parsing. + * @return LY_ERR value in case of error. Additional error information can be obtained from the context using ly_err* functions. + */ +LIBYANG_API_DECL LY_ERR lyd_parse_value_fragment(const struct ly_ctx *ctx, const char *path, struct ly_in *in, + LYD_FORMAT format, uint32_t new_val_options, uint32_t parse_options, uint32_t validate_options, + struct lyd_node **tree); + /** * @ingroup datatree * @defgroup datatype Data operation type diff --git a/src/parser_internal.h b/src/parser_internal.h index b78d151f7..e5e3ba2e6 100644 --- a/src/parser_internal.h +++ b/src/parser_internal.h @@ -286,6 +286,7 @@ LY_ERR lyd_parse_xml_netconf(const struct ly_ctx *ctx, const struct lysc_ext_ins * @param[in] ctx libyang context. * @param[in] ext Optional extension instance to parse data following the schema tree specified in the extension instance * @param[in] parent Parent to connect the parsed nodes to, if any. + * @param[in] schema Optional schema node of the parsed node (mandatory when parsing JSON value fragment). * @param[in,out] first_p Pointer to the first top-level parsed node, used only if @p parent is NULL. * @param[in] in Input structure. * @param[in] parse_opts Options for parser, see @ref dataparseroptions. @@ -293,12 +294,13 @@ LY_ERR lyd_parse_xml_netconf(const struct ly_ctx *ctx, const struct lysc_ext_ins * @param[in] int_opts Internal data parser options. * @param[out] parsed Set to add all the parsed siblings into. * @param[out] subtree_sibling Set if ::LYD_PARSE_SUBTREE is used and another subtree is following in @p in. - * @param[out] lydctx_p Data parser context to finish validation. + * @param[out] lydctx_p Optional data parser context to finish validation. * @return LY_ERR value. */ LY_ERR lyd_parse_json(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, struct lyd_node *parent, - struct lyd_node **first_p, struct ly_in *in, uint32_t parse_opts, uint32_t val_opts, uint32_t int_opts, - struct ly_set *parsed, ly_bool *subtree_sibling, struct lyd_ctx **lydctx_p); + const struct lysc_node *schema, struct lyd_node **first_p, struct ly_in *in, uint32_t parse_opts, + uint32_t val_opts, uint32_t int_opts, struct ly_set *parsed, ly_bool *subtree_sibling, + struct lyd_ctx **lydctx_p); /** * @brief Parse JSON string as a RESTCONF message. diff --git a/src/parser_json.c b/src/parser_json.c index d46d84ebd..689609a37 100644 --- a/src/parser_json.c +++ b/src/parser_json.c @@ -935,7 +935,7 @@ lydjson_meta_attr(struct lyd_json_ctx *lydctx, const struct lysc_node *snode, st */ static void lydjson_maintain_children(struct lyd_node *parent, struct lyd_node **first_p, struct lyd_node **node_p, ly_bool last, - struct lysc_ext_instance *ext) + const struct lysc_ext_instance *ext) { if (!*node_p) { return; @@ -1822,15 +1822,17 @@ lydjson_subtree_r(struct lyd_json_ctx *lydctx, struct lyd_node *parent, struct l * @brief Common start of JSON parser processing different types of the input data. * * @param[in] ctx libyang context + * @param[in] schema Schema node of the potential bare value to check. * @param[in] in Input structure. * @param[in] parse_opts Options for parser, see @ref dataparseroptions. * @param[in] val_opts Options for the validation phase, see @ref datavalidationoptions. + * @param[out] status_p Initial status of the input structure. * @param[out] lydctx_p Data parser context to finish validation. * @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, - struct lyd_json_ctx **lydctx_p) +lyd_parse_json_init(const struct ly_ctx *ctx, const struct lysc_node *schema, struct ly_in *in, uint32_t parse_opts, + uint32_t val_opts, enum LYJSON_PARSER_STATUS *status_p, struct lyd_json_ctx **lydctx_p) { LY_ERR ret = LY_SUCCESS; struct lyd_json_ctx *lydctx; @@ -1849,28 +1851,139 @@ 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) { - /* expecting top-level object */ - LOGVAL(ctx, LYVE_SYNTAX_JSON, "Expected top-level JSON object, but %s found.", lyjson_token2str(status)); + /* there are two options: either we want to parse a bare JSON value or a JSON object + * node of the bare JSON value has to have a schema, otherwise we do not know where to put the value + * the only types of nodes that can take on a value are a leaf (number, string or bool) and a leaf-list (array) */ + if (schema && + (((status == LYJSON_ARRAY) && (schema->nodetype & LYS_LEAFLIST)) || + (((status == LYJSON_NUMBER) || (status == LYJSON_STRING) || (status == LYJSON_FALSE) || + (status == LYJSON_TRUE) || (status == LYJSON_NULL) || (status == LYJSON_ARRAY)) && (schema->nodetype & LYS_LEAF)))) { + /* bare value (bare anydata 'value = object' is not supported) */ + } else if (status == LYJSON_OBJECT) { + /* JSON object */ + } else { + /* expecting top-level object or bare value */ + LOGVAL(ctx, LYVE_SYNTAX_JSON, "Expected top-level JSON object or correct bare value, but %s found.", lyjson_token2str(status)); *lydctx_p = NULL; lyd_json_ctx_free((struct lyd_ctx *)lydctx); return LY_EVALID; } *lydctx_p = lydctx; + if (status_p) { + *status_p = status; + } return LY_SUCCESS; } +/** + * @brief Parse a bare JSON value. + * + * @param[in] lydctx Data parser context. + * @param[in,out] status Current status of the data parser context. + * @param[in] parent Parent to connect the parsed nodes to, if any. + * @param[in] schema Optional schema node of the parsed node (mandatory when parsing JSON value fragment). + * @param[in,out] first_p Pointer to the first top-level parsed node, used only if @p parent is NULL. + * @return LY_ERR value. + */ +static LY_ERR +lyd_parse_json_bare_value(struct lyd_json_ctx *lydctx, enum LYJSON_PARSER_STATUS *status, struct lyd_node *parent, + const struct lysc_node *schema, struct lyd_node **first_p) +{ + LY_ERR r, rc = LY_SUCCESS; + const struct ly_ctx *ctx = lydctx->jsonctx->ctx; + struct lyd_node *node = NULL; + const char *expected = NULL; + + assert(schema); + + /* this branch partly copies the behavior of lydjson_subtree_r() */ + + /* set expected representation */ + switch (schema->nodetype) { + case LYS_LEAFLIST: + expected = "array of values"; + break; + case LYS_LEAF: + if (*status == LYJSON_ARRAY) { + expected = "[null]"; + } else { + expected = "value"; + } + break; + } + + /* check the representation according to the nodetype and then continue with the content */ + /* for now object values are not supported (anydata) */ + /* for now extensions not supported */ + switch (schema->nodetype) { + case LYS_LEAFLIST: + LY_CHECK_GOTO(*status != LYJSON_ARRAY, representation_error); + + /* process all the values/objects */ + do { + /* move into array/next value */ + r = lyjson_ctx_next(lydctx->jsonctx, status); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + if (*status == LYJSON_ARRAY_CLOSED) { + /* empty array, fine... */ + break; + } + + r = lydjson_parse_instance(lydctx, parent, first_p, schema, NULL, schema->name, strlen(schema->name), + NULL, 0, status, &node); + if (r == LY_ENOT) { + goto representation_error; + } + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + + lydjson_maintain_children(parent, first_p, &node, + lydctx->parse_opts & LYD_PARSE_ORDERED ? LYD_INSERT_NODE_LAST : LYD_INSERT_NODE_DEFAULT, NULL); + + /* move after the item(s) */ + r = lyjson_ctx_next(lydctx->jsonctx, status); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + } while (*status == LYJSON_ARRAY_NEXT); + + break; + case LYS_LEAF: + /* process the value/object */ + r = lydjson_parse_instance(lydctx, parent, first_p, schema, NULL, schema->name, strlen(schema->name), + NULL, 0, status, &node); + if (r == LY_ENOT) { + goto representation_error; + } + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + + /* finally connect the parsed node, is zeroed */ + lydjson_maintain_children(parent, first_p, &node, + lydctx->parse_opts & LYD_PARSE_ORDERED ? LYD_INSERT_NODE_LAST : LYD_INSERT_NODE_DEFAULT, NULL); + break; + } + + goto cleanup; + +representation_error: + LOGVAL(ctx, LYVE_SYNTAX_JSON, "Expecting JSON %s but %s \"%s\" is represented in input data as %s.", + expected, lys_nodetype2str(schema->nodetype), schema->name, lyjson_token2str(*status)); + rc = LY_EVALID; + +cleanup: + lyd_free_tree(node); + return rc; +} + LY_ERR lyd_parse_json(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, struct lyd_node *parent, - struct lyd_node **first_p, struct ly_in *in, uint32_t parse_opts, uint32_t val_opts, uint32_t int_opts, - struct ly_set *parsed, ly_bool *subtree_sibling, struct lyd_ctx **lydctx_p) + const struct lysc_node *schema, struct lyd_node **first_p, struct ly_in *in, uint32_t parse_opts, + uint32_t val_opts, uint32_t int_opts, struct ly_set *parsed, ly_bool *subtree_sibling, + struct lyd_ctx **lydctx_p) { LY_ERR r, rc = LY_SUCCESS; struct lyd_json_ctx *lydctx = NULL; - enum LYJSON_PARSER_STATUS status; + enum LYJSON_PARSER_STATUS status = LYJSON_ERROR; - rc = lyd_parse_json_init(ctx, in, parse_opts, val_opts, &lydctx); + rc = lyd_parse_json_init(ctx, schema, in, parse_opts, val_opts, &status, &lydctx); LY_CHECK_GOTO(rc, cleanup); lydctx->int_opts = int_opts; @@ -1879,17 +1992,25 @@ 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); - LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + if (status != LYJSON_OBJECT) { + /* parse bare JSON value */ + r = lyd_parse_json_bare_value(lydctx, &status, parent, schema, first_p); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + } else { + /* parse JSON object */ - status = lyjson_ctx_status(lydctx->jsonctx); + /* read subtree(s) */ + do { + r = lydjson_subtree_r(lydctx, parent, first_p, parsed); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); - if (!(int_opts & LYD_INTOPT_WITH_SIBLINGS)) { - break; - } - } while (status == LYJSON_OBJECT_NEXT); + status = lyjson_ctx_status(lydctx->jsonctx); + + if (!(int_opts & LYD_INTOPT_WITH_SIBLINGS)) { + break; + } + } 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."); @@ -1927,11 +2048,16 @@ lyd_parse_json(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, st if (rc && (!lydctx || !(lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR) || (rc != LY_EVALID))) { lyd_json_ctx_free((struct lyd_ctx *)lydctx); } else { - *lydctx_p = (struct lyd_ctx *)lydctx; - /* the JSON context is no more needed, freeing it also stops logging line numbers which would be confusing now */ lyjson_ctx_free(lydctx->jsonctx); lydctx->jsonctx = NULL; + + /* set optional lydctx pointer, otherwise free */ + if (lydctx_p) { + *lydctx_p = (struct lyd_ctx *)lydctx; + } else { + lyd_json_ctx_free((struct lyd_ctx *)lydctx); + } } return rc; } @@ -2020,7 +2146,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, NULL, in, parse_opts, val_opts, NULL, &lydctx); LY_CHECK_GOTO(rc, cleanup); lydctx->ext = ext; diff --git a/src/printer_data.h b/src/printer_data.h index 4a2f878b2..12867bbd3 100644 --- a/src/printer_data.h +++ b/src/printer_data.h @@ -111,6 +111,13 @@ 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_JSON_NO_NESTED_PREFIX 0x0100 /**< Do not print the prefix in JSON data for nested nodes + (non-top-level). By nested we mean any node that does not appear + in the top-level of a corresponding schema. The printed data do + not have the information about the module they belong to. When + parsing such data it is important to include this information + elsewhere (e.g. for lyd_parse_value_fragment() the module name + should be part of the path parameter). */ /** * @} */ diff --git a/src/printer_json.c b/src/printer_json.c index d9e603d7c..c1e6a1ff4 100644 --- a/src/printer_json.c +++ b/src/printer_json.c @@ -307,7 +307,7 @@ json_print_member(struct jsonpr_ctx *pctx, const struct lyd_node *node, const st } PRINT_COMMA; - if ((LEVEL == 1) || json_nscmp(node, snode, pctx->parent)) { + if (json_nscmp(node, snode, pctx->parent)) { /* print "namespace" */ node_prefix(node, snode, &pref, NULL); ly_print_(pctx->out, "%*s\"%s%s:%s\":%s", INDENT, is_attr ? "@" : "", pref, name, DO_FORMAT ? " " : ""); @@ -1174,6 +1174,10 @@ json_print_data(struct ly_out *out, const struct lyd_node *root, uint32_t option /* content */ LY_LIST_FOR(root, node) { pctx.root = node; + if (options & LYD_PRINT_JSON_NO_NESTED_PREFIX) { + /* update parent, if it has the same namespace it will not be printed */ + pctx.parent = (const struct lyd_node *)(node->parent); + } LY_CHECK_RET(json_print_node(&pctx, node)); if (!(options & LYD_PRINT_SIBLINGS)) { break; diff --git a/src/tree_data.c b/src/tree_data.c index d5a13d343..f626a03a3 100644 --- a/src/tree_data.c +++ b/src/tree_data.c @@ -53,6 +53,8 @@ #include "xml.h" #include "xpath.h" +static int lyd_insert_has_keys(const struct lyd_node *list); + static LY_ERR lyd_compare_siblings_(const struct lyd_node *node1, const struct lyd_node *node2, uint32_t options, ly_bool parental_schemas_checked); @@ -130,7 +132,7 @@ lyd_parse(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, struct &subtree_sibling, &lydctx); break; case LYD_JSON: - r = lyd_parse_json(ctx, ext, parent, first_p, in, parse_opts, val_opts, int_opts, &parsed, + r = lyd_parse_json(ctx, ext, parent, NULL, first_p, in, parse_opts, val_opts, int_opts, &parsed, &subtree_sibling, &lydctx); break; case LYD_LYB: @@ -279,6 +281,103 @@ lyd_parse_data_path(const struct ly_ctx *ctx, const char *path, LYD_FORMAT forma return ret; } +LIBYANG_API_DEF LY_ERR +lyd_parse_value_fragment(const struct ly_ctx *ctx, const char *path, struct ly_in *in, LYD_FORMAT format, + uint32_t new_val_options, uint32_t parse_options, uint32_t validate_options, struct lyd_node **tree) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxp_expr *exp = NULL; + struct ly_path *p = NULL; + struct lyd_node *new_last_parent = NULL, *new_top_parent = NULL; + const struct lysc_node *new_node_schema = NULL; + ly_bool p_decremented = 0; + struct lyd_node *iter = NULL, *parent = NULL; + const char *key_value = NULL; + const char *path_key_value = NULL; + + LY_CHECK_ARG_RET(ctx, ctx, path && (path[0] == '/'), LY_EINVAL); + + /* other formats are not supported for now */ + if (format != LYD_JSON) { + LOGARG(ctx, "invalid format (only JSON supported)"); + return LY_EINVAL; + } + + /* parse path */ + LY_CHECK_GOTO(ret = ly_path_parse(ctx, NULL, path, 0, 0, LY_PATH_BEGIN_EITHER, LY_PATH_PREFIX_FIRST, + LY_PATH_PRED_SIMPLE, &exp), cleanup); + + /* compile path */ + LY_CHECK_GOTO(ret = ly_path_compile(ctx, NULL, NULL, NULL, exp, new_val_options & LYD_NEW_VAL_OUTPUT ? + LY_PATH_OPER_OUTPUT : LY_PATH_OPER_INPUT, LY_PATH_TARGET_MANY, 0, LY_VALUE_JSON, NULL, &p), cleanup); + + /* has to have a schema */ + new_node_schema = p[LY_ARRAY_COUNT(p) - 1].node; + + /* only the term nodes get their path shortened */ + if (new_node_schema->nodetype & LYD_NODE_TERM) { + /* shorten the ly_path by one element (to avoid a leaflist without predicate at the end) */ + LY_ARRAY_DECREMENT(p); + p_decremented = 1; + } + + if (LY_ARRAY_COUNT(p)) { + /* create nodes */ + LY_CHECK_GOTO(ret = lyd_new_path_create(NULL, ctx, NULL, p, path, NULL, 0, 0, new_val_options, &new_top_parent, + &new_last_parent), cleanup); + } + + /* parse the json value */ + LY_CHECK_GOTO(ret = lyd_parse_json(ctx, NULL, new_last_parent, new_node_schema, new_last_parent ? NULL : &new_top_parent, + in, parse_options, validate_options, 0, NULL, NULL, NULL), cleanup); + + /* when setting keys they have to have a correct value (same as in the path) */ + if (lysc_is_key(new_node_schema)) { + LY_LIST_FOR(lyd_child(new_last_parent), iter) { + /* look for the same schema as is in the path */ + if (!strcmp(iter->schema->name, new_node_schema->name)) { + key_value = lyd_get_value(iter); + if (!path_key_value) { + path_key_value = key_value; + } else { + LY_CHECK_ERR_GOTO(strcmp(key_value, path_key_value), LOGVAL(ctx, LYVE_DATA, + "Path [%s] contains a different key [%s] than data [%s]", path, path_key_value, key_value); + ret = LY_EINVAL, cleanup); + + /* when unlinking duplicate key we need to recalculate the list hash, + so store the list (parent) and recalculate the hash after unlinking the duplicate key */ + parent = lyd_parent(iter); + assert(parent); + + lyd_unlink(iter); + lyd_free_tree(iter); + + lyd_unlink_hash(parent); + lyd_hash(parent); + lyd_insert_hash(parent); + break; + } + } + } + } + + /* set output tree */ + if (tree) { + *tree = new_top_parent; + } + +cleanup: + lyxp_expr_free(exp); + if (p_decremented) { + LY_ARRAY_INCREMENT(p); + } + ly_path_free(p); + if (ret) { + lyd_free_all(new_top_parent); + } + return ret; +} + /** * @brief Parse YANG data into an operation data tree, in case the extension instance is specified, keep the searching * for schema nodes locked inside the extension instance. @@ -401,7 +500,7 @@ lyd_parse_op_(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, str rc = lyd_parse_xml(ctx, ext, parent, &first, in, parse_opts, val_opts, int_opts, &parsed, NULL, &lydctx); break; case LYD_JSON: - rc = lyd_parse_json(ctx, ext, parent, &first, in, parse_opts, val_opts, int_opts, &parsed, NULL, &lydctx); + rc = lyd_parse_json(ctx, ext, parent, NULL, &first, in, parse_opts, val_opts, int_opts, &parsed, NULL, &lydctx); break; case LYD_LYB: rc = lyd_parse_lyb(ctx, ext, parent, &first, in, parse_opts, val_opts, int_opts, &parsed, NULL, &lydctx); diff --git a/src/tree_data_internal.h b/src/tree_data_internal.h index b4d24d678..46248f808 100644 --- a/src/tree_data_internal.h +++ b/src/tree_data_internal.h @@ -357,6 +357,39 @@ LY_ERR lyd_create_opaq(const struct ly_ctx *ctx, const char *name, uint32_t name */ LY_ERR lyd_change_term_val(struct lyd_node *term, struct lyd_value *val, ly_bool use_val, ly_bool is_dflt); +/** + * @brief Create a new node in the data tree based on a ly_path structure @p. All node types can be created. + * + * If @p path points to a list key, the key value from the predicate is used and @p value is ignored. + * Also, if a leaf-list is being created and both a predicate is defined in @p path + * and @p value is set, the predicate is preferred. + * + * For key-less lists and state leaf-lists, positional predicates can be used. If no preciate is used for these + * nodes, they are always created. + * + * @param[in] parent Data parent to add to/modify, can be NULL. Note that in case a first top-level sibling is used, + * it may no longer be first if @p path is absolute and starts with a non-existing top-level node inserted + * before @p parent. Use ::lyd_first_sibling() to adjust @p parent in these cases. + * @param[in] ctx libyang context, must be set if @p parent is NULL. + * @param[in] ext Extension instance where the node being created is defined. This argument takes effect only for absolute + * path or when the relative paths touches document root (top-level). In such cases the present extension instance replaces + * searching for the appropriate module. + * @param[in] p Compiled path. + * @param[in] path [Path](@ref howtoXPath) to create. + * @param[in] value Value of the new leaf/leaf-list (const char *) in ::LY_VALUE_JSON format. If creating an + * anyxml/anydata node, the expected type depends on @p value_type. For other node types, it should be NULL. + * @param[in] value_size_bits Size of @p value in bits, must be set correctly. Ignored when + * creating anyxml/anydata nodes. + * @param[in] value_type Anyxml/anydata node @p value type. + * @param[in] options Bitmask of new value creation options, see @ref newvaloptions. + * @param[out] new_parent Optional first parent node created. If only one node was created, equals to @p new_node. + * @param[out] new_node Optional last node created. + * @return LY_ERR value. + */ +LY_ERR lyd_new_path_create(struct lyd_node *parent, const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, + struct ly_path *p, const char *path, const void *value, uint32_t value_size_bits, LYD_ANYDATA_VALUETYPE value_type, + uint32_t options, struct lyd_node **new_parent, struct lyd_node **new_node); + /** * @brief Check the existence and create any non-existing implicit children. * diff --git a/src/tree_data_new.c b/src/tree_data_new.c index 013655655..95673f136 100644 --- a/src/tree_data_new.c +++ b/src/tree_data_new.c @@ -245,7 +245,7 @@ lyd_create_any_datatree(const struct ly_ctx *ctx, struct ly_in *value_in, LYD_AN rc = lyd_parse_xml(ctx, NULL, NULL, tree, value_in, parse_opts, 0, int_opts, NULL, NULL, &lydctx); break; case LYD_ANYDATA_JSON: - rc = lyd_parse_json(ctx, NULL, NULL, tree, value_in, parse_opts, 0, int_opts, NULL, NULL, &lydctx); + rc = lyd_parse_json(ctx, NULL, NULL, NULL, tree, value_in, parse_opts, 0, int_opts, NULL, NULL, &lydctx); break; } if (lydctx) { @@ -1601,42 +1601,12 @@ lyd_new_path_check_find_lypath(struct ly_path *path, const struct lysc_ext_insta return LY_SUCCESS; } -/** - * @brief Create a new node in the data tree based on a path. All node types can be created. - * - * If @p path points to a list key, the key value from the predicate is used and @p value is ignored. - * Also, if a leaf-list is being created and both a predicate is defined in @p path - * and @p value is set, the predicate is preferred. - * - * For key-less lists and state leaf-lists, positional predicates can be used. If no preciate is used for these - * nodes, they are always created. - * - * @param[in] parent Data parent to add to/modify, can be NULL. Note that in case a first top-level sibling is used, - * it may no longer be first if @p path is absolute and starts with a non-existing top-level node inserted - * before @p parent. Use ::lyd_first_sibling() to adjust @p parent in these cases. - * @param[in] ctx libyang context, must be set if @p parent is NULL. - * @param[in] ext Extension instance where the node being created is defined. This argument takes effect only for absolute - * path or when the relative paths touches document root (top-level). In such cases the present extension instance replaces - * searching for the appropriate module. - * @param[in] path [Path](@ref howtoXPath) to create. - * @param[in] value Value of the new leaf/leaf-list. If creating an anyxml/anydata node, the expected type depends on - * @p value_type. For other node types, it should be NULL. - * @param[in] value_size_bits Size of @p value in bits, must be set correctly. Ignored when - * creating anyxml/anydata nodes. - * @param[in] value_type Anyxml/anydata node @p value type. - * @param[in] options Bitmask of options, see @ref pathoptions. - * @param[out] new_parent Optional first parent node created. If only one node was created, equals to @p new_node. - * @param[out] new_node Optional last node created. - * @return LY_ERR value. - */ -static LY_ERR -lyd_new_path_(struct lyd_node *parent, const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, const char *path, - const void *value, uint32_t value_size_bits, LYD_ANYDATA_VALUETYPE value_type, uint32_t options, - struct lyd_node **new_parent, struct lyd_node **new_node) +LY_ERR +lyd_new_path_create(struct lyd_node *parent, const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, + struct ly_path *p, const char *path, const void *value, uint32_t value_size_bits, LYD_ANYDATA_VALUETYPE value_type, + uint32_t options, struct lyd_node **new_parent, struct lyd_node **new_node) { LY_ERR ret = LY_SUCCESS, r; - struct lyxp_expr *exp = NULL; - struct ly_path *p = NULL; struct lyd_node *nparent = NULL, *nnode = NULL, *node = NULL, *cur_parent, *iter; const struct lysc_node *schema; const char *val = NULL; @@ -1646,23 +1616,9 @@ lyd_new_path_(struct lyd_node *parent, const struct ly_ctx *ctx, const struct ly LY_VALUE_FORMAT format; uint32_t value_size, hints, count; - assert(parent || ctx); - assert(path && ((path[0] == '/') || parent)); - - if (!ctx) { - ctx = LYD_CTX(parent); - } LY_CHECK_GOTO(ret = lyd_new_val_get_format(options, &format), cleanup); value_size = LYPLG_BITS2BYTES(value_size_bits); - /* parse path */ - LY_CHECK_GOTO(ret = ly_path_parse(ctx, NULL, path, 0, 0, LY_PATH_BEGIN_EITHER, LY_PATH_PREFIX_FIRST, - LY_PATH_PRED_SIMPLE, &exp), cleanup); - - /* compile path */ - LY_CHECK_GOTO(ret = ly_path_compile(ctx, NULL, lyd_node_schema(parent), ext, exp, options & LYD_NEW_VAL_OUTPUT ? - LY_PATH_OPER_OUTPUT : LY_PATH_OPER_INPUT, LY_PATH_TARGET_MANY, 0, LY_VALUE_JSON, NULL, &p), cleanup); - /* check the compiled path before searching existing nodes, it may be shortened */ orig_count = LY_ARRAY_COUNT(p); LY_CHECK_GOTO(ret = lyd_new_path_check_find_lypath(p, ext, path, value, value_size_bits, format, options), cleanup); @@ -1856,13 +1812,11 @@ lyd_new_path_(struct lyd_node *parent, const struct ly_ctx *ctx, const struct ly } cleanup: - lyxp_expr_free(exp); if (p) { while (orig_count > LY_ARRAY_COUNT(p)) { LY_ARRAY_INCREMENT(p); } } - ly_path_free(p); if (!ret) { /* set out params only on success */ if (new_parent) { @@ -1877,6 +1831,68 @@ lyd_new_path_(struct lyd_node *parent, const struct ly_ctx *ctx, const struct ly return ret; } +/** + * @brief Create a new node in the data tree based on a path. All node types can be created. + * + * If @p path points to a list key, the key value from the predicate is used and @p value is ignored. + * Also, if a leaf-list is being created and both a predicate is defined in @p path + * and @p value is set, the predicate is preferred. + * + * For key-less lists and state leaf-lists, positional predicates can be used. If no preciate is used for these + * nodes, they are always created. + * + * @param[in] parent Data parent to add to/modify, can be NULL. Note that in case a first top-level sibling is used, + * it may no longer be first if @p path is absolute and starts with a non-existing top-level node inserted + * before @p parent. Use ::lyd_first_sibling() to adjust @p parent in these cases. + * @param[in] ctx libyang context, must be set if @p parent is NULL. + * @param[in] ext Extension instance where the node being created is defined. This argument takes effect only for absolute + * path or when the relative paths touches document root (top-level). In such cases the present extension instance replaces + * searching for the appropriate module. + * @param[in] path [Path](@ref howtoXPath) to create. + * @param[in] value Value of the new leaf/leaf-list (const char *) in ::LY_VALUE_JSON format. If creating an + * anyxml/anydata node, the expected type depends on @p value_type. For other node types, it should be NULL. + * @param[in] value_size_bits Size of @p value in bits, must be set correctly. Ignored when + * creating anyxml/anydata nodes. + * @param[in] value_type Anyxml/anydata node @p value type. + * @param[in] options Bitmask of options, see @ref pathoptions. + * @param[out] new_parent Optional first parent node created. If only one node was created, equals to @p new_node. + * @param[out] new_node Optional last node created. + * @return LY_ERR value. + */ +static LY_ERR +lyd_new_path_(struct lyd_node *parent, const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, const char *path, + const void *value, uint32_t value_size_bits, LYD_ANYDATA_VALUETYPE value_type, uint32_t options, + struct lyd_node **new_parent, struct lyd_node **new_node) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxp_expr *exp = NULL; + struct ly_path *p = NULL; + + assert(parent || ctx); + assert(path && ((path[0] == '/') || parent)); + + if (!ctx) { + ctx = LYD_CTX(parent); + } + + /* parse path */ + LY_CHECK_GOTO(ret = ly_path_parse(ctx, NULL, path, 0, 0, LY_PATH_BEGIN_EITHER, LY_PATH_PREFIX_FIRST, + LY_PATH_PRED_SIMPLE, &exp), cleanup); + + /* compile path */ + LY_CHECK_GOTO(ret = ly_path_compile(ctx, NULL, lyd_node_schema(parent), ext, exp, options & LYD_NEW_VAL_OUTPUT ? + LY_PATH_OPER_OUTPUT : LY_PATH_OPER_INPUT, LY_PATH_TARGET_MANY, 0, LY_VALUE_JSON, NULL, &p), cleanup); + + /* create nodes */ + LY_CHECK_GOTO(ret = lyd_new_path_create(parent, ctx, ext, p, path, value, value_size_bits, value_type, options, + new_parent, new_node), cleanup); + +cleanup: + lyxp_expr_free(exp); + ly_path_free(p); + return ret; +} + LIBYANG_API_DEF LY_ERR lyd_new_path(struct lyd_node *parent, const struct ly_ctx *ctx, const char *path, const char *value, uint32_t options, struct lyd_node **node) diff --git a/tests/utests/data/test_parser_json.c b/tests/utests/data/test_parser_json.c index 132215636..bfec8f3ed 100644 --- a/tests/utests/data/test_parser_json.c +++ b/tests/utests/data/test_parser_json.c @@ -36,6 +36,7 @@ setup(void **state) "leaf foo { type string;}" "container c {" " leaf x {type string;}" + " leaf-list y { type uint8; }" " action act { input { leaf al {type string;} } output { leaf al {type uint8;} } }" " notification n1 { leaf nl {type string;} }" "}" @@ -43,6 +44,7 @@ setup(void **state) "anydata any {config false;}" "anyxml axml;" "leaf-list ll1 { type uint8; }" + "leaf-list ll2 { type string; }" "leaf foo2 { type string; default \"default-val\"; }" "leaf foo3 { type uint32; }" "leaf foo4 { type uint64; }" @@ -69,6 +71,62 @@ setup(void **state) #define CHECK_LYD_STRING(IN_MODEL, PRINT_OPTION, TEXT) \ CHECK_LYD_STRING_PARAM(IN_MODEL, TEXT, LYD_JSON, PRINT_OPTION) +static void +test_baretop_leaf(void **state) +{ + struct lyd_node *tree; + struct ly_in *in; + int ret, i; + + /* please bear in mind that a leaflist always has to have an array value (bare value without square brackets is not + * allowed according to the RFC7951)*/ + char *xpath[] = {"/a:foo", "/a:ll2", "/a:ll1", "/a:c", "/a:l1[a=\"mi\"][b=\"ni\"][c=12]/b"}; + char *data[] = {"\"foo value\"", "[\"abc\", \"def\", \"ghi\"]", "[1, 2, 3]", "{\"x\":\"foo value\"}", "\"ni\""}; + char *exp_str_sib[] = { + "{\"a:foo\":\"foo value\"}", + "{\"a:ll2\":[\"abc\",\"def\",\"ghi\"]}", + "{\"a:ll1\":[1,2,3]}", + "{\"a:c\":{\"x\":\"foo value\"}}", + "{\"a:l1\":[{\"a\":\"mi\",\"b\":\"ni\",\"c\":12}]}", + }; + + for (i = 0; i < 5; i++) { + tree = NULL; + + if ((ret = ly_in_new_memory(data[i], &in))) { + fail_msg("Print err 0x%d; MSG: %s", ret, ly_err_last(UTEST_LYCTX)->msg); + } + + if ((ret = lyd_parse_value_fragment(UTEST_LYCTX, xpath[i], in, LYD_JSON, 0, LYD_PARSE_JSON_NULL, 0, &tree))) { + fail_msg("Print err 0x%d; MSG: %s", ret, ly_err_last(UTEST_LYCTX)->msg); + } + + CHECK_LYD_STRING(tree, LYD_PRINT_SHRINK | LYD_PRINT_SIBLINGS, exp_str_sib[i]); + lyd_free_all(tree); + ly_in_free(in, 0); + } + + tree = NULL; + + if ((ret = ly_in_new_memory("\"nx\"", &in))) { + fail_msg("Print err 0x%d; MSG: %s", ret, ly_err_last(UTEST_LYCTX)->msg); + } + + ret = lyd_parse_value_fragment(UTEST_LYCTX, "/a:l1[a=\"mi\"][b=\"ni\"][c=12]/b", in, LYD_JSON, 0, LYD_PARSE_JSON_NULL, + 0, &tree); + if (ret != LY_EINVAL) { + fail_msg("Print err 0x%d; MSG: %s", ret, ly_err_last(UTEST_LYCTX)->msg); + } + if (strcmp(ly_err_last(UTEST_LYCTX)->msg, "Path [/a:l1[a=\"mi\"][b=\"ni\"][c=12]/b] contains a different key [ni] than data [nx]")) { + fail_msg("Print err 0x%d; MSG: %s :differs from: " + "Path [/a:l1[a=\"mi\"][b=\"ni\"][c=12]/b] contains a different key [ni] than data [nx]", ret, ly_err_last(UTEST_LYCTX)->msg); + } + + ly_err_clean(UTEST_LYCTX, NULL); + lyd_free_all(tree); + ly_in_free(in, 0); +} + static void test_leaf(void **state) { @@ -969,6 +1027,7 @@ int main(void) { const struct CMUnitTest tests[] = { + UTEST(test_baretop_leaf, setup), UTEST(test_leaf, setup), UTEST(test_leaflist, setup), UTEST(test_anydata, setup), diff --git a/tests/utests/data/test_printer_json.c b/tests/utests/data/test_printer_json.c index 90a343e74..2f648019b 100644 --- a/tests/utests/data/test_printer_json.c +++ b/tests/utests/data/test_printer_json.c @@ -135,6 +135,27 @@ test_empty_leaf_list(void **state) lyd_free_all(tree); } +static void +test_no_json_nested_prefix(void **state) +{ + struct lyd_node *tree; + char *buffer = NULL; + const char *data = "{\"schema2:a\":{\"b\":{\"c\":\"dflt\"}}}"; + + CHECK_PARSE_LYD_PARAM(data, LYD_JSON, 0, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree); + assert_int_equal(LY_SUCCESS, lyd_print_mem(&buffer, tree, LYD_JSON, LYD_PRINT_SHRINK | LYD_PRINT_WD_ALL | + LYD_PRINT_JSON_NO_NESTED_PREFIX)); + CHECK_STRING(buffer, "{\"schema2:a\":{\"b\":{\"c\":\"dflt\"}}}"); + free(buffer); + + assert_int_equal(LY_SUCCESS, lyd_print_mem(&buffer, lyd_child(tree), LYD_JSON, LYD_PRINT_SHRINK | LYD_PRINT_WD_ALL | + LYD_PRINT_JSON_NO_NESTED_PREFIX)); + CHECK_STRING(buffer, "{\"b\":{\"c\":\"dflt\"}}"); + free(buffer); + + lyd_free_all(tree); +} + int main(void) { @@ -142,6 +163,7 @@ main(void) UTEST(test_container_presence, setup), UTEST(test_empty_container_wd_trim, setup), UTEST(test_empty_leaf_list, setup), + UTEST(test_no_json_nested_prefix, setup), }; return cmocka_run_group_tests(tests, NULL, NULL);