schema compile CHANGE leaf-list support
diff --git a/src/parser_yang.c b/src/parser_yang.c
index cbac833..310acb1 100644
--- a/src/parser_yang.c
+++ b/src/parser_yang.c
@@ -2659,6 +2659,10 @@
LOGVAL_YANG(ctx, LY_VCODE_MISSTMT, "type", "leaf-list");
return LY_EVALID;
}
+ if ((llist->min) && (llist->dflts)) {
+ LOGVAL_YANG(ctx, LY_VCODE_INCHILDSTMSCOMB, "min-elements", "default", "leaf-list");
+ return LY_EVALID;
+ }
return ret;
}
diff --git a/src/tree_schema.h b/src/tree_schema.h
index bb018c3..ec4e21e 100644
--- a/src/tree_schema.h
+++ b/src/tree_schema.h
@@ -565,6 +565,7 @@
* 5 LYS_FENABLED | | | | | | | | | | | |x| | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 6 LYS_MAND_TRUE | |x|x| | |x| | | | | | | | |
+ * LYS_ORDBY_SYSTEM | | | |x|x| | | | | | | | | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 7 LYS_ORDBY_USER | | | |x|x| | | | | | | | | |
* ---------------------+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
@@ -614,6 +615,8 @@
#define LYS_SET_RANGE 0x0080 /**< type's flag for present range substatement */
#define LYS_SET_TYPE 0x0100 /**< type's flag for present type substatement */
#define LYS_SET_REQINST 0x0200 /**< type's flag for present require-instance substatement */
+
+#define LYS_FLAGS_COMPILED_MASK 0x7f /**< mask for flags that maps to the compiled structures */
/** @} */
/**
@@ -1236,6 +1239,11 @@
struct lysc_must *musts; /**< list of must restrictions ([sized array](@ref sizedarrays)) */
struct lysc_type *type; /**< type of the leaf node (mandatory) */
+ const char *units; /**< units of the leaf's type */
+ const char **dflts; /**< list of default values ([sized array](@ref sizedarrays)) */
+ uint32_t min; /**< min-elements constraint */
+ uint32_t max; /**< max-elements constraint */
+
};
struct lysc_node_list {
diff --git a/src/tree_schema_compile.c b/src/tree_schema_compile.c
index 74f8ca3..191a379 100644
--- a/src/tree_schema_compile.c
+++ b/src/tree_schema_compile.c
@@ -437,7 +437,7 @@
/* make sure that we have the parsed version (lysp_) of the imported module to import groupings or typedefs.
* The compiled version is needed only for augments, deviates and leafrefs, so they are checked (and added,
- * if needed) when these nodes are finally being instantiated and validated at the end of context compilation. */
+ * if needed) when these nodes are finally being instantiated and validated at the end of schema compilation. */
if (!imp->module->parsed) {
comp = imp->module->compiled;
/* try to get filepath from the compiled version */
@@ -2686,6 +2686,84 @@
}
/**
+ * @brief Compile parsed leaf-list node information.
+ * @param[in] ctx Compile context
+ * @param[in] node_p Parsed leaf-list node.
+ * @param[in] options Various options to modify compiler behavior, see [compile flags](@ref scflags).
+ * @param[in,out] node Pre-prepared structure from lys_compile_node() with filled generic node information
+ * is enriched with the leaf-list-specific information.
+ * @return LY_ERR value - LY_SUCCESS or LY_EVALID.
+ */
+static LY_ERR
+lys_compile_node_leaflist(struct lysc_ctx *ctx, struct lysp_node *node_p, int options, struct lysc_node *node)
+{
+ struct lysp_node_leaflist *llist_p = (struct lysp_node_leaflist*)node_p;
+ struct lysc_node_leaflist *llist = (struct lysc_node_leaflist*)node;
+ unsigned int u, v;
+ const char *dflt = NULL;
+ LY_ERR ret = LY_SUCCESS;
+
+ COMPILE_MEMBER_GOTO(ctx, llist_p->when, llist->when, options, lys_compile_when, ret, done);
+ COMPILE_ARRAY_GOTO(ctx, llist_p->iffeatures, llist->iffeatures, options, u, lys_compile_iffeature, ret, done);
+ COMPILE_ARRAY_GOTO(ctx, llist_p->musts, llist->musts, options, u, lys_compile_must, ret, done);
+ DUP_STRING(ctx->ctx, llist_p->units, llist->units);
+
+ if (llist_p->dflts) {
+ LY_ARRAY_CREATE_GOTO(ctx->ctx, llist->dflts, LY_ARRAY_SIZE(llist_p->dflts), ret, done);
+ LY_ARRAY_FOR(llist_p->dflts, u) {
+ DUP_STRING(ctx->ctx, llist_p->dflts[u], llist->dflts[u]);
+ LY_ARRAY_INCREMENT(llist->dflts);
+ }
+ }
+
+ llist->min = llist_p->min;
+ llist->max = llist_p->max ? llist_p->max : -1;
+
+ ret = lys_compile_type(ctx, node_p, node_p->flags, ctx->mod->parsed, node_p->name, &llist_p->type, options, &llist->type,
+ llist->units ? NULL : &llist->units, (llist->dflts || llist->min) ? NULL : &dflt);
+ LY_CHECK_GOTO(ret, done);
+ if (dflt) {
+ LY_ARRAY_CREATE_GOTO(ctx->ctx, llist->dflts, 1, ret, done);
+ llist->dflts[0] = dflt;
+ LY_ARRAY_INCREMENT(llist->dflts);
+ }
+
+ if (llist->type->basetype == LY_TYPE_LEAFREF) {
+ /* store to validate the path in the current context at the end of schema compiling when all the nodes are present */
+ ly_set_add(&ctx->unres, llist, 0);
+ } else if (llist->type->basetype == LY_TYPE_UNION) {
+ LY_ARRAY_FOR(((struct lysc_type_union*)llist->type)->types, u) {
+ if (((struct lysc_type_union*)llist->type)->types[u]->basetype == LY_TYPE_LEAFREF) {
+ /* store to validate the path in the current context at the end of schema compiling when all the nodes are present */
+ ly_set_add(&ctx->unres, llist, 0);
+ }
+ }
+ } else if (llist->type->basetype == LY_TYPE_EMPTY && llist_p->dflts) {
+ LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SEMANTICS,
+ "Leaf-list of type \"empty\" must not have a default value (%s).", llist_p->dflts[0]);
+ return LY_EVALID;
+ }
+
+ if ((llist->flags & LYS_CONFIG_W) && llist->dflts && LY_ARRAY_SIZE(llist->dflts)) {
+ /* configuration data values must be unique - so check the default values */
+ LY_ARRAY_FOR(llist->dflts, u) {
+ for (v = u + 1; v < LY_ARRAY_SIZE(llist->dflts); ++v) {
+ if (!strcmp(llist->dflts[u], llist->dflts[v])) {
+ LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SEMANTICS,
+ "Configuration leaf-list has multiple defaults of the same value \"%s\".", llist->dflts[v]);
+ return LY_EVALID;
+ }
+ }
+ }
+ }
+
+ /* TODO validate default value according to the type, possibly postpone the check when the leafref target is known */
+
+done:
+ return ret;
+}
+
+/**
* @brief Compile parsed schema node information.
* @param[in] ctx Compile context
* @param[in] node_p Parsed schema node.
@@ -2717,6 +2795,7 @@
break;
case LYS_LEAFLIST:
node = (struct lysc_node*)calloc(1, sizeof(struct lysc_node_leaflist));
+ node_compile_spec = lys_compile_node_leaflist;
break;
case LYS_CHOICE:
node = (struct lysc_node*)calloc(1, sizeof(struct lysc_node_choice));
@@ -2733,7 +2812,7 @@
node->nodetype = node_p->nodetype;
node->module = ctx->mod;
node->prev = node;
- node->flags = node_p->flags;
+ node->flags = node_p->flags & LYS_FLAGS_COMPILED_MASK;
/* config */
if (!(node->flags & LYS_CONFIG_MASK)) {
@@ -2876,7 +2955,7 @@
* can be also leafref, in case it is already resolved, go through the chain and check that it does not
* point to the starting leafref type). The second round stores the first non-leafref type for later data validation. */
for (u = 0; u < ctx.unres.count; ++u) {
- if (((struct lysc_node*)ctx.unres.objs[u])->nodetype == LYS_LEAF) {
+ if (((struct lysc_node*)ctx.unres.objs[u])->nodetype & (LYS_LEAF | LYS_LEAFLIST)) {
type = ((struct lysc_node_leaf*)ctx.unres.objs[u])->type;
if (type->basetype == LY_TYPE_LEAFREF) {
/* validate the path */
@@ -2895,7 +2974,7 @@
}
}
for (u = 0; u < ctx.unres.count; ++u) {
- if (((struct lysc_node*)ctx.unres.objs[u])->nodetype == LYS_LEAF) {
+ if (((struct lysc_node*)ctx.unres.objs[u])->nodetype & (LYS_LEAF | LYS_LEAFLIST)) {
type = ((struct lysc_node_leaf*)ctx.unres.objs[u])->type;
if (type->basetype == LY_TYPE_LEAFREF) {
/* store pointer to the real type */
diff --git a/src/tree_schema_free.c b/src/tree_schema_free.c
index ceec0a8..22a3b3e 100644
--- a/src/tree_schema_free.c
+++ b/src/tree_schema_free.c
@@ -620,6 +620,7 @@
lysc_node_container_free(ctx, (struct lysc_node_container*)node);
break;
case LYS_LEAF:
+ case LYS_LEAFLIST:
lysc_node_leaf_free(ctx, (struct lysc_node_leaf*)node);
break;
default:
diff --git a/tests/src/test_parser_yang.c b/tests/src/test_parser_yang.c
index d9958bb..4cb85bf 100644
--- a/tests/src/test_parser_yang.c
+++ b/tests/src/test_parser_yang.c
@@ -1347,6 +1347,7 @@
TEST_DUP("mandatory", "true", "false");
TEST_DUP("reference", "1", "2");
TEST_DUP("status", "current", "obsolete");
+ TEST_DUP("type", "int8", "uint8");
TEST_DUP("units", "text1", "text2");
TEST_DUP("when", "true", "false");
#undef TEST_DUP
@@ -1397,6 +1398,101 @@
ly_ctx_destroy(ctx.ctx, NULL);
}
+static void
+test_leaflist(void **state)
+{
+ *state = test_leaf;
+
+ struct lysp_module mod = {0};
+ struct ly_parser_ctx ctx = {0};
+ struct lysp_node_leaflist *ll = NULL;
+ const char *str;
+
+ assert_int_equal(LY_SUCCESS, ly_ctx_new(NULL, 0, &ctx.ctx));
+ assert_non_null(ctx.ctx);
+ ctx.line = 1;
+ ctx.mod = &mod;
+ ctx.mod->version = 2; /* simulate YANG 1.1 */
+
+ /* invalid cardinality */
+#define TEST_DUP(MEMBER, VALUE1, VALUE2) \
+ str = "ll {" MEMBER" "VALUE1";"MEMBER" "VALUE2";} ..."; \
+ assert_int_equal(LY_EVALID, parse_leaflist(&ctx, &str, NULL, (struct lysp_node**)&ll)); \
+ logbuf_assert("Duplicate keyword \""MEMBER"\". Line number 1."); \
+ lysp_node_free(ctx.ctx, (struct lysp_node*)ll); ll = NULL;
+
+ TEST_DUP("config", "true", "false");
+ TEST_DUP("description", "text1", "text2");
+ TEST_DUP("max-elements", "10", "20");
+ TEST_DUP("min-elements", "10", "20");
+ TEST_DUP("ordered-by", "user", "system");
+ TEST_DUP("reference", "1", "2");
+ TEST_DUP("status", "current", "obsolete");
+ TEST_DUP("type", "int8", "uint8");
+ TEST_DUP("units", "text1", "text2");
+ TEST_DUP("when", "true", "false");
+#undef TEST_DUP
+
+ /* full content - without min-elements which is mutual exclusive with default */
+ str = "ll {config false;default \"xxx\"; default \"yyy\";description test;if-feature f;"
+ "max-elements 10;must 'expr';ordered-by user;reference test;"
+ "status current;type string; units zzz;when true;m:ext;} ...";
+ assert_int_equal(LY_SUCCESS, parse_leaflist(&ctx, &str, NULL, (struct lysp_node**)&ll));
+ assert_non_null(ll);
+ assert_int_equal(LYS_LEAFLIST, ll->nodetype);
+ assert_string_equal("ll", ll->name);
+ assert_string_equal("test", ll->dsc);
+ assert_non_null(ll->dflts);
+ assert_int_equal(2, LY_ARRAY_SIZE(ll->dflts));
+ assert_string_equal("xxx", ll->dflts[0]);
+ assert_string_equal("yyy", ll->dflts[1]);
+ assert_string_equal("zzz", ll->units);
+ assert_int_equal(10, ll->max);
+ assert_int_equal(0, ll->min);
+ assert_string_equal("string", ll->type.name);
+ assert_non_null(ll->exts);
+ assert_non_null(ll->iffeatures);
+ assert_non_null(ll->musts);
+ assert_string_equal("test", ll->ref);
+ assert_non_null(ll->when);
+ assert_null(ll->parent);
+ assert_null(ll->next);
+ assert_int_equal(LYS_CONFIG_R | LYS_STATUS_CURR | LYS_ORDBY_USER | LYS_SET_MAX, ll->flags);
+ lysp_node_free(ctx.ctx, (struct lysp_node*)ll); ll = NULL;
+
+ /* full content - now with min-elements */
+ str = "ll {min-elements 10; type string;} ...";
+ assert_int_equal(LY_SUCCESS, parse_leaflist(&ctx, &str, NULL, (struct lysp_node**)&ll));
+ assert_non_null(ll);
+ assert_int_equal(LYS_LEAFLIST, ll->nodetype);
+ assert_string_equal("ll", ll->name);
+ assert_string_equal("string", ll->type.name);
+ assert_int_equal(0, ll->max);
+ assert_int_equal(10, ll->min);
+ assert_int_equal(LYS_SET_MIN, ll->flags);
+ lysp_node_free(ctx.ctx, (struct lysp_node*)ll); ll = NULL;
+
+ /* invalid */
+ str = " ll {min-elements 1; default xx; type string;} ...";
+ assert_int_equal(LY_EVALID, parse_leaflist(&ctx, &str, NULL, (struct lysp_node**)&ll));
+ logbuf_assert("Invalid combination of keywords \"min-elements\" and \"default\" as children of \"leaf-list\". Line number 1.");
+ lysp_node_free(ctx.ctx, (struct lysp_node*)ll); ll = NULL;
+
+ str = " ll {description \"missing type\";} ...";
+ assert_int_equal(LY_EVALID, parse_leaflist(&ctx, &str, NULL, (struct lysp_node**)&ll));
+ logbuf_assert("Missing mandatory keyword \"type\" as a child of \"leaf-list\". Line number 1.");
+ lysp_node_free(ctx.ctx, (struct lysp_node*)ll); ll = NULL;
+
+ ctx.mod->version = 1; /* simulate YANG 1.0 - default statement is not allowed */
+ str = " ll {default xx; type string;} ...";
+ assert_int_equal(LY_EVALID, parse_leaflist(&ctx, &str, NULL, (struct lysp_node**)&ll));
+ logbuf_assert("Invalid keyword \"default\" as a child of \"leaf-list\" - the statement is allowed only in YANG 1.1 modules. Line number 1.");
+ lysp_node_free(ctx.ctx, (struct lysp_node*)ll); ll = NULL;
+
+ *state = NULL;
+ ly_ctx_destroy(ctx.ctx, NULL);
+}
+
int main(void)
{
const struct CMUnitTest tests[] = {
@@ -1412,6 +1508,7 @@
cmocka_unit_test_setup(test_deviate, logger_setup),
cmocka_unit_test_setup(test_container, logger_setup),
cmocka_unit_test_setup_teardown(test_leaf, logger_setup, logger_teardown),
+ cmocka_unit_test_setup_teardown(test_leaflist, logger_setup, logger_teardown),
};
return cmocka_run_group_tests(tests, NULL, NULL);
diff --git a/tests/src/test_tree_schema_compile.c b/tests/src/test_tree_schema_compile.c
index cae2d2c..f4dfa8b 100644
--- a/tests/src/test_tree_schema_compile.c
+++ b/tests/src/test_tree_schema_compile.c
@@ -372,6 +372,54 @@
ly_ctx_destroy(ctx, NULL);
}
+static void
+test_node_leaflist(void **state)
+{
+ *state = test_node_leaflist;
+
+ struct ly_ctx *ctx;
+ struct lys_module *mod;
+ struct lysc_type *type;
+ struct lysc_node_leaflist *ll;
+
+ assert_int_equal(LY_SUCCESS, ly_ctx_new(NULL, LY_CTX_DISABLE_SEARCHDIRS, &ctx));
+
+ assert_non_null(mod = lys_parse_mem(ctx, "module a {namespace urn:a;prefix a;"
+ "typedef mytype {type union {type leafref {path ../target;} type string;}}"
+ "leaf-list ll1 {type union {type decimal64 {fraction-digits 2;} type mytype;}}"
+ "leaf-list ll2 {type leafref {path ../target;}}"
+ "leaf target {type int8;}}",
+ LYS_IN_YANG));
+ assert_int_equal(LY_SUCCESS, lys_compile(mod, 0));
+ type = ((struct lysc_node_leaf*)mod->compiled->data)->type;
+ assert_non_null(type);
+ assert_int_equal(1, type->refcount);
+ assert_int_equal(LY_TYPE_UNION, type->basetype);
+ assert_non_null(((struct lysc_type_union*)type)->types);
+ assert_int_equal(3, LY_ARRAY_SIZE(((struct lysc_type_union*)type)->types));
+ assert_int_equal(LY_TYPE_DEC64, ((struct lysc_type_union*)type)->types[0]->basetype);
+ assert_int_equal(LY_TYPE_LEAFREF, ((struct lysc_type_union*)type)->types[1]->basetype);
+ assert_int_equal(LY_TYPE_STRING, ((struct lysc_type_union*)type)->types[2]->basetype);
+ assert_non_null(((struct lysc_type_leafref*)((struct lysc_type_union*)type)->types[1])->realtype);
+ assert_int_equal(LY_TYPE_INT8, ((struct lysc_type_leafref*)((struct lysc_type_union*)type)->types[1])->realtype->basetype);
+ type = ((struct lysc_node_leaf*)mod->compiled->data->next)->type;
+ assert_non_null(type);
+ assert_int_equal(1, type->refcount);
+ assert_int_equal(LY_TYPE_LEAFREF, type->basetype);
+ assert_non_null(((struct lysc_type_leafref*)type)->realtype);
+ assert_int_equal(LY_TYPE_INT8, ((struct lysc_type_leafref*)type)->realtype->basetype);
+
+ assert_non_null(mod = lys_parse_mem(ctx, "module b {namespace urn:b;prefix b;feature f; leaf-list ll {type string;}}", LYS_IN_YANG));
+ assert_int_equal(LY_SUCCESS, lys_compile(mod, 0));
+ assert_non_null(mod->compiled);
+ assert_non_null((ll = (struct lysc_node_leaflist*)mod->compiled->data));
+ assert_int_equal(0, ll->min);
+ assert_int_equal((uint32_t)-1, ll->max);
+
+ *state = NULL;
+ ly_ctx_destroy(ctx, NULL);
+}
+
/**
* actually the same as length restriction (tested in test_type_length()), so just check the correct handling in appropriate types,
* do not test the expression itself
@@ -1666,6 +1714,7 @@
cmocka_unit_test_setup_teardown(test_type_union, logger_setup, logger_teardown),
cmocka_unit_test_setup_teardown(test_type_dflt, logger_setup, logger_teardown),
cmocka_unit_test_setup_teardown(test_node_container, logger_setup, logger_teardown),
+ cmocka_unit_test_setup_teardown(test_node_leaflist, logger_setup, logger_teardown),
};
return cmocka_run_group_tests(tests, NULL, NULL);