diff --git a/src/path.c b/src/path.c index 74d4fe1f9..00cdf1f76 100644 --- a/src/path.c +++ b/src/path.c @@ -1020,6 +1020,95 @@ ly_path_append(const struct ly_ctx *ctx, const struct ly_path *src, struct ly_pa return ret; } +/** + * @brief Compile deref XPath function into ly_path structure. + * + * @param[in] ctx libyang context. + * @param[in] cur_node Current (original context) node. + * @param[in] target_node The deref target schema node. + * @param[in] cur_type The currently evaluated type of deref target node. + * @param[in] top_ext Extension instance containing the definition of the data being created. It is used to find + * the top-level node inside the extension instance instead of a module. Note that this is the case not only if + * the @p ctx_node is NULL, but also if the relative path starting in @p ctx_node reaches the document root + * via double dots. + * @param[in] expr Parsed path. + * @param[in] oper Oper option (@ref path_oper_options). + * @param[in] target Target option (@ref path_target_options). + * @param[in] format Format of the path. + * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix). + * @param[in] log Whether to generate log message or not + * @param[in,out] tok_idx Index in @p exp, is adjusted. + * @param[out] path Compiled path. + * @return LY_ERR value. + */ +static LY_ERR +ly_path_compile_deref_type(const struct ly_ctx *ctx, const struct lysc_node *cur_node, const struct lysc_node *target_node, + const struct lysc_type *cur_type, const struct lysc_ext_instance *top_ext, const struct lyxp_expr *expr, uint16_t oper, uint16_t target, + LY_VALUE_FORMAT format, void *prefix_data, ly_bool log, uint32_t *tok_idx, struct ly_path **path) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxp_expr expr2; + struct ly_path *path2 = NULL; + const struct lysc_type_union *union_type; + const struct lysc_type_leafref *lref; + uint32_t cur_tok_idx = *tok_idx; + LY_ARRAY_COUNT_TYPE u; + + if (cur_type->basetype == LY_TYPE_UNION) { + union_type = (const struct lysc_type_union *)cur_type; + ret = LY_EVALID; + LY_ARRAY_FOR(union_type->types, u) { + *tok_idx = cur_tok_idx; + if (ly_path_compile_deref_type(ctx, cur_node, target_node, union_type->types[u], top_ext, expr, oper, target, format, prefix_data, 0, tok_idx, path) == LY_SUCCESS) { + ret = LY_SUCCESS; + } + } + if (log && ret) { + LOGVAL_PATH(ctx, cur_node, target_node, LYVE_XPATH, "Deref function target node \"%s\" is union type with no leafrefs.", target_node->name); + } + goto cleanup; + } else if (cur_type->basetype != LY_TYPE_LEAFREF) { + if (log) { + LOGVAL_PATH(ctx, cur_node, target_node, LYVE_XPATH, "Deref function target node \"%s\" is not leafref.", target_node->name); + } + ret = LY_EVALID; + goto cleanup; + } + lref = (const struct lysc_type_leafref *)cur_type; + + /* compile dereferenced leafref expression and append it to the path */ + LY_CHECK_GOTO(ret = ly_path_compile_leafref(ctx, target_node, top_ext, lref->path, oper, target, format, prefix_data, + &path2), cleanup); + target_node = path2[LY_ARRAY_COUNT(path2) - 1].node; + LY_CHECK_GOTO(ret = ly_path_append(ctx, path2, path), cleanup); + ly_path_free(path2); + path2 = NULL; + + /* properly parsed path must always continue with ')' and '/' */ + assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_PAR2)); + (*tok_idx)++; + assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_OPER_PATH)); + (*tok_idx)++; + + /* prepare expr representing rest of the path after deref */ + expr2.tokens = &expr->tokens[*tok_idx]; + expr2.tok_pos = &expr->tok_pos[*tok_idx]; + expr2.tok_len = &expr->tok_len[*tok_idx]; + expr2.repeat = &expr->repeat[*tok_idx]; + expr2.used = expr->used - *tok_idx; + expr2.size = expr->size - *tok_idx; + expr2.expr = expr->expr; + + /* compile rest of the path and append it to the path */ + LY_CHECK_GOTO(ret = ly_path_compile_leafref(ctx, target_node, top_ext, &expr2, oper, target, format, prefix_data, &path2), + cleanup); + LY_CHECK_GOTO(ret = ly_path_append(ctx, path2, path), cleanup); + +cleanup: + ly_path_free(path2); + return ret; +} + /** * @brief Compile deref XPath function into ly_path structure. * @@ -1049,7 +1138,6 @@ ly_path_compile_deref(const struct ly_ctx *ctx, const struct lysc_node *cur_node struct ly_path *path2 = NULL; const struct lysc_node *node2; const struct lysc_node_leaf *deref_leaf_node; - const struct lysc_type_leafref *lref; uint32_t begin_token; *path = NULL; @@ -1083,50 +1171,22 @@ ly_path_compile_deref(const struct ly_ctx *ctx, const struct lysc_node *cur_node LY_CHECK_GOTO(ret = ly_path_compile_leafref(ctx, ctx_node, top_ext, &expr2, oper, target, format, prefix_data, &path2), cleanup); node2 = path2[LY_ARRAY_COUNT(path2) - 1].node; - if ((node2->nodetype != LYS_LEAF) && (node2->nodetype != LYS_LEAFLIST)) { - LOGVAL_PATH(ctx, cur_node, node2, LYVE_XPATH, "Deref function target node \"%s\" is not leaf nor leaflist.", + if (node2 == ctx_node) { + LOGVAL_PATH(ctx, cur_node, node2, LYVE_XPATH, "Deref function target node \"%s\" is node itself.", node2->name); ret = LY_EVALID; goto cleanup; } - deref_leaf_node = (const struct lysc_node_leaf *)node2; - if (deref_leaf_node->type->basetype != LY_TYPE_LEAFREF) { - LOGVAL_PATH(ctx, cur_node, node2, LYVE_XPATH, "Deref function target node \"%s\" is not leafref.", node2->name); + if ((node2->nodetype != LYS_LEAF) && (node2->nodetype != LYS_LEAFLIST)) { + LOGVAL_PATH(ctx, cur_node, node2, LYVE_XPATH, "Deref function target node \"%s\" is not leaf nor leaflist.", + node2->name); ret = LY_EVALID; goto cleanup; } - lref = (const struct lysc_type_leafref *)deref_leaf_node->type; - LY_CHECK_GOTO(ret = ly_path_append(ctx, path2, path), cleanup); - ly_path_free(path2); - path2 = NULL; - /* compile dereferenced leafref expression and append it to the path */ - LY_CHECK_GOTO(ret = ly_path_compile_leafref(ctx, node2, top_ext, lref->path, oper, target, format, prefix_data, - &path2), cleanup); - node2 = path2[LY_ARRAY_COUNT(path2) - 1].node; - LY_CHECK_GOTO(ret = ly_path_append(ctx, path2, path), cleanup); - ly_path_free(path2); - path2 = NULL; - - /* properly parsed path must always continue with ')' and '/' */ - assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_PAR2)); - (*tok_idx)++; - assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_OPER_PATH)); - (*tok_idx)++; - - /* prepare expr representing rest of the path after deref */ - expr2.tokens = &expr->tokens[*tok_idx]; - expr2.tok_pos = &expr->tok_pos[*tok_idx]; - expr2.tok_len = &expr->tok_len[*tok_idx]; - expr2.repeat = &expr->repeat[*tok_idx]; - expr2.used = expr->used - *tok_idx; - expr2.size = expr->size - *tok_idx; - expr2.expr = expr->expr; - - /* compile rest of the path and append it to the path */ - LY_CHECK_GOTO(ret = ly_path_compile_leafref(ctx, node2, top_ext, &expr2, oper, target, format, prefix_data, &path2), - cleanup); LY_CHECK_GOTO(ret = ly_path_append(ctx, path2, path), cleanup); + deref_leaf_node = (const struct lysc_node_leaf *)node2; + ret = ly_path_compile_deref_type(ctx, cur_node, node2, deref_leaf_node->type, top_ext, expr, oper, target, format, prefix_data, 1, tok_idx, path); cleanup: ly_path_free(path2); diff --git a/src/xpath.c b/src/xpath.c index b2c69cd5e..5c3a26937 100644 --- a/src/xpath.c +++ b/src/xpath.c @@ -3980,6 +3980,83 @@ xpath_current(struct lyxp_set **args, uint32_t arg_count, struct lyxp_set *set, return LY_SUCCESS; } +/** + * @brief Executes deref function on specific type and value. It performs evaluation in recursive manner, + * which supports usage of union types as deref targets. Returns LYXP_SET_NODE_SET with either + * leafref or instance-identifier target node(s). + * + * @param[in] leaf The target deref data node + * @param[in] sleaf The target deref schema node + * @param[in] value The currently evaluated value of target deref node depending on current type + * @param[in] cur_type The currently evaluated type of target deref node + * @param[in] log Whether to generate log message or not + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_deref_type(struct lyd_node_term *leaf, struct lysc_node_leaf *sleaf, struct lyd_value *value, struct lysc_type *cur_type, ly_bool log, struct lyxp_set *set) +{ + LY_ERR r; + LY_ERR ret = LY_SUCCESS; + char *errmsg = NULL; + struct lyd_node *node; + struct ly_set *targets = NULL; + uint32_t i; + const struct lysc_type_union *union_type; + LY_ARRAY_COUNT_TYPE u; + + if (sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST)) { + if (cur_type->basetype == LY_TYPE_LEAFREF) { + /* find leafref target */ + r = lyplg_type_resolve_leafref((struct lysc_type_leafref *)cur_type, &leaf->node, value, set->tree, + set->ext, &targets, &errmsg); + if (r) { + if (log) { + LOGERR(set->ctx, LY_EINVAL, "%s", errmsg); + } + free(errmsg); + ret = LY_EINVAL; + goto cleanup; + } + + /* insert nodes into set */ + for (i = 0; i < targets->count; ++i) { + set_insert_node(set, targets->dnodes[i], 0, LYXP_NODE_ELEM, 0); + } + } else if (cur_type->basetype == LY_TYPE_INST) { + if (ly_path_eval(value->target, set->tree, NULL, set->ext, &node)) { + if (log) { + LOGERR(set->ctx, LY_EINVAL, "Invalid instance-identifier \"%s\" value - required instance not found.", + lyd_get_value(&leaf->node)); + } + ret = LY_EINVAL; + goto cleanup; + } + + /* insert it */ + set_insert_node(set, node, 0, LYXP_NODE_ELEM, 0); + } else if (cur_type->basetype == LY_TYPE_UNION) { + union_type = (const struct lysc_type_union *)cur_type; + ret = LY_EINVAL; + LY_ARRAY_FOR(union_type->types, u) { + if (!xpath_deref_type(leaf, sleaf, &value->subvalue->value, union_type->types[u], 0, set)) { + ret = LY_SUCCESS; + goto cleanup; + } + } + if (log) { + LOGERR(set->ctx, LY_EINVAL, "Invalid leafref or instance-identifier \"%s\" value - required instance not found.", + lyd_get_value(&leaf->node)); + } + } + } + +cleanup: + ly_set_free(targets, NULL); + return ret; +} + /** * @brief Execute the YANG 1.1 deref(node-set) function. Returns LYXP_SET_NODE_SET with either * leafref or instance-identifier target node(s). @@ -3998,13 +4075,9 @@ xpath_deref(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set struct lysc_type_leafref *lref; const struct lysc_node *target; struct ly_path *p; - struct lyd_node *node; - char *errmsg = NULL; uint8_t oper; LY_ERR r; LY_ERR ret = LY_SUCCESS; - struct ly_set *targets = NULL; - uint32_t i; if (options & LYXP_SCNODE_ALL) { if (args[0]->type != LYXP_SET_SCNODE_SET) { @@ -4050,39 +4123,10 @@ xpath_deref(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set if (args[0]->used) { leaf = (struct lyd_node_term *)args[0]->val.nodes[0].node; sleaf = (struct lysc_node_leaf *)leaf->schema; - if (sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST)) { - if (sleaf->type->basetype == LY_TYPE_LEAFREF) { - /* find leafref target */ - r = lyplg_type_resolve_leafref((struct lysc_type_leafref *)sleaf->type, &leaf->node, &leaf->value, set->tree, - set->ext, &targets, &errmsg); - if (r) { - LOGERR(set->ctx, LY_EINVAL, "%s", errmsg); - free(errmsg); - ret = LY_EINVAL; - goto cleanup; - } - - /* insert nodes into set */ - for (i = 0; i < targets->count; ++i) { - set_insert_node(set, targets->dnodes[i], 0, LYXP_NODE_ELEM, 0); - } - } else { - assert(sleaf->type->basetype == LY_TYPE_INST); - if (ly_path_eval(leaf->value.target, set->tree, NULL, set->ext, &node)) { - LOGERR(set->ctx, LY_EINVAL, "Invalid instance-identifier \"%s\" value - required instance not found.", - lyd_get_value(&leaf->node)); - ret = LY_EINVAL; - goto cleanup; - } - - /* insert it */ - set_insert_node(set, node, 0, LYXP_NODE_ELEM, 0); - } - } + ret = xpath_deref_type(leaf, sleaf, &leaf->value, sleaf->type, 1, set); } cleanup: - ly_set_free(targets, NULL); return ret; } diff --git a/tests/utests/types/leafref.c b/tests/utests/types/leafref.c index af782873d..ff332bbef 100644 --- a/tests/utests/types/leafref.c +++ b/tests/utests/types/leafref.c @@ -278,10 +278,45 @@ test_data_xpath_json(void **state) lyd_free_all(tree); } +static void +test_data_xpath_deref_union(void **state) +{ + const char *schema, *data; + struct lyd_node *tree; + + ly_ctx_set_options(UTEST_LYCTX, LY_CTX_LEAFREF_EXTENDED); + + /* json xpath test */ + schema = MODULE_CREATE_YANG("xp_test", + "list l1 {key k; leaf k {type string;} leaf v {type string;}}" + "list l2 {key k; leaf k {type uint8;} leaf v {type string;}}" + "leaf ref1 {type union {type leafref {path \"../l1/k\";} type leafref {path \"../l2/k\";}}}" + "leaf ref2 {type leafref {path \"deref(../ref1)/../v\";}}" + "leaf ref3 {type union {type leafref {path \"../l1/k\";} type leafref {path \"../l2/k\";}}}" + "leaf ref4 {type leafref {path \"deref(../ref3)/../v\";}}" + "leaf ref5 {type union {type leafref {path \"../l1/k\";} type string;}}" + "leaf ref6 {type leafref {path \"deref(../ref5)/../v\";}}"); + + UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, NULL); + + data = "{" + " \"xp_test:l1\": [{\"k\": \"key1\", \"v\": \"value1\"}, {\"k\": \"key2\", \"v\": \"value2\"}]," + " \"xp_test:l2\": [{\"k\": 1, \"v\": \"value3\" }, {\"k\": 2, \"v\": \"value4\"}]," + " \"xp_test:ref1\": \"key2\"," + " \"xp_test:ref2\": \"value2\"," + " \"xp_test:ref3\": 2," + " \"xp_test:ref4\": \"value4\"," + " \"xp_test:ref5\": \"key2\"," + " \"xp_test:ref6\": \"value2\"" + "}"; + CHECK_PARSE_LYD_PARAM(data, LYD_JSON, 0, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree); + lyd_free_all(tree); +} + static void test_xpath_invalid_schema(void **state) { - const char *schema1, *schema2; + const char *schema1, *schema2, *schema3; ly_ctx_set_options(UTEST_LYCTX, LY_CTX_LEAFREF_EXTENDED); schema1 = MODULE_CREATE_YANG("xp_test", @@ -308,6 +343,12 @@ test_xpath_invalid_schema(void **state) UTEST_INVALID_MODULE(schema2, LYS_IN_YANG, NULL, LY_EVALID) CHECK_LOG_CTX("Deref function target node \"r1\" is not leafref.", "/xp_test:r2", 0); + + schema3 = MODULE_CREATE_YANG("xp_test", + "leaf v1 {type string;}" + "leaf r1 {type leafref {path \"deref(../r1)/../v1\";}}"); + UTEST_INVALID_MODULE(schema3, LYS_IN_YANG, NULL, LY_EVALID) + CHECK_LOG_CTX("Deref function target node \"r1\" is node itself.", "/xp_test:r1", 0); } int @@ -319,6 +360,7 @@ main(void) UTEST(test_plugin_lyb), UTEST(test_plugin_sort), UTEST(test_data_xpath_json), + UTEST(test_data_xpath_deref_union), UTEST(test_xpath_invalid_schema) };