validation FEATURE operational datastore validation
diff --git a/src/parser_data.h b/src/parser_data.h
index edfdff8..2f7592b 100644
--- a/src/parser_data.h
+++ b/src/parser_data.h
@@ -1,9 +1,10 @@
 /**
  * @file parser_data.h
  * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
  * @brief Data parsers for libyang
  *
- * Copyright (c) 2015-2020 CESNET, z.s.p.o.
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.
@@ -197,6 +198,11 @@
                                                  Also, no implicit state data are added. */
 #define LYD_VALIDATE_PRESENT    0x0002      /**< Validate only modules whose data actually exist. */
 #define LYD_VALIDATE_MULTI_ERROR 0x0004     /**< Do not stop validation on the first error but generate all the detected errors. */
+#define LYD_VALIDATE_OPERATIONAL 0x0008     /**< Semantic constraint violations are reported only as warnings instead
+                                                 of errors (see [RFC 8342 sec. 5.3](https://datatracker.ietf.org/doc/html/rfc8342#section-5.3)). */
+#define LYD_VALIDATE_NO_DEFAULTS 0x0010     /**< Do not add any default nodes during validation, other implicit nodes
+                                                 (such as NP containers) are still added. Validation will fail if a
+                                                 default node is required for it to pass. */
 
 #define LYD_VALIDATE_OPTS_MASK  0x0000FFFF  /**< Mask for all the LYD_VALIDATE_* options. */
 
diff --git a/src/validation.c b/src/validation.c
index dc1d3e7..1ffc42d 100644
--- a/src/validation.c
+++ b/src/validation.c
@@ -3,7 +3,7 @@
  * @author Michal Vasko <mvasko@cesnet.cz>
  * @brief Validation
  *
- * Copyright (c) 2019 - 2022 CESNET, z.s.p.o.
+ * Copyright (c) 2019 - 2023 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.
@@ -252,6 +252,9 @@
 
                     /* free */
                     lyd_free_tree(node);
+                } else if (val_opts & LYD_VALIDATE_OPERATIONAL) {
+                    /* only a warning */
+                    LOGWRN(LYD_CTX(node), "When condition \"%s\" not satisfied.", disabled->cond->expr);
                 } else {
                     /* invalid data */
                     LOGVAL(LYD_CTX(node), LY_VCODE_NOWHEN, disabled->cond->expr);
@@ -386,10 +389,11 @@
  *
  * @param[in] first First sibling to search in.
  * @param[in] node Data node instance to check.
+ * @param[in] val_opts Validation options.
  * @return LY_ERR value.
  */
 static LY_ERR
-lyd_validate_duplicates(const struct lyd_node *first, const struct lyd_node *node)
+lyd_validate_duplicates(const struct lyd_node *first, const struct lyd_node *node, uint32_t val_opts)
 {
     struct lyd_node **match_p;
     ly_bool fail = 0;
@@ -426,10 +430,17 @@
     }
 
     if (fail) {
-        LOG_LOCSET(NULL, node, NULL, NULL);
-        LOGVAL(node->schema->module->ctx, LY_VCODE_DUP, node->schema->name);
-        LOG_LOCBACK(0, 1, 0, 0);
-        return LY_EVALID;
+        if ((node->schema->nodetype & (LYS_LIST | LYS_LEAFLIST)) && (val_opts & LYD_VALIDATE_OPERATIONAL)) {
+            /* only a warning */
+            LOG_LOCSET(NULL, node, NULL, NULL);
+            LOGWRN(node->schema->module->ctx, "Duplicate instance of \"%s\".", node->schema->name);
+            LOG_LOCBACK(0, 1, 0, 0);
+        } else {
+            LOG_LOCSET(NULL, node, NULL, NULL);
+            LOGVAL(node->schema->module->ctx, LY_VCODE_DUP, node->schema->name);
+            LOG_LOCBACK(0, 1, 0, 0);
+            return LY_EVALID;
+        }
     }
     return LY_SUCCESS;
 }
@@ -826,7 +837,7 @@
 
         if (node->flags & LYD_NEW) {
             /* then check new node instance duplicities */
-            r = lyd_validate_duplicates(*first, node);
+            r = lyd_validate_duplicates(*first, node, val_opts);
             LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
 
             /* this node is valid */
@@ -921,10 +932,12 @@
  * @param[in] first First sibling to search in.
  * @param[in] parent Data parent.
  * @param[in] snode Schema node to validate.
+ * @param[in] val_opts Validation options.
  * @return LY_ERR value.
  */
 static LY_ERR
-lyd_validate_mandatory(const struct lyd_node *first, const struct lyd_node *parent, const struct lysc_node *snode)
+lyd_validate_mandatory(const struct lyd_node *first, const struct lyd_node *parent, const struct lysc_node *snode,
+        uint32_t val_opts)
 {
     const struct lysc_when *disabled;
 
@@ -949,15 +962,26 @@
     }
 
     if (!disabled) {
-        /* node instance not found */
-        LOG_LOCSET(parent ? NULL : snode, parent, NULL, NULL);
-        if (snode->nodetype == LYS_CHOICE) {
-            LOGVAL_APPTAG(snode->module->ctx, "missing-choice", LY_VCODE_NOMAND_CHOIC, snode->name);
+        if (val_opts & LYD_VALIDATE_OPERATIONAL) {
+            /* only a warning */
+            LOG_LOCSET(parent ? NULL : snode, parent, NULL, NULL);
+            if (snode->nodetype == LYS_CHOICE) {
+                LOGWRN(snode->module->ctx, "Mandatory choice \"%s\" data do not exist.", snode->name);
+            } else {
+                LOGWRN(snode->module->ctx, "Mandatory node \"%s\" instance does not exist.", snode->name);
+            }
+            LOG_LOCBACK(parent ? 0 : 1, parent ? 1 : 0, 0, 0);
         } else {
-            LOGVAL(snode->module->ctx, LY_VCODE_NOMAND, snode->name);
+            /* node instance not found */
+            LOG_LOCSET(parent ? NULL : snode, parent, NULL, NULL);
+            if (snode->nodetype == LYS_CHOICE) {
+                LOGVAL_APPTAG(snode->module->ctx, "missing-choice", LY_VCODE_NOMAND_CHOIC, snode->name);
+            } else {
+                LOGVAL(snode->module->ctx, LY_VCODE_NOMAND, snode->name);
+            }
+            LOG_LOCBACK(parent ? 0 : 1, parent ? 1 : 0, 0, 0);
+            return LY_EVALID;
         }
-        LOG_LOCBACK(parent ? 0 : 1, parent ? 1 : 0, 0, 0);
-        return LY_EVALID;
     }
 
     return LY_SUCCESS;
@@ -971,11 +995,12 @@
  * @param[in] snode Schema node to validate.
  * @param[in] min Minimum number of elements, 0 for no restriction.
  * @param[in] max Max number of elements, 0 for no restriction.
+ * @param[in] val_opts Validation options.
  * @return LY_ERR value.
  */
 static LY_ERR
 lyd_validate_minmax(const struct lyd_node *first, const struct lyd_node *parent, const struct lysc_node *snode,
-        uint32_t min, uint32_t max)
+        uint32_t min, uint32_t max, uint32_t val_opts)
 {
     uint32_t count = 0;
     struct lyd_node *iter;
@@ -1020,15 +1045,29 @@
     }
 
     if (min) {
-        LOG_LOCSET(snode, NULL, NULL, NULL);
-        LOGVAL_APPTAG(snode->module->ctx, "too-few-elements", LY_VCODE_NOMIN, snode->name);
-        LOG_LOCBACK(1, 0, 0, 0);
-        return LY_EVALID;
+        if (val_opts & LYD_VALIDATE_OPERATIONAL) {
+            /* only a warning */
+            LOG_LOCSET(snode, NULL, NULL, NULL);
+            LOGWRN(snode->module->ctx, "Too few \"%s\" instances.", snode->name);
+            LOG_LOCBACK(1, 0, 0, 0);
+        } else {
+            LOG_LOCSET(snode, NULL, NULL, NULL);
+            LOGVAL_APPTAG(snode->module->ctx, "too-few-elements", LY_VCODE_NOMIN, snode->name);
+            LOG_LOCBACK(1, 0, 0, 0);
+            return LY_EVALID;
+        }
     } else if (max) {
-        LOG_LOCSET(NULL, iter, NULL, NULL);
-        LOGVAL_APPTAG(snode->module->ctx, "too-many-elements", LY_VCODE_NOMAX, snode->name);
-        LOG_LOCBACK(0, 1, 0, 0);
-        return LY_EVALID;
+        if (val_opts & LYD_VALIDATE_OPERATIONAL) {
+            /* only a warning */
+            LOG_LOCSET(NULL, iter, NULL, NULL);
+            LOGWRN(snode->module->ctx, "Too many \"%s\" instances.", snode->name);
+            LOG_LOCBACK(0, 1, 0, 0);
+        } else {
+            LOG_LOCSET(NULL, iter, NULL, NULL);
+            LOGVAL_APPTAG(snode->module->ctx, "too-many-elements", LY_VCODE_NOMAX, snode->name);
+            LOG_LOCBACK(0, 1, 0, 0);
+            return LY_EVALID;
+        }
     }
     return LY_SUCCESS;
 }
@@ -1070,11 +1109,17 @@
 }
 
 /**
+ * @brief Unique list validation callback argument.
+ */
+struct lyd_val_uniq_arg {
+    LY_ARRAY_COUNT_TYPE action; /**< Action to perform - 0 to compare all uniques, n to compare only n-th unique. */
+    uint32_t val_opts;          /**< Validation options. */
+};
+
+/**
  * @brief Callback for comparing 2 list unique leaf values.
  *
  * Implementation of ::lyht_value_equal_cb.
- *
- * @param[in] cb_data 0 to compare all uniques, n to compare only n-th unique.
  */
 static ly_bool
 lyd_val_uniq_list_equal(void *val1_p, void *val2_p, ly_bool UNUSED(mod), void *cb_data)
@@ -1084,13 +1129,14 @@
     struct lyd_node *diter, *first, *second;
     struct lyd_value *val1, *val2;
     char *path1, *path2, *uniq_str, *ptr;
-    LY_ARRAY_COUNT_TYPE u, v, action;
+    LY_ARRAY_COUNT_TYPE u, v;
+    struct lyd_val_uniq_arg *arg = cb_data;
+    const uint32_t uniq_err_msg_size = 1024;
 
     assert(val1_p && val2_p);
 
     first = *((struct lyd_node **)val1_p);
     second = *((struct lyd_node **)val2_p);
-    action = (uintptr_t)cb_data;
 
     assert(first && (first->schema->nodetype == LYS_LIST));
     assert(second && (second->schema == first->schema));
@@ -1100,8 +1146,8 @@
     slist = (struct lysc_node_list *)first->schema;
 
     /* compare unique leaves */
-    if (action > 0) {
-        u = action - 1;
+    if (arg->action > 0) {
+        u = arg->action - 1;
         if (u < LY_ARRAY_COUNT(slist->uniques)) {
             goto uniquecheck;
         }
@@ -1133,13 +1179,12 @@
             }
         }
         if (v && (v == LY_ARRAY_COUNT(slist->uniques[u]))) {
-            /* all unique leafs are the same in this set, create this nice error */
+            /* all unique leaves are the same in this set, create this nice error */
             path1 = lyd_path(first, LYD_PATH_STD, NULL, 0);
             path2 = lyd_path(second, LYD_PATH_STD, NULL, 0);
 
             /* use buffer to rebuild the unique string */
-#define UNIQ_BUF_SIZE 1024
-            uniq_str = malloc(UNIQ_BUF_SIZE);
+            uniq_str = malloc(uniq_err_msg_size);
             uniq_str[0] = '\0';
             ptr = uniq_str;
             LY_ARRAY_FOR(slist->uniques[u], v) {
@@ -1148,7 +1193,7 @@
                     ++ptr;
                 }
                 ptr = lysc_path_until((struct lysc_node *)slist->uniques[u][v], &slist->node, LYSC_PATH_LOG,
-                        ptr, UNIQ_BUF_SIZE - (ptr - uniq_str));
+                        ptr, uniq_err_msg_size - (ptr - uniq_str));
                 if (!ptr) {
                     /* path will be incomplete, whatever */
                     break;
@@ -1157,18 +1202,24 @@
                 ptr += strlen(ptr);
             }
             LOG_LOCSET(NULL, second, NULL, NULL);
-            LOGVAL_APPTAG(ctx, "data-not-unique", LY_VCODE_NOUNIQ, uniq_str, path1, path2);
+            if (arg->val_opts & LYD_VALIDATE_OPERATIONAL) {
+                /* only a warning */
+                LOGWRN(ctx, "Unique data leaf(s) \"%s\" not satisfied in \"%s\" and \"%s\".", uniq_str, path1, path2);
+            } else {
+                LOGVAL_APPTAG(ctx, "data-not-unique", LY_VCODE_NOUNIQ, uniq_str, path1, path2);
+            }
             LOG_LOCBACK(0, 1, 0, 0);
 
             free(path1);
             free(path2);
             free(uniq_str);
-#undef UNIQ_BUF_SIZE
 
-            return 1;
+            if (!(arg->val_opts & LYD_VALIDATE_OPERATIONAL)) {
+                return 1;
+            }
         }
 
-        if (action > 0) {
+        if (arg->action > 0) {
             /* done */
             return 0;
         }
@@ -1183,10 +1234,12 @@
  * @param[in] first First sibling to search in.
  * @param[in] snode Schema node to validate.
  * @param[in] uniques List unique arrays to validate.
+ * @param[in] val_opts Validation options.
  * @return LY_ERR value.
  */
 static LY_ERR
-lyd_validate_unique(const struct lyd_node *first, const struct lysc_node *snode, const struct lysc_node_leaf ***uniques)
+lyd_validate_unique(const struct lyd_node *first, const struct lysc_node *snode, const struct lysc_node_leaf ***uniques,
+        uint32_t val_opts)
 {
     const struct lyd_node *diter;
     struct ly_set *set;
@@ -1196,7 +1249,7 @@
     size_t key_len;
     ly_bool dyn;
     const void *hash_key;
-    void *cb_data;
+    struct lyd_val_uniq_arg arg, *args = NULL;
     struct hash_table **uniqtables = NULL;
     struct lyd_value *val;
     struct ly_ctx *ctx = snode->module->ctx;
@@ -1214,7 +1267,9 @@
 
     if (set->count == 2) {
         /* simple comparison */
-        if (lyd_val_uniq_list_equal(&set->objs[0], &set->objs[1], 0, (void *)0)) {
+        arg.action = 0;
+        arg.val_opts = val_opts;
+        if (lyd_val_uniq_list_equal(&set->objs[0], &set->objs[1], 0, &arg)) {
             /* instance duplication */
             ret = LY_EVALID;
             goto cleanup;
@@ -1222,12 +1277,14 @@
     } else if (set->count > 2) {
         /* use hashes for comparison */
         uniqtables = malloc(LY_ARRAY_COUNT(uniques) * sizeof *uniqtables);
-        LY_CHECK_ERR_GOTO(!uniqtables, LOGMEM(ctx); ret = LY_EMEM, cleanup);
+        args = malloc(LY_ARRAY_COUNT(uniques) * sizeof *args);
+        LY_CHECK_ERR_GOTO(!uniqtables || !args, LOGMEM(ctx); ret = LY_EMEM, cleanup);
         x = LY_ARRAY_COUNT(uniques);
         for (v = 0; v < x; v++) {
-            cb_data = (void *)(uintptr_t)(v + 1L);
+            args[v].action = v + 1;
+            args[v].val_opts = val_opts;
             uniqtables[v] = lyht_new(lyht_get_fixed_size(set->count), sizeof(struct lyd_node *),
-                    lyd_val_uniq_list_equal, cb_data, 0);
+                    lyd_val_uniq_list_equal, &args[v], 0);
             LY_CHECK_ERR_GOTO(!uniqtables[v], LOGMEM(ctx); ret = LY_EMEM, cleanup);
         }
 
@@ -1284,6 +1341,7 @@
         lyht_free(uniqtables[v], NULL);
     }
     free(uniqtables);
+    free(args);
 
     return ret;
 }
@@ -1321,19 +1379,19 @@
         if (snode->nodetype == LYS_LIST) {
             slist = (struct lysc_node_list *)snode;
             if (slist->min || slist->max) {
-                r = lyd_validate_minmax(first, parent, snode, slist->min, slist->max);
+                r = lyd_validate_minmax(first, parent, snode, slist->min, slist->max, val_opts);
                 LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
             }
         } else if (snode->nodetype == LYS_LEAFLIST) {
             sllist = (struct lysc_node_leaflist *)snode;
             if (sllist->min || sllist->max) {
-                r = lyd_validate_minmax(first, parent, snode, sllist->min, sllist->max);
+                r = lyd_validate_minmax(first, parent, snode, sllist->min, sllist->max, val_opts);
                 LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
             }
 
         } else if (snode->flags & LYS_MAND_TRUE) {
             /* check generic mandatory existence */
-            r = lyd_validate_mandatory(first, parent, snode);
+            r = lyd_validate_mandatory(first, parent, snode, val_opts);
             LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
         }
 
@@ -1341,7 +1399,7 @@
         if (snode->nodetype == LYS_LIST) {
             slist = (struct lysc_node_list *)snode;
             if (slist->uniques) {
-                r = lyd_validate_unique(first, snode, (const struct lysc_node_leaf ***)slist->uniques);
+                r = lyd_validate_unique(first, snode, (const struct lysc_node_leaf ***)slist->uniques, val_opts);
                 LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
             }
         }
@@ -1445,18 +1503,30 @@
         /* check the result */
         lyxp_set_cast(&xp_set, LYXP_SET_BOOLEAN);
         if (!xp_set.val.bln) {
-            /* use specific error information */
-            emsg = musts[u].emsg;
-            eapptag = musts[u].eapptag ? musts[u].eapptag : "must-violation";
-            LOG_LOCSET(NULL, node, NULL, NULL);
-            if (emsg) {
-                LOGVAL_APPTAG(LYD_CTX(node), eapptag, LYVE_DATA, "%s", emsg);
+            if (val_opts & LYD_VALIDATE_OPERATIONAL) {
+                /* only a warning */
+                emsg = musts[u].emsg;
+                LOG_LOCSET(NULL, node, NULL, NULL);
+                if (emsg) {
+                    LOGWRN(LYD_CTX(node), "%s", emsg);
+                } else {
+                    LOGWRN(LYD_CTX(node), "Must condition \"%s\" not satisfied.", musts[u].cond->expr);
+                }
+                LOG_LOCBACK(0, 1, 0, 0);
             } else {
-                LOGVAL_APPTAG(LYD_CTX(node), eapptag, LY_VCODE_NOMUST, musts[u].cond->expr);
+                /* use specific error information */
+                emsg = musts[u].emsg;
+                eapptag = musts[u].eapptag ? musts[u].eapptag : "must-violation";
+                LOG_LOCSET(NULL, node, NULL, NULL);
+                if (emsg) {
+                    LOGVAL_APPTAG(LYD_CTX(node), eapptag, LYVE_DATA, "%s", emsg);
+                } else {
+                    LOGVAL_APPTAG(LYD_CTX(node), eapptag, LY_VCODE_NOMUST, musts[u].cond->expr);
+                }
+                LOG_LOCBACK(0, 1, 0, 0);
+                r = LY_EVALID;
+                LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
             }
-            LOG_LOCBACK(0, 1, 0, 0);
-            r = LY_EVALID;
-            LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
         }
     }
 
@@ -1657,6 +1727,7 @@
     const struct lyd_meta *meta;
     const struct lysc_type *type;
     struct lyd_node *node;
+    uint32_t impl_opts;
 
     LYD_TREE_DFS_BEGIN(root, node) {
         if (node->flags & LYD_EXT) {
@@ -1688,8 +1759,14 @@
             LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
 
             /* add nested defaults */
-            r = lyd_new_implicit_r(node, lyd_node_child_p(node), NULL, NULL, NULL, NULL, NULL,
-                    (val_opts & LYD_VALIDATE_NO_STATE) ? LYD_IMPLICIT_NO_STATE : 0, diff);
+            impl_opts = 0;
+            if (val_opts & LYD_VALIDATE_NO_STATE) {
+                impl_opts |= LYD_IMPLICIT_NO_STATE;
+            }
+            if (val_opts & LYD_VALIDATE_NO_DEFAULTS) {
+                impl_opts |= LYD_IMPLICIT_NO_DEFAULTS;
+            }
+            r = lyd_new_implicit_r(node, lyd_node_child_p(node), NULL, NULL, NULL, NULL, NULL, impl_opts, diff);
             LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
         }
 
@@ -1720,7 +1797,7 @@
     struct lyd_node *first, *next, **first2, *iter;
     const struct lys_module *mod;
     struct ly_set node_types = {0}, meta_types = {0}, node_when = {0}, ext_node = {0}, ext_val = {0};
-    uint32_t i = 0;
+    uint32_t i = 0, impl_opts;
 
     assert(tree && ctx);
     assert((node_when_p && node_types_p && meta_types_p && ext_node_p && ext_val_p) ||
@@ -1757,9 +1834,15 @@
 
         /* add all top-level defaults for this module, if going to validate subtree, do not add into unres sets
          * (lyd_validate_subtree() adds all the nodes in that case) */
+        impl_opts = 0;
+        if (val_opts & LYD_VALIDATE_NO_STATE) {
+            impl_opts |= LYD_IMPLICIT_NO_STATE;
+        }
+        if (val_opts & LYD_VALIDATE_NO_DEFAULTS) {
+            impl_opts |= LYD_IMPLICIT_NO_DEFAULTS;
+        }
         r = lyd_new_implicit_r(NULL, first2, NULL, mod, validate_subtree ? NULL : node_when_p,
-                validate_subtree ? NULL : node_types_p, validate_subtree ? NULL : ext_node_p,
-                (val_opts & LYD_VALIDATE_NO_STATE) ? LYD_IMPLICIT_NO_STATE : 0, diff);
+                validate_subtree ? NULL : node_types_p, validate_subtree ? NULL : ext_node_p, impl_opts, diff);
         LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
 
         /* our first module node pointer may no longer be the first */