validation FEATURE multi-error parsing/validation

Refs #1978
diff --git a/src/parser_data.h b/src/parser_data.h
index 050ced3..edfdff8 100644
--- a/src/parser_data.h
+++ b/src/parser_data.h
@@ -196,6 +196,7 @@
 #define LYD_VALIDATE_NO_STATE   0x0001      /**< Consider state data not allowed and raise an error if they are found.
                                                  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_OPTS_MASK  0x0000FFFF  /**< Mask for all the LYD_VALIDATE_* options. */
 
diff --git a/src/parser_internal.h b/src/parser_internal.h
index 458d297..f807d4d 100644
--- a/src/parser_internal.h
+++ b/src/parser_internal.h
@@ -1,9 +1,10 @@
 /**
  * @file parser_internal.h
  * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
  * @brief Internal structures and functions for libyang parsers
  *
- * Copyright (c) 2020 CESNET, z.s.p.o.
+ * Copyright (c) 2020 - 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.
@@ -27,6 +28,22 @@
 struct lysp_ctx;
 
 /**
+ * @brief Check data parser error taking into account multi-error validation.
+ *
+ * @param[in] r Local return value.
+ * @param[in] err_cmd Command to perform on any error.
+ * @param[in] lydctx Data parser context.
+ * @param[in] label Label to go to on fatal error.
+ */
+#define LY_DPARSER_ERR_GOTO(r, err_cmd, lydctx, label) \
+        if (r) { \
+            err_cmd; \
+            if ((r != LY_EVALID) || !lydctx || !(lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR)) { \
+                goto label; \
+            } \
+        }
+
+/**
  * @brief Callback for ::lyd_ctx to free the structure
  *
  * @param[in] ctx Data parser context to free.
diff --git a/src/parser_json.c b/src/parser_json.c
index 6219c6e..7a87af1 100644
--- a/src/parser_json.c
+++ b/src/parser_json.c
@@ -183,7 +183,7 @@
 }
 
 /**
- * @brief Skip the current JSON object/array.
+ * @brief Skip the current JSON item (baed on status).
  *
  * @param[in] jsonctx JSON context with the input data to skip.
  * @return LY_ERR value.
@@ -195,12 +195,15 @@
     uint32_t orig_depth;
 
     status = lyjson_ctx_status(jsonctx, 0);
-    assert((status == LYJSON_OBJECT) || (status == LYJSON_ARRAY));
     orig_depth = jsonctx->depth;
 
     /* next */
     LY_CHECK_RET(lyjson_ctx_next(jsonctx, &current));
 
+    if ((status != LYJSON_OBJECT) && (status != LYJSON_ARRAY)) {
+        return LY_SUCCESS;
+    }
+
     if ((status == LYJSON_OBJECT) && (current != LYJSON_OBJECT) && (current != LYJSON_ARRAY)) {
         /* no nested objects */
         LY_CHECK_RET(lyjson_ctx_next(jsonctx, NULL));
@@ -922,24 +925,26 @@
 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)
 {
-    if (*node_p) {
-        /* insert, keep first pointer correct */
-        if (ext) {
-            lyplg_ext_insert(parent, *node_p);
+    if (!*node_p) {
+        return;
+    }
+
+    /* insert, keep first pointer correct */
+    if (ext) {
+        lyplg_ext_insert(parent, *node_p);
+    } else {
+        lyd_insert_node(parent, first_p, *node_p, last);
+    }
+    if (first_p) {
+        if (parent) {
+            *first_p = lyd_child(parent);
         } else {
-            lyd_insert_node(parent, first_p, *node_p, last);
-        }
-        if (first_p) {
-            if (parent) {
-                *first_p = lyd_child(parent);
-            } else {
-                while ((*first_p)->prev->next) {
-                    *first_p = (*first_p)->prev;
-                }
+            while ((*first_p)->prev->next) {
+                *first_p = (*first_p)->prev;
             }
         }
-        *node_p = NULL;
     }
+    *node_p = NULL;
 }
 
 /**
@@ -1232,7 +1237,7 @@
 lydjson_parse_any(struct lyd_json_ctx *lydctx, const struct lysc_node *snode, struct lysc_ext_instance *ext,
         enum LYJSON_PARSER_STATUS *status, struct lyd_node **node)
 {
-    LY_ERR rc = LY_SUCCESS;
+    LY_ERR r, rc = LY_SUCCESS;
     uint32_t prev_parse_opts, prev_int_opts;
     struct ly_in in_start;
     char *val = NULL;
@@ -1262,7 +1267,9 @@
 
         /* process the anydata content */
         while (*status != LYJSON_OBJECT_CLOSED) {
-            LY_CHECK_RET(lydjson_subtree_r(lydctx, NULL, &tree, NULL));
+            r = lydjson_subtree_r(lydctx, NULL, &tree, NULL);
+            LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
+
             *status = lyjson_ctx_status(lydctx->jsonctx, 0);
         }
 
@@ -1271,9 +1278,11 @@
         lydctx->int_opts = prev_int_opts;
 
         /* finish linking metadata */
-        LY_CHECK_RET(lydjson_metadata_finish(lydctx, &tree));
+        r = lydjson_metadata_finish(lydctx, &tree);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
 
-        LY_CHECK_RET(lyd_create_any(snode, tree, LYD_ANYDATA_DATATREE, 1, node));
+        r = lyd_create_any(snode, tree, LYD_ANYDATA_DATATREE, 1, node);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
         break;
     case LYJSON_ARRAY_EMPTY:
         /* store the empty array */
@@ -1281,7 +1290,8 @@
             LOGMEM(lydctx->jsonctx->ctx);
             return LY_EMEM;
         }
-        LY_CHECK_GOTO(rc = lyd_create_any(snode, val, LYD_ANYDATA_JSON, 1, node), val_err);
+        r = lyd_create_any(snode, val, LYD_ANYDATA_JSON, 1, node);
+        LY_CHECK_ERR_GOTO(r, rc = r, val_err);
         break;
     case LYJSON_ARRAY:
         /* skip until the array end */
@@ -1293,7 +1303,8 @@
             LOGMEM(lydctx->jsonctx->ctx);
             return LY_EMEM;
         }
-        LY_CHECK_GOTO(rc = lyd_create_any(snode, val, LYD_ANYDATA_JSON, 1, node), val_err);
+        r = lyd_create_any(snode, val, LYD_ANYDATA_JSON, 1, node);
+        LY_CHECK_ERR_GOTO(r, rc = r, val_err);
         break;
     case LYJSON_STRING:
         /* string value */
@@ -1304,7 +1315,8 @@
             val = strndup(lydctx->jsonctx->value, lydctx->jsonctx->value_len);
             LY_CHECK_ERR_RET(!val, LOGMEM(lydctx->jsonctx->ctx), LY_EMEM);
 
-            LY_CHECK_GOTO(rc = lyd_create_any(snode, val, LYD_ANYDATA_STRING, 1, node), val_err);
+            r = lyd_create_any(snode, val, LYD_ANYDATA_STRING, 1, node);
+            LY_CHECK_ERR_GOTO(r, rc = r, val_err);
         }
         break;
     case LYJSON_NUMBER:
@@ -1315,21 +1327,25 @@
         val = strndup(lydctx->jsonctx->value, lydctx->jsonctx->value_len);
         LY_CHECK_ERR_RET(!val, LOGMEM(lydctx->jsonctx->ctx), LY_EMEM);
 
-        LY_CHECK_GOTO(rc = lyd_create_any(snode, val, LYD_ANYDATA_JSON, 1, node), val_err);
+        r = lyd_create_any(snode, val, LYD_ANYDATA_JSON, 1, node);
+        LY_CHECK_ERR_GOTO(r, rc = r, val_err);
         break;
     case LYJSON_NULL:
         /* no value */
-        LY_CHECK_RET(lyd_create_any(snode, NULL, LYD_ANYDATA_JSON, 1, node));
+        r = lyd_create_any(snode, NULL, LYD_ANYDATA_JSON, 1, node);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
         break;
     case LYJSON_OBJECT_EMPTY:
         /* empty object */
-        LY_CHECK_RET(lyd_create_any(snode, NULL, LYD_ANYDATA_DATATREE, 1, node));
+        r = lyd_create_any(snode, NULL, LYD_ANYDATA_DATATREE, 1, node);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
         break;
     default:
         LOGINT_RET(lydctx->jsonctx->ctx);
     }
 
-    return LY_SUCCESS;
+cleanup:
+    return rc;
 
 val_err:
     free(val);
@@ -1352,7 +1368,7 @@
 lydjson_parse_instance_inner(struct lyd_json_ctx *lydctx, const struct lysc_node *snode, struct lysc_ext_instance *ext,
         enum LYJSON_PARSER_STATUS *status, struct lyd_node **node)
 {
-    LY_ERR ret = LY_SUCCESS;
+    LY_ERR r, rc = LY_SUCCESS;
     uint32_t prev_parse_opts = lydctx->parse_opts;
 
     LY_CHECK_RET((*status != LYJSON_OBJECT) && (*status != LYJSON_OBJECT_EMPTY), LY_ENOT);
@@ -1370,37 +1386,37 @@
 
     /* process children */
     while ((*status != LYJSON_OBJECT_CLOSED) && (*status != LYJSON_OBJECT_EMPTY)) {
-        ret = lydjson_subtree_r(lydctx, *node, lyd_node_child_p(*node), NULL);
-        LY_CHECK_GOTO(ret, cleanup);
+        r = lydjson_subtree_r(lydctx, *node, lyd_node_child_p(*node), NULL);
+        LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
+
         *status = lyjson_ctx_status(lydctx->jsonctx, 0);
     }
 
     /* finish linking metadata */
-    ret = lydjson_metadata_finish(lydctx, lyd_node_child_p(*node));
-    LY_CHECK_GOTO(ret, cleanup);
+    r = lydjson_metadata_finish(lydctx, lyd_node_child_p(*node));
+    LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
 
     if (snode->nodetype == LYS_LIST) {
         /* check all keys exist */
-        ret = lyd_parse_check_keys(*node);
-        LY_CHECK_GOTO(ret, cleanup);
+        r = lyd_parse_check_keys(*node);
+        LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
     }
 
     if (!(lydctx->parse_opts & LYD_PARSE_ONLY)) {
         /* new node validation, autodelete CANNOT occur, all nodes are new */
-        ret = lyd_validate_new(lyd_node_child_p(*node), snode, NULL, NULL);
-        LY_CHECK_GOTO(ret, cleanup);
+        r = lyd_validate_new(lyd_node_child_p(*node), snode, NULL, lydctx->val_opts, NULL);
+        LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
 
         /* add any missing default children */
-        ret = lyd_new_implicit_r(*node, lyd_node_child_p(*node), NULL, NULL, &lydctx->node_when,
-                &lydctx->node_types, &lydctx->ext_node,
-                (lydctx->val_opts & LYD_VALIDATE_NO_STATE) ? LYD_IMPLICIT_NO_STATE : 0, NULL);
-        LY_CHECK_GOTO(ret, cleanup);
+        r = lyd_new_implicit_r(*node, lyd_node_child_p(*node), NULL, NULL, &lydctx->node_when, &lydctx->node_types,
+                &lydctx->ext_node, (lydctx->val_opts & LYD_VALIDATE_NO_STATE) ? LYD_IMPLICIT_NO_STATE : 0, NULL);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
     }
 
 cleanup:
     lydctx->parse_opts = prev_parse_opts;
     LOG_LOCBACK(0, 1, 0, 0);
-    return ret;
+    return rc;
 }
 
 /**
@@ -1427,52 +1443,61 @@
         const struct lysc_node *snode, struct lysc_ext_instance *ext, const char *name, size_t name_len,
         const char *prefix, size_t prefix_len, enum LYJSON_PARSER_STATUS *status, struct lyd_node **node)
 {
-    LY_ERR ret = LY_SUCCESS;
+    LY_ERR r, rc = LY_SUCCESS;
     uint32_t type_hints = 0;
 
     LOG_LOCSET(snode, NULL, NULL, NULL);
 
-    ret = lydjson_data_check_opaq(lydctx, snode, &type_hints);
-    if (ret == LY_SUCCESS) {
+    r = lydjson_data_check_opaq(lydctx, snode, &type_hints);
+    if (r == LY_SUCCESS) {
         assert(snode->nodetype & (LYD_NODE_TERM | LYD_NODE_INNER | LYD_NODE_ANY));
         if (snode->nodetype & LYD_NODE_TERM) {
             if ((*status != LYJSON_ARRAY) && (*status != LYJSON_NUMBER) && (*status != LYJSON_STRING) &&
                     (*status != LYJSON_FALSE) && (*status != LYJSON_TRUE) && (*status != LYJSON_NULL)) {
-                ret = LY_ENOT;
+                rc = LY_ENOT;
                 goto cleanup;
             }
 
             /* create terminal node */
-            LY_CHECK_GOTO(ret = lyd_parser_create_term((struct lyd_ctx *)lydctx, snode, lydctx->jsonctx->value,
-                    lydctx->jsonctx->value_len, &lydctx->jsonctx->dynamic, LY_VALUE_JSON, NULL, type_hints, node), cleanup);
+            r = lyd_parser_create_term((struct lyd_ctx *)lydctx, snode, lydctx->jsonctx->value,
+                    lydctx->jsonctx->value_len, &lydctx->jsonctx->dynamic, LY_VALUE_JSON, NULL, type_hints, node);
+            LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
 
             /* move JSON parser */
             if (*status == LYJSON_ARRAY) {
                 /* only [null], 2 more moves are needed */
-                LY_CHECK_GOTO(ret = lyjson_ctx_next(lydctx->jsonctx, status), cleanup);
+                r = lyjson_ctx_next(lydctx->jsonctx, status);
+                LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
                 assert(*status == LYJSON_NULL);
-                LY_CHECK_GOTO(ret = lyjson_ctx_next(lydctx->jsonctx, status), cleanup);
+
+                r = lyjson_ctx_next(lydctx->jsonctx, status);
+                LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
                 assert(*status == LYJSON_ARRAY_CLOSED);
             }
         } else if (snode->nodetype & LYD_NODE_INNER) {
             /* create inner node */
-            LY_CHECK_GOTO(ret = lydjson_parse_instance_inner(lydctx, snode, ext, status, node), cleanup);
+            r = lydjson_parse_instance_inner(lydctx, snode, ext, status, node);
+            LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
         } else {
             /* create any node */
-            LY_CHECK_GOTO(ret = lydjson_parse_any(lydctx, snode, ext, status, node), cleanup);
+            r = lydjson_parse_any(lydctx, snode, ext, status, node);
+            LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
         }
+        LY_CHECK_GOTO(!*node, cleanup);
 
         /* add/correct flags */
-        LY_CHECK_GOTO(ret = lyd_parse_set_data_flags(*node, &(*node)->meta, (struct lyd_ctx *)lydctx, ext), cleanup);
+        r = lyd_parse_set_data_flags(*node, &(*node)->meta, (struct lyd_ctx *)lydctx, ext);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
 
         if (!(lydctx->parse_opts & LYD_PARSE_ONLY)) {
             /* store for ext instance node validation, if needed */
-            LY_CHECK_GOTO(ret = lyd_validate_node_ext(*node, &lydctx->ext_node), cleanup);
+            r = lyd_validate_node_ext(*node, &lydctx->ext_node);
+            LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
         }
-    } else if (ret == LY_ENOT) {
+    } else if (r == LY_ENOT) {
         /* parse it again as an opaq node */
-        LY_CHECK_GOTO(ret = lydjson_parse_opaq(lydctx, name, name_len, prefix, prefix_len, parent, status, status,
-                first_p, node), cleanup);
+        r = lydjson_parse_opaq(lydctx, name, name_len, prefix, prefix_len, parent, status, status, first_p, node);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
 
         if (snode->nodetype == LYS_LIST) {
             ((struct lyd_node_opaq *)*node)->hints |= LYD_NODEHINT_LIST;
@@ -1486,7 +1511,7 @@
 
 cleanup:
     LOG_LOCBACK(1, 0, 0, 0);
-    return ret;
+    return rc;
 }
 
 /**
@@ -1501,7 +1526,7 @@
 static LY_ERR
 lydjson_subtree_r(struct lyd_json_ctx *lydctx, struct lyd_node *parent, struct lyd_node **first_p, struct ly_set *parsed)
 {
-    LY_ERR ret = LY_SUCCESS, r;
+    LY_ERR r, rc = LY_SUCCESS;
     enum LYJSON_PARSER_STATUS status = lyjson_ctx_status(lydctx->jsonctx, 0);
     const char *name, *prefix = NULL, *expected = NULL;
     size_t name_len, prefix_len = 0;
@@ -1530,7 +1555,7 @@
             /* data parsed */
             goto cleanup;
         }
-        LY_CHECK_ERR_GOTO(r, ret = r, cleanup);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
 
         if (!snode) {
             /* we will not be parsing it as metadata */
@@ -1540,20 +1565,19 @@
 
     if (is_meta) {
         /* parse as metadata */
-        if (!name_len && !prefix_len) {
+        if (!name_len && !prefix_len && !parent) {
+            LOGVAL(ctx, LYVE_SYNTAX_JSON,
+                    "Invalid metadata format - \"@\" can be used only inside anydata, container or list entries.");
+            r = LY_EVALID;
+            LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
+        } else if (!name_len && !prefix_len) {
             /* parent's metadata without a name - use the schema from the parent */
-            if (!parent) {
-                LOGVAL(ctx, LYVE_SYNTAX_JSON,
-                        "Invalid metadata format - \"@\" can be used only inside anydata, container or list entries.");
-                ret = LY_EVALID;
-                goto cleanup;
-            }
             attr_node = parent;
             snode = attr_node->schema;
         }
-        ret = lydjson_parse_attribute(lydctx, attr_node, snode, name, name_len, prefix, prefix_len, parent, &status,
+        r = lydjson_parse_attribute(lydctx, attr_node, snode, name, name_len, prefix, prefix_len, parent, &status,
                 first_p, &node);
-        LY_CHECK_GOTO(ret, cleanup);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
     } else if (!snode) {
         /* parse as an opaq node */
         assert((lydctx->parse_opts & LYD_PARSE_OPAQ) || (lydctx->int_opts));
@@ -1561,18 +1585,19 @@
         /* opaq node cannot have an empty string as the name. */
         if (name_len == 0) {
             LOGVAL(lydctx->jsonctx->ctx, LYVE_SYNTAX_JSON, "A JSON object member name cannot be a zero-length string.");
-            ret = LY_EVALID;
-            goto cleanup;
+            r = LY_EVALID;
+            LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
         }
 
         /* move to the second item in the name/X pair and parse opaq */
-        ret = lydjson_ctx_next_parse_opaq(lydctx, name, name_len, prefix, prefix_len, parent, &status, first_p, &node);
-        LY_CHECK_GOTO(ret, cleanup);
+        r = lydjson_ctx_next_parse_opaq(lydctx, name, name_len, prefix, prefix_len, parent, &status, first_p, &node);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
     } else {
         /* parse as a standard lyd_node but it can still turn out to be an opaque node */
 
         /* move to the second item in the name/X pair */
-        LY_CHECK_GOTO(ret = lyjson_ctx_next(lydctx->jsonctx, &status), cleanup);
+        r = lyjson_ctx_next(lydctx->jsonctx, &status);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
 
         /* set expected representation */
         switch (snode->nodetype) {
@@ -1616,22 +1641,23 @@
             LY_CHECK_GOTO(status != LYJSON_ARRAY, representation_error);
 
             /* move into array */
-            ret = lyjson_ctx_next(lydctx->jsonctx, &status);
-            LY_CHECK_GOTO(ret, cleanup);
+            r = lyjson_ctx_next(lydctx->jsonctx, &status);
+            LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
 
             /* process all the values/objects */
             do {
-                ret = lydjson_parse_instance(lydctx, parent, first_p, snode, ext, name, name_len, prefix, prefix_len,
+                r = lydjson_parse_instance(lydctx, parent, first_p, snode, ext, name, name_len, prefix, prefix_len,
                         &status, &node);
-                if (ret == LY_ENOT) {
+                if (r == LY_ENOT) {
                     goto representation_error;
-                } else if (ret) {
-                    goto cleanup;
                 }
+                LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
+
                 lydjson_maintain_children(parent, first_p, &node, lydctx->parse_opts & LYD_PARSE_ORDERED ? 1 : 0, ext);
 
                 /* move after the item(s) */
-                LY_CHECK_GOTO(ret = lyjson_ctx_next(lydctx->jsonctx, &status), cleanup);
+                r = lyjson_ctx_next(lydctx->jsonctx, &status);
+                LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
             } while (status != LYJSON_ARRAY_CLOSED);
 
             break;
@@ -1643,13 +1669,12 @@
         case LYS_ANYDATA:
         case LYS_ANYXML:
             /* process the value/object */
-            ret = lydjson_parse_instance(lydctx, parent, first_p, snode, ext, name, name_len, prefix, prefix_len,
+            r = lydjson_parse_instance(lydctx, parent, first_p, snode, ext, name, name_len, prefix, prefix_len,
                     &status, &node);
-            if (ret == LY_ENOT) {
+            if (r == LY_ENOT) {
                 goto representation_error;
-            } else if (ret) {
-                goto cleanup;
             }
+            LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
 
             if (snode->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)) {
                 /* rememeber the RPC/action/notification */
@@ -1669,7 +1694,8 @@
 
     if (!parse_subtree) {
         /* move after the item(s) */
-        LY_CHECK_GOTO(ret = lyjson_ctx_next(lydctx->jsonctx, &status), cleanup);
+        r = lyjson_ctx_next(lydctx->jsonctx, &status);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
     }
 
     /* success */
@@ -1678,12 +1704,16 @@
 representation_error:
     LOGVAL(ctx, LYVE_SYNTAX_JSON, "The %s \"%s\" is expected to be represented as JSON %s, but input data contains name/%s.",
             lys_nodetype2str(snode->nodetype), snode->name, expected, lyjson_token2str(status));
-    ret = LY_EVALID;
+    rc = LY_EVALID;
+    if (lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR) {
+        /* try to skip the invalid data */
+        lydjson_data_skip(lydctx->jsonctx);
+    }
 
 cleanup:
     free(value);
     lyd_free_tree(node);
-    return ret;
+    return rc;
 }
 
 /**
@@ -1745,7 +1775,7 @@
         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 rc = LY_SUCCESS;
+    LY_ERR r, rc = LY_SUCCESS;
     struct lyd_json_ctx *lydctx = NULL;
     enum LYJSON_PARSER_STATUS status;
 
@@ -1762,8 +1792,8 @@
 
     /* read subtree(s) */
     while (lydctx->jsonctx->in->current[0] && (status != LYJSON_OBJECT_CLOSED)) {
-        rc = lydjson_subtree_r(lydctx, parent, first_p, parsed);
-        LY_CHECK_GOTO(rc, cleanup);
+        r = lydjson_subtree_r(lydctx, parent, first_p, parsed);
+        LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
 
         status = lyjson_ctx_status(lydctx->jsonctx, 0);
 
@@ -1775,18 +1805,18 @@
     if ((int_opts & LYD_INTOPT_NO_SIBLINGS) && lydctx->jsonctx->in->current[0] &&
             (lyjson_ctx_status(lydctx->jsonctx, 0) != LYJSON_OBJECT_CLOSED)) {
         LOGVAL(ctx, LYVE_SYNTAX, "Unexpected sibling node.");
-        rc = LY_EVALID;
-        goto cleanup;
+        r = LY_EVALID;
+        LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
     }
     if ((int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION | LYD_INTOPT_NOTIF | LYD_INTOPT_REPLY)) && !lydctx->op_node) {
         LOGVAL(ctx, LYVE_DATA, "Missing the operation node.");
-        rc = LY_EVALID;
-        goto cleanup;
+        r = LY_EVALID;
+        LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
     }
 
     /* finish linking metadata */
-    rc = lydjson_metadata_finish(lydctx, parent ? lyd_node_child_p(parent) : first_p);
-    LY_CHECK_GOTO(rc, cleanup);
+    r = lydjson_metadata_finish(lydctx, parent ? lyd_node_child_p(parent) : first_p);
+    LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
 
     if (parse_opts & LYD_PARSE_SUBTREE) {
         /* check for a sibling object */
@@ -1806,7 +1836,7 @@
     assert(!(parse_opts & LYD_PARSE_ONLY) || !lydctx || (!lydctx->node_types.count && !lydctx->meta_types.count &&
             !lydctx->node_when.count));
 
-    if (rc) {
+    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;
diff --git a/src/parser_lyb.c b/src/parser_lyb.c
index dbdb8b0..783f496 100644
--- a/src/parser_lyb.c
+++ b/src/parser_lyb.c
@@ -1082,7 +1082,7 @@
 
     if (!(lybctx->parse_opts & LYD_PARSE_ONLY)) {
         /* new node validation, autodelete CANNOT occur, all nodes are new */
-        ret = lyd_validate_new(lyd_node_child_p(node), snode, NULL, NULL);
+        ret = lyd_validate_new(lyd_node_child_p(node), snode, NULL, 0, NULL);
         LY_CHECK_RET(ret);
 
         /* add any missing default children */
diff --git a/src/parser_xml.c b/src/parser_xml.c
index 5a929da..ccbb2a9 100644
--- a/src/parser_xml.c
+++ b/src/parser_xml.c
@@ -40,6 +40,9 @@
 #include "validation.h"
 #include "xml.h"
 
+static LY_ERR lydxml_subtree_r(struct lyd_xml_ctx *lydctx, struct lyd_node *parent, struct lyd_node **first_p,
+        struct ly_set *parsed);
+
 void
 lyd_xml_ctx_free(struct lyd_ctx *lydctx)
 {
@@ -430,7 +433,7 @@
  * @param[out] anchor Anchor to insert after in case of a list.
  */
 static void
-lydxml_get_hints_opaq(const char *name, size_t name_len, const char *value, size_t value_len, struct lyd_node *first,
+lydxml_get_hints_opaq(const char *name, size_t name_len, const char *value, size_t value_len, const struct lyd_node *first,
         const char *ns, uint32_t *hints, struct lyd_node **anchor)
 {
     struct lyd_node_opaq *opaq;
@@ -485,7 +488,7 @@
                 opaq->hints |= LYD_NODEHINT_LIST;
                 *hints |= LYD_NODEHINT_LIST;
             }
-            *anchor = first;
+            *anchor = (struct lyd_node *)first;
             break;
         }
     } while (first->prev->next);
@@ -506,7 +509,7 @@
  * @return LY_ERR on error.
  */
 static LY_ERR
-lydxml_subtree_snode(struct lyd_xml_ctx *lydctx, const struct lyd_node *parent, const char *prefix, size_t prefix_len,
+lydxml_subtree_get_snode(struct lyd_xml_ctx *lydctx, const struct lyd_node *parent, const char *prefix, size_t prefix_len,
         const char *name, size_t name_len, const struct lysc_node **snode, struct lysc_ext_instance **ext)
 {
     LY_ERR r;
@@ -604,34 +607,338 @@
 }
 
 /**
- * @brief Parse XML subtree.
+ * @brief Parse an XML opque node.
+ *
+ * @param[in] lydctx XML YANG data parser context.
+ * @param[in] sibling Existing sibling node, if any.
+ * @param[in] prefix Parsed node prefix.
+ * @param[in] prefix_len Length of @p prefix.
+ * @param[in] name Parsed node name.
+ * @param[in] name_len Length of @p name.
+ * @param[out] insert_anchor Optional anchor node for inserting this node.
+ * @param[out] node Created node.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+lydxml_subtree_opaq(struct lyd_xml_ctx *lydctx, const struct lyd_node *sibling, const char *prefix, uint32_t prefix_len,
+        const char *name, uint32_t name_len, struct lyd_node **insert_anchor, struct lyd_node **node)
+{
+    LY_ERR rc = LY_SUCCESS;
+    struct lyxml_ctx *xmlctx = lydctx->xmlctx;
+    const char *ns_uri;
+    const struct lyxml_ns *ns;
+    uint32_t hints;
+    void *val_prefix_data = NULL;
+    LY_VALUE_FORMAT format;
+
+    assert(lydctx->parse_opts & LYD_PARSE_OPAQ);
+
+    *node = NULL;
+
+    if (xmlctx->ws_only) {
+        /* ignore WS-only value */
+        if (xmlctx->dynamic) {
+            free((char *)xmlctx->value);
+        }
+        xmlctx->dynamic = 0;
+        xmlctx->value = "";
+        xmlctx->value_len = 0;
+        format = LY_VALUE_XML;
+    } else {
+        /* get value prefixes */
+        rc = ly_store_prefix_data(xmlctx->ctx, xmlctx->value, xmlctx->value_len, LY_VALUE_XML, &xmlctx->ns, &format,
+                &val_prefix_data);
+        LY_CHECK_GOTO(rc, cleanup);
+    }
+
+    /* get NS again, it may have been backed up and restored */
+    ns = lyxml_ns_get(&xmlctx->ns, prefix, prefix_len);
+    ns_uri = ns ? ns->uri : NULL;
+
+    /* get best-effort node hints */
+    lydxml_get_hints_opaq(name, name_len, xmlctx->value, xmlctx->value_len, sibling, ns_uri, &hints, insert_anchor);
+
+    /* create node */
+    rc = lyd_create_opaq(xmlctx->ctx, name, name_len, prefix, prefix_len, ns_uri, ns_uri ? strlen(ns_uri) : 0,
+            xmlctx->value, xmlctx->value_len, &xmlctx->dynamic, format, val_prefix_data, hints, node);
+    LY_CHECK_GOTO(rc, cleanup);
+    val_prefix_data = NULL;
+
+    /* parser next */
+    rc = lyxml_ctx_next(xmlctx);
+    LY_CHECK_GOTO(rc, cleanup);
+
+    /* process children */
+    while (xmlctx->status == LYXML_ELEMENT) {
+        rc = lydxml_subtree_r(lydctx, *node, lyd_node_child_p(*node), NULL);
+        LY_CHECK_GOTO(rc, cleanup);
+    }
+
+cleanup:
+    ly_free_prefix_data(format, val_prefix_data);
+    if (rc) {
+        lyd_free_tree(*node);
+        *node = NULL;
+    }
+    return rc;
+}
+
+/**
+ * @brief Parse an XML leaf/leaf-list node.
+ *
+ * @param[in] lydctx XML YANG data parser context.
+ * @param[in] parent Parent node, if any.
+ * @param[in] snode Schema node of the new node.
+ * @param[out] node Created node.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+lydxml_subtree_term(struct lyd_xml_ctx *lydctx, struct lyd_node *parent, const struct lysc_node *snode,
+        struct lyd_node **node)
+{
+    LY_ERR r, rc = LY_SUCCESS;
+    struct lyxml_ctx *xmlctx = lydctx->xmlctx;
+    struct lyd_node *anchor;
+
+    *node = NULL;
+
+    /* create node */
+    r = lyd_parser_create_term((struct lyd_ctx *)lydctx, snode, xmlctx->value, xmlctx->value_len, &xmlctx->dynamic,
+            LY_VALUE_XML, &xmlctx->ns, LYD_HINT_DATA, node);
+    LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
+
+    if (*node) {
+        LOG_LOCSET(snode, *node, NULL, NULL);
+    }
+
+    if (parent && (snode->flags & LYS_KEY)) {
+        /* check the key order, the anchor must never be a key */
+        anchor = lyd_insert_get_next_anchor(lyd_child(parent), *node);
+        if (anchor && anchor->schema && (anchor->schema->flags & LYS_KEY)) {
+            if (lydctx->parse_opts & LYD_PARSE_STRICT) {
+                LOGVAL(xmlctx->ctx, LYVE_DATA, "Invalid position of the key \"%s\" in a list.", snode->name);
+                r = LY_EVALID;
+                LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
+            } else {
+                LOGWRN(xmlctx->ctx, "Invalid position of the key \"%s\" in a list.", snode->name);
+            }
+        }
+    }
+
+    /* parser next */
+    r = lyxml_ctx_next(xmlctx);
+    LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
+
+    /* no children expected */
+    if (xmlctx->status == LYXML_ELEMENT) {
+        LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Child element \"%.*s\" inside a terminal node \"%s\" found.",
+                (int)xmlctx->name_len, xmlctx->name, snode->name);
+        r = LY_EVALID;
+        LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
+    }
+
+cleanup:
+    if (*node) {
+        LOG_LOCBACK(1, 1, 0, 0);
+    }
+    if (rc && (!(lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR) || (rc != LY_EVALID))) {
+        lyd_free_tree(*node);
+        *node = NULL;
+    }
+    return rc;
+}
+
+/**
+ * @brief Parse an XML inner node.
+ *
+ * @param[in] lydctx XML YANG data parser context.
+ * @param[in] snode Schema node of the new node.
+ * @param[in] ext Extension instance of @p snode, if any.
+ * @param[out] node Created node.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+lydxml_subtree_inner(struct lyd_xml_ctx *lydctx, const struct lysc_node *snode, const struct lysc_ext_instance *ext,
+        struct lyd_node **node)
+{
+    LY_ERR r, rc = LY_SUCCESS;
+    struct lyxml_ctx *xmlctx = lydctx->xmlctx;
+    uint32_t prev_parse_opts = lydctx->parse_opts;
+
+    *node = NULL;
+
+    if (!xmlctx->ws_only) {
+        /* value in inner node */
+        LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Text value \"%.*s\" inside an inner node \"%s\" found.",
+                (int)xmlctx->value_len, xmlctx->value, snode->name);
+        r = LY_EVALID;
+        LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
+    }
+
+    /* create node */
+    rc = lyd_create_inner(snode, node);
+    LY_CHECK_GOTO(rc, cleanup);
+
+    if (*node) {
+        LOG_LOCSET(snode, *node, NULL, NULL);
+    }
+
+    /* parser next */
+    rc = lyxml_ctx_next(xmlctx);
+    LY_CHECK_GOTO(rc, cleanup);
+
+    if (ext) {
+        /* only parse these extension data and validate afterwards */
+        lydctx->parse_opts |= LYD_PARSE_ONLY;
+    }
+
+    /* process children */
+    while (xmlctx->status == LYXML_ELEMENT) {
+        r = lydxml_subtree_r(lydctx, *node, lyd_node_child_p(*node), NULL);
+        LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
+    }
+
+    /* restore options */
+    lydctx->parse_opts = prev_parse_opts;
+
+    if (snode->nodetype == LYS_LIST) {
+        /* check all keys exist */
+        r = lyd_parse_check_keys(*node);
+        LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
+    }
+
+    if (!(lydctx->parse_opts & LYD_PARSE_ONLY)) {
+        /* new node validation, autodelete CANNOT occur, all nodes are new */
+        r = lyd_validate_new(lyd_node_child_p(*node), snode, NULL, lydctx->val_opts, NULL);
+        LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
+
+        /* add any missing default children */
+        r = lyd_new_implicit_r(*node, lyd_node_child_p(*node), NULL, NULL, &lydctx->node_when, &lydctx->node_types,
+                &lydctx->ext_node, (lydctx->val_opts & LYD_VALIDATE_NO_STATE) ? LYD_IMPLICIT_NO_STATE : 0, NULL);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
+    }
+
+    if (snode->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)) {
+        /* rememeber the RPC/action/notification */
+        lydctx->op_node = *node;
+    }
+
+cleanup:
+    if (*node) {
+        LOG_LOCBACK(1, 1, 0, 0);
+    }
+    lydctx->parse_opts = prev_parse_opts;
+    if (rc && (!(lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR) || (rc != LY_EVALID))) {
+        lyd_free_tree(*node);
+        *node = NULL;
+    }
+    return rc;
+}
+
+/**
+ * @brief Parse an XML anyxml/anydata node.
+ *
+ * @param[in] lydctx XML YANG data parser context.
+ * @param[in] snode Schema node of the new node.
+ * @param[in] ext Extension instance of @p snode, if any.
+ * @param[out] node Created node.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+lydxml_subtree_any(struct lyd_xml_ctx *lydctx, const struct lysc_node *snode, const struct lysc_ext_instance *ext,
+        struct lyd_node **node)
+{
+    LY_ERR r, rc = LY_SUCCESS;
+    struct lyxml_ctx *xmlctx = lydctx->xmlctx;
+    uint32_t prev_parse_opts = lydctx->parse_opts, prev_int_opts = lydctx->int_opts;
+    struct lyd_node *child = NULL;
+    char *val = NULL;
+
+    *node = NULL;
+
+    if ((snode->nodetype == LYS_ANYDATA) && !xmlctx->ws_only) {
+        /* value in anydata node, we expect a tree */
+        LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Text value \"%.*s\" inside an anydata node \"%s\" found.",
+                (int)xmlctx->value_len < 20 ? xmlctx->value_len : 20, xmlctx->value, snode->name);
+        r = LY_EVALID;
+        LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
+    }
+
+    if (!xmlctx->ws_only) {
+        /* use an arbitrary text value for anyxml */
+        val = strndup(xmlctx->value, xmlctx->value_len);
+        LY_CHECK_ERR_GOTO(!val, LOGMEM(xmlctx->ctx); rc = LY_EMEM, cleanup);
+
+        /* parser next */
+        r = lyxml_ctx_next(xmlctx);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
+
+        /* create node */
+        r = lyd_create_any(snode, val, LYD_ANYDATA_STRING, 1, node);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
+        val = NULL;
+    } else {
+        /* parser next */
+        r = lyxml_ctx_next(xmlctx);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
+
+        /* update options so that generic data can be parsed */
+        lydctx->parse_opts &= ~LYD_PARSE_STRICT;
+        lydctx->parse_opts |= LYD_PARSE_OPAQ | (ext ? LYD_PARSE_ONLY : 0);
+        lydctx->int_opts |= LYD_INTOPT_ANY | LYD_INTOPT_WITH_SIBLINGS;
+
+        /* parse any data tree */
+        while (xmlctx->status == LYXML_ELEMENT) {
+            r = lydxml_subtree_r(lydctx, NULL, &child, NULL);
+            LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
+        }
+
+        /* restore options */
+        lydctx->parse_opts = prev_parse_opts;
+        lydctx->int_opts = prev_int_opts;
+
+        /* create node */
+        r = lyd_create_any(snode, child, LYD_ANYDATA_DATATREE, 1, node);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
+        child = NULL;
+    }
+
+cleanup:
+    lydctx->parse_opts = prev_parse_opts;
+    lydctx->int_opts = prev_int_opts;
+    free(val);
+    lyd_free_tree(child);
+    if (rc && (!(lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR) || (rc != LY_EVALID))) {
+        lyd_free_tree(*node);
+        *node = NULL;
+    }
+    return rc;
+}
+
+/**
+ * @brief Parse an XML subtree, recursively.
  *
  * @param[in] lydctx XML YANG data parser context.
  * @param[in,out] parent Parent node where the children are inserted. NULL in case of parsing top-level elements.
- * @param[in,out] first_p Pointer to the first (@p parent or top-level) child. In case there were already some siblings,
- * this may point to a previously existing node.
+ * @param[in,out] first_p Pointer to the first (@p parent or top-level) child.
  * @param[in,out] parsed Optional set to add all the parsed siblings into.
  * @return LY_ERR value.
  */
 static LY_ERR
 lydxml_subtree_r(struct lyd_xml_ctx *lydctx, struct lyd_node *parent, struct lyd_node **first_p, struct ly_set *parsed)
 {
-    LY_ERR ret = LY_SUCCESS;
-    const char *prefix, *name, *ns_uri;
+    LY_ERR r, rc = LY_SUCCESS;
+    const char *prefix, *name;
     size_t prefix_len, name_len;
     struct lyxml_ctx *xmlctx;
     const struct ly_ctx *ctx;
-    const struct lyxml_ns *ns;
     struct lyd_meta *meta = NULL;
     struct lyd_attr *attr = NULL;
     const struct lysc_node *snode;
     struct lysc_ext_instance *ext;
-    uint32_t prev_parse_opts, orig_parse_opts, prev_int_opts, hints;
-    struct lyd_node *node = NULL, *anchor, *insert_anchor = NULL;
-    void *val_prefix_data = NULL;
-    LY_VALUE_FORMAT format;
+    uint32_t orig_parse_opts;
+    struct lyd_node *node = NULL, *insert_anchor = NULL;
     ly_bool parse_subtree;
-    char *val;
 
     assert(parent || first_p);
 
@@ -652,237 +959,85 @@
     name_len = xmlctx->name_len;
 
     /* parser next */
-    LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), error);
+    rc = lyxml_ctx_next(xmlctx);
+    LY_CHECK_GOTO(rc, cleanup);
 
     /* get the schema node */
-    LY_CHECK_GOTO(ret = lydxml_subtree_snode(lydctx, parent, prefix, prefix_len, name, name_len, &snode, &ext), error);
+    rc = lydxml_subtree_get_snode(lydctx, parent, prefix, prefix_len, name, name_len, &snode, &ext);
+    LY_CHECK_GOTO(rc, cleanup);
 
     if (!snode && !(lydctx->parse_opts & LYD_PARSE_OPAQ)) {
         LOGVRB("Skipping parsing of unknown node \"%.*s\".", name_len, name);
 
         /* skip element with children */
-        LY_CHECK_GOTO(ret = lydxml_data_skip(xmlctx), error);
-        return LY_SUCCESS;
+        rc = lydxml_data_skip(xmlctx);
+        goto cleanup;
     }
 
     /* create metadata/attributes */
     if (xmlctx->status == LYXML_ATTRIBUTE) {
         if (snode) {
-            ret = lydxml_metadata(lydctx, snode, &meta);
-            LY_CHECK_GOTO(ret, error);
+            rc = lydxml_metadata(lydctx, snode, &meta);
+            LY_CHECK_GOTO(rc, cleanup);
         } else {
             assert(lydctx->parse_opts & LYD_PARSE_OPAQ);
-            ret = lydxml_attrs(xmlctx, &attr);
-            LY_CHECK_GOTO(ret, error);
+            rc = lydxml_attrs(xmlctx, &attr);
+            LY_CHECK_GOTO(rc, cleanup);
         }
     }
 
     assert(xmlctx->status == LYXML_ELEM_CONTENT);
     if (!snode) {
-        assert(lydctx->parse_opts & LYD_PARSE_OPAQ);
-
-        if (xmlctx->ws_only) {
-            /* ignore WS-only value */
-            if (xmlctx->dynamic) {
-                free((char *)xmlctx->value);
-            }
-            xmlctx->dynamic = 0;
-            xmlctx->value = "";
-            xmlctx->value_len = 0;
-            format = LY_VALUE_XML;
-        } else {
-            /* get value prefixes */
-            ret = ly_store_prefix_data(xmlctx->ctx, xmlctx->value, xmlctx->value_len, LY_VALUE_XML,
-                    &xmlctx->ns, &format, &val_prefix_data);
-            LY_CHECK_GOTO(ret, error);
-        }
-
-        /* get NS again, it may have been backed up and restored */
-        ns = lyxml_ns_get(&xmlctx->ns, prefix, prefix_len);
-        ns_uri = ns ? ns->uri : NULL;
-
-        /* get best-effort node hints */
-        lydxml_get_hints_opaq(name, name_len, xmlctx->value, xmlctx->value_len, parent ? lyd_child(parent) : *first_p,
-                ns_uri, &hints, &insert_anchor);
-
-        /* create node */
-        ret = lyd_create_opaq(ctx, name, name_len, prefix, prefix_len, ns_uri, ns_uri ? strlen(ns_uri) : 0,
-                xmlctx->value, xmlctx->value_len, &xmlctx->dynamic, format, val_prefix_data, hints, &node);
-        LY_CHECK_GOTO(ret, error);
-
-        /* parser next */
-        LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), error);
-
-        /* process children */
-        while (xmlctx->status == LYXML_ELEMENT) {
-            ret = lydxml_subtree_r(lydctx, node, lyd_node_child_p(node), NULL);
-            LY_CHECK_GOTO(ret, error);
-        }
+        /* opaque */
+        r = lydxml_subtree_opaq(lydctx, parent ? lyd_child(parent) : *first_p, prefix, prefix_len, name, name_len,
+                &insert_anchor, &node);
     } else if (snode->nodetype & LYD_NODE_TERM) {
-        /* create node */
-        LY_CHECK_GOTO(ret = lyd_parser_create_term((struct lyd_ctx *)lydctx, snode, xmlctx->value, xmlctx->value_len,
-                &xmlctx->dynamic, LY_VALUE_XML, &xmlctx->ns, LYD_HINT_DATA, &node), error);
-        LOG_LOCSET(snode, node, NULL, NULL);
-
-        if (parent && (node->schema->flags & LYS_KEY)) {
-            /* check the key order, the anchor must never be a key */
-            anchor = lyd_insert_get_next_anchor(lyd_child(parent), node);
-            if (anchor && anchor->schema && (anchor->schema->flags & LYS_KEY)) {
-                if (lydctx->parse_opts & LYD_PARSE_STRICT) {
-                    LOGVAL(ctx, LYVE_DATA, "Invalid position of the key \"%s\" in a list.", node->schema->name);
-                    ret = LY_EVALID;
-                    goto error;
-                } else {
-                    LOGWRN(ctx, "Invalid position of the key \"%s\" in a list.", node->schema->name);
-                }
-            }
-        }
-
-        /* parser next */
-        LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), error);
-
-        /* no children expected */
-        if (xmlctx->status == LYXML_ELEMENT) {
-            LOGVAL(ctx, LYVE_SYNTAX, "Child element \"%.*s\" inside a terminal node \"%s\" found.",
-                    (int)xmlctx->name_len, xmlctx->name, snode->name);
-            ret = LY_EVALID;
-            goto error;
-        }
+        /* term */
+        r = lydxml_subtree_term(lydctx, parent, snode, &node);
     } else if (snode->nodetype & LYD_NODE_INNER) {
-        if (!xmlctx->ws_only) {
-            /* value in inner node */
-            LOGVAL(ctx, LYVE_SYNTAX, "Text value \"%.*s\" inside an inner node \"%s\" found.",
-                    (int)xmlctx->value_len, xmlctx->value, snode->name);
-            ret = LY_EVALID;
-            goto error;
-        }
-
-        /* create node */
-        ret = lyd_create_inner(snode, &node);
-        LY_CHECK_GOTO(ret, error);
-
-        LOG_LOCSET(snode, node, NULL, NULL);
-
-        /* parser next */
-        LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), error);
-
-        prev_parse_opts = lydctx->parse_opts;
-        if (ext) {
-            /* only parse these extension data and validate afterwards */
-            lydctx->parse_opts |= LYD_PARSE_ONLY;
-        }
-
-        /* process children */
-        while (xmlctx->status == LYXML_ELEMENT) {
-            ret = lydxml_subtree_r(lydctx, node, lyd_node_child_p(node), NULL);
-            LY_CHECK_GOTO(ret, error);
-        }
-
-        /* restore options */
-        lydctx->parse_opts = prev_parse_opts;
-
-        if (snode->nodetype == LYS_LIST) {
-            /* check all keys exist */
-            LY_CHECK_GOTO(ret = lyd_parse_check_keys(node), error);
-        }
-
-        if (!(lydctx->parse_opts & LYD_PARSE_ONLY)) {
-            /* new node validation, autodelete CANNOT occur, all nodes are new */
-            ret = lyd_validate_new(lyd_node_child_p(node), snode, NULL, NULL);
-            LY_CHECK_GOTO(ret, error);
-
-            /* add any missing default children */
-            ret = lyd_new_implicit_r(node, lyd_node_child_p(node), NULL, NULL, &lydctx->node_when, &lydctx->node_types,
-                    &lydctx->ext_node, (lydctx->val_opts & LYD_VALIDATE_NO_STATE) ? LYD_IMPLICIT_NO_STATE : 0, NULL);
-            LY_CHECK_GOTO(ret, error);
-        }
-
-        if (snode->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)) {
-            /* rememeber the RPC/action/notification */
-            lydctx->op_node = node;
-        }
+        /* inner */
+        r = lydxml_subtree_inner(lydctx, snode, ext, &node);
     } else if (snode->nodetype & LYD_NODE_ANY) {
-        if ((snode->nodetype == LYS_ANYDATA) && !xmlctx->ws_only) {
-            /* value in anydata node, we expect a tree */
-            LOGVAL(ctx, LYVE_SYNTAX, "Text value \"%.*s\" inside an anydata node \"%s\" found.",
-                    (int)xmlctx->value_len < 20 ? xmlctx->value_len : 20, xmlctx->value, snode->name);
-            ret = LY_EVALID;
-            goto error;
-        }
-
-        if (!xmlctx->ws_only) {
-            /* use an arbitrary text value for anyxml */
-            val = strndup(xmlctx->value, xmlctx->value_len);
-            LY_CHECK_ERR_GOTO(!val, LOGMEM(xmlctx->ctx); ret = LY_EMEM, error);
-
-            /* parser next */
-            LY_CHECK_ERR_GOTO(ret = lyxml_ctx_next(xmlctx), free(val), error);
-
-            /* create node */
-            ret = lyd_create_any(snode, val, LYD_ANYDATA_STRING, 1, &node);
-            LY_CHECK_ERR_GOTO(ret, free(val), error);
-        } else {
-            /* parser next */
-            LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), error);
-
-            /* update options so that generic data can be parsed */
-            prev_parse_opts = lydctx->parse_opts;
-            lydctx->parse_opts &= ~LYD_PARSE_STRICT;
-            lydctx->parse_opts |= LYD_PARSE_OPAQ | (ext ? LYD_PARSE_ONLY : 0);
-            prev_int_opts = lydctx->int_opts;
-            lydctx->int_opts |= LYD_INTOPT_ANY | LYD_INTOPT_WITH_SIBLINGS;
-
-            /* parse any data tree */
-            anchor = NULL;
-            while (xmlctx->status == LYXML_ELEMENT) {
-                ret = lydxml_subtree_r(lydctx, NULL, &anchor, NULL);
-                if (ret) {
-                    lyd_free_siblings(anchor);
-                    break;
-                }
-            }
-
-            /* restore options */
-            lydctx->parse_opts = prev_parse_opts;
-            lydctx->int_opts = prev_int_opts;
-
-            LY_CHECK_GOTO(ret, error);
-
-            /* create node */
-            ret = lyd_create_any(snode, anchor, LYD_ANYDATA_DATATREE, 1, &node);
-            LY_CHECK_GOTO(ret, error);
-        }
+        /* any */
+        r = lydxml_subtree_any(lydctx, snode, ext, &node);
     }
-    assert(node);
+    LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
+    LY_CHECK_GOTO(!node, cleanup);
 
     if (snode) {
         /* add/correct flags */
-        LY_CHECK_GOTO(ret = lyd_parse_set_data_flags(node, &meta, (struct lyd_ctx *)lydctx, ext), error);
+        r = lyd_parse_set_data_flags(node, &meta, (struct lyd_ctx *)lydctx, ext);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
 
         if (!(lydctx->parse_opts & LYD_PARSE_ONLY)) {
             /* store for ext instance node validation, if needed */
-            LY_CHECK_GOTO(ret = lyd_validate_node_ext(node, &lydctx->ext_node), error);
+            r = lyd_validate_node_ext(node, &lydctx->ext_node);
+            LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
         }
     }
 
     /* parser next */
     assert(xmlctx->status == LYXML_ELEM_CLOSE);
     if (!parse_subtree) {
-        LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), error);
+        r = lyxml_ctx_next(xmlctx);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
     }
 
     /* add metadata/attributes */
     if (snode) {
         lyd_insert_meta(node, meta, 0);
+        meta = NULL;
     } else {
         lyd_insert_attr(node, attr);
+        attr = NULL;
     }
 
     /* insert, keep first pointer correct */
     if (insert_anchor) {
         lyd_insert_after(insert_anchor, node);
     } else if (ext) {
-        LY_CHECK_GOTO(ret = lyplg_ext_insert(parent, node), error);
+        r = lyplg_ext_insert(parent, node);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
     } else {
         lyd_insert_node(parent, first_p, node, lydctx->parse_opts & LYD_PARSE_ORDERED ? 1 : 0);
     }
@@ -895,17 +1050,11 @@
         ly_set_add(parsed, node, 1, NULL);
     }
 
+cleanup:
     lydctx->parse_opts = orig_parse_opts;
-    LOG_LOCBACK(node ? 1 : 0, node ? 1 : 0, 0, 0);
-    return LY_SUCCESS;
-
-error:
-    lydctx->parse_opts = orig_parse_opts;
-    LOG_LOCBACK(node ? 1 : 0, node ? 1 : 0, 0, 0);
     lyd_free_meta_siblings(meta);
     lyd_free_attr_siblings(ctx, attr);
-    lyd_free_tree(node);
-    return ret;
+    return rc;
 }
 
 /**
@@ -987,7 +1136,7 @@
         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 rc = LY_SUCCESS;
+    LY_ERR r, rc = LY_SUCCESS;
     struct lyd_xml_ctx *lydctx;
     ly_bool parsed_data_nodes = 0;
     enum LYXML_PARSER_STATUS status;
@@ -1011,7 +1160,9 @@
 
     /* parse XML data */
     while (lydctx->xmlctx->status == LYXML_ELEMENT) {
-        LY_CHECK_GOTO(rc = lydxml_subtree_r(lydctx, parent, first_p, parsed), cleanup);
+        r = lydxml_subtree_r(lydctx, parent, first_p, parsed);
+        LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
+
         parsed_data_nodes = 1;
 
         if (!(int_opts & LYD_INTOPT_WITH_SIBLINGS)) {
@@ -1022,13 +1173,13 @@
     /* check final state */
     if ((int_opts & LYD_INTOPT_NO_SIBLINGS) && (lydctx->xmlctx->status == LYXML_ELEMENT)) {
         LOGVAL(ctx, LYVE_SYNTAX, "Unexpected sibling node.");
-        rc = LY_EVALID;
-        goto cleanup;
+        r = LY_EVALID;
+        LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
     }
     if ((int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION | LYD_INTOPT_NOTIF | LYD_INTOPT_REPLY)) && !lydctx->op_node) {
         LOGVAL(ctx, LYVE_DATA, "Missing the operation node.");
-        rc = LY_EVALID;
-        goto cleanup;
+        r = LY_EVALID;
+        LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
     }
 
     if (!parsed_data_nodes) {
@@ -1051,7 +1202,7 @@
     assert(!(parse_opts & LYD_PARSE_ONLY) || (!lydctx->node_types.count && !lydctx->meta_types.count &&
             !lydctx->node_when.count));
 
-    if (rc) {
+    if (rc && (!(lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR) || (rc != LY_EVALID))) {
         lyd_xml_ctx_free((struct lyd_ctx *)lydctx);
     } else {
         *lydctx_p = (struct lyd_ctx *)lydctx;
diff --git a/src/tree_data.c b/src/tree_data.c
index 02dac9a..4abb79f 100644
--- a/src/tree_data.c
+++ b/src/tree_data.c
@@ -96,7 +96,7 @@
 lyd_parse(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, struct lyd_node *parent, struct lyd_node **first_p,
         struct ly_in *in, LYD_FORMAT format, uint32_t parse_opts, uint32_t val_opts, struct lyd_node **op)
 {
-    LY_ERR rc = LY_SUCCESS;
+    LY_ERR r, rc = LY_SUCCESS;
     struct lyd_ctx *lydctx = NULL;
     struct ly_set parsed = {0};
     struct lyd_node *first;
@@ -121,23 +121,23 @@
     /* parse the data */
     switch (format) {
     case LYD_XML:
-        rc = lyd_parse_xml(ctx, ext, parent, first_p, in, parse_opts, val_opts, int_opts, &parsed,
+        r = lyd_parse_xml(ctx, ext, parent, first_p, in, parse_opts, val_opts, int_opts, &parsed,
                 &subtree_sibling, &lydctx);
         break;
     case LYD_JSON:
-        rc = lyd_parse_json(ctx, ext, parent, first_p, in, parse_opts, val_opts, int_opts, &parsed,
+        r = lyd_parse_json(ctx, ext, parent, first_p, in, parse_opts, val_opts, int_opts, &parsed,
                 &subtree_sibling, &lydctx);
         break;
     case LYD_LYB:
-        rc = lyd_parse_lyb(ctx, ext, parent, first_p, in, parse_opts, val_opts, int_opts, &parsed,
+        r = lyd_parse_lyb(ctx, ext, parent, first_p, in, parse_opts, val_opts, int_opts, &parsed,
                 &subtree_sibling, &lydctx);
         break;
     case LYD_UNKNOWN:
         LOGARG(ctx, format);
-        rc = LY_EINVAL;
+        r = LY_EINVAL;
         break;
     }
-    LY_CHECK_GOTO(rc, cleanup);
+    LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
 
     if (parent) {
         /* get first top-level sibling */
diff --git a/src/validation.c b/src/validation.c
index 3ee600e..b629008 100644
--- a/src/validation.c
+++ b/src/validation.c
@@ -40,6 +40,22 @@
 #include "tree_schema_internal.h"
 #include "xpath.h"
 
+/**
+ * @brief Check validation error taking into account multi-error validation.
+ *
+ * @param[in] r Local return value.
+ * @param[in] err_cmd Command to perform on any error.
+ * @param[in] val_opts Validation options.
+ * @param[in] label Label to go to on fatal error.
+ */
+#define LY_VAL_ERR_GOTO(r, err_cmd, val_opts, label) \
+        if (r) { \
+            err_cmd; \
+            if ((r != LY_EVALID) || !(val_opts & LYD_VALIDATE_MULTI_ERROR)) { \
+                goto label; \
+            } \
+        }
+
 LY_ERR
 lyd_val_diff_add(const struct lyd_node *node, enum lyd_diff_op op, struct lyd_node **diff)
 {
@@ -126,7 +142,7 @@
 lyd_validate_node_when(const struct lyd_node *tree, const struct lyd_node *node, const struct lysc_node *schema,
         uint32_t xpath_options, const struct lysc_when **disabled)
 {
-    LY_ERR ret;
+    LY_ERR r;
     const struct lyd_node *ctx_node;
     struct lyxp_set xp_set;
     LY_ARRAY_COUNT_TYPE u;
@@ -152,12 +168,12 @@
 
             /* evaluate when */
             memset(&xp_set, 0, sizeof xp_set);
-            ret = lyxp_eval(LYD_CTX(node), when->cond, schema->module, LY_VALUE_SCHEMA_RESOLVED, when->prefixes,
+            r = lyxp_eval(LYD_CTX(node), when->cond, schema->module, LY_VALUE_SCHEMA_RESOLVED, when->prefixes,
                     ctx_node, ctx_node, tree, NULL, &xp_set, LYXP_SCHEMA | xpath_options);
             lyxp_set_cast(&xp_set, LYXP_SET_BOOLEAN);
 
             /* return error or LY_EINCOMPLETE for dependant unresolved when */
-            LY_CHECK_RET(ret);
+            LY_CHECK_RET(r);
 
             if (!xp_set.val.bln) {
                 /* false when */
@@ -180,6 +196,7 @@
  * If set, it is expected @p tree should point to the first node of @p mod. Otherwise it will simply be
  * the first top-level sibling.
  * @param[in] node_when Set with nodes with "when" conditions.
+ * @param[in] val_opts Validation options.
  * @param[in] xpath_options Additional XPath options to use.
  * @param[in,out] node_types Set with nodes with unresolved types, remove any with false "when" parents.
  * @param[in,out] diff Validation diff.
@@ -187,10 +204,10 @@
  * @return LY_ERR value on error.
  */
 static LY_ERR
-lyd_validate_unres_when(struct lyd_node **tree, const struct lys_module *mod, struct ly_set *node_when,
+lyd_validate_unres_when(struct lyd_node **tree, const struct lys_module *mod, struct ly_set *node_when, uint32_t val_opts,
         uint32_t xpath_options, struct ly_set *node_types, struct lyd_node **diff)
 {
-    LY_ERR rc, r;
+    LY_ERR rc = LY_SUCCESS, r;
     uint32_t i, idx;
     const struct lysc_when *disabled;
     struct lyd_node *node = NULL, *elem;
@@ -215,7 +232,8 @@
                     lyd_del_move_root(tree, node, mod);
                     if (diff) {
                         /* add into diff */
-                        LY_CHECK_GOTO(rc = lyd_val_diff_add(node, LYD_DIFF_OP_DELETE, diff), error);
+                        r = lyd_val_diff_add(node, LYD_DIFF_OP_DELETE, diff);
+                        LY_CHECK_ERR_GOTO(r, rc = r, error);
                     }
 
                     /* remove from node types set, if present */
@@ -225,7 +243,8 @@
                             if ((elem->schema->nodetype & LYD_NODE_TERM) &&
                                     ((struct lysc_node_leaf *)elem->schema)->type->plugin->validate &&
                                     ly_set_contains(node_types, elem, &idx)) {
-                                LY_CHECK_GOTO(rc = ly_set_rm_index(node_types, idx, NULL), error);
+                                r = ly_set_rm_index(node_types, idx, NULL);
+                                LY_CHECK_ERR_GOTO(r, rc = r, error);
                             }
                             LYD_TREE_DFS_END(node, elem);
                         }
@@ -236,8 +255,8 @@
                 } else {
                     /* invalid data */
                     LOGVAL(LYD_CTX(node), LY_VCODE_NOWHEN, disabled->cond->expr);
-                    rc = LY_EVALID;
-                    goto error;
+                    r = LY_EVALID;
+                    LY_VAL_ERR_GOTO(r, rc = r, val_opts, error);
                 }
             } else {
                 /* when true */
@@ -248,14 +267,13 @@
             ly_set_rm_index_ordered(node_when, i, NULL);
         } else if (r != LY_EINCOMPLETE) {
             /* error */
-            rc = r;
-            goto error;
+            LY_VAL_ERR_GOTO(r, rc = r, val_opts, error);
         }
 
         LOG_LOCBACK(1, 1, 0, 0);
     } while (i);
 
-    return LY_SUCCESS;
+    return rc;
 
 error:
     LOG_LOCBACK(1, 1, 0, 0);
@@ -267,7 +285,7 @@
         uint32_t when_xp_opts, struct ly_set *node_types, struct ly_set *meta_types, struct ly_set *ext_node,
         struct ly_set *ext_val, uint32_t val_opts, struct lyd_node **diff)
 {
-    LY_ERR ret = LY_SUCCESS;
+    LY_ERR r, rc = LY_SUCCESS;
     uint32_t i;
 
     if (ext_val && ext_val->count) {
@@ -279,8 +297,8 @@
             struct lyd_ctx_ext_val *ext_v = ext_val->objs[i];
 
             /* validate extension data */
-            ret = ext_v->ext->def->plugin->validate(ext_v->ext, ext_v->sibling, *tree, data_type, val_opts, diff);
-            LY_CHECK_RET(ret);
+            r = ext_v->ext->def->plugin->validate(ext_v->ext, ext_v->sibling, *tree, data_type, val_opts, diff);
+            LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
 
             /* remove this item from the set */
             ly_set_rm_index(ext_val, i, free);
@@ -296,8 +314,8 @@
             struct lyd_ctx_ext_node *ext_n = ext_node->objs[i];
 
             /* validate the node */
-            ret = ext_n->ext->def->plugin->node(ext_n->ext, ext_n->node, val_opts);
-            LY_CHECK_RET(ret);
+            r = ext_n->ext->def->plugin->node(ext_n->ext, ext_n->node, val_opts);
+            LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
 
             /* remove this item from the set */
             ly_set_rm_index(ext_node, i, free);
@@ -310,7 +328,9 @@
 
         do {
             prev_count = node_when->count;
-            LY_CHECK_RET(lyd_validate_unres_when(tree, mod, node_when, when_xp_opts, node_types, diff));
+            r = lyd_validate_unres_when(tree, mod, node_when, val_opts, when_xp_opts, node_types, diff);
+            LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
+
             /* there must have been some when conditions resolved */
         } while (prev_count > node_when->count);
 
@@ -329,9 +349,9 @@
 
             /* resolve the value of the node */
             LOG_LOCSET(NULL, &node->node, NULL, NULL);
-            ret = lyd_value_validate_incomplete(LYD_CTX(node), type, &node->value, &node->node, *tree);
+            r = lyd_value_validate_incomplete(LYD_CTX(node), type, &node->value, &node->node, *tree);
             LOG_LOCBACK(0, 1, 0, 0);
-            LY_CHECK_RET(ret);
+            LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
 
             /* remove this node from the set */
             ly_set_rm_index(node_types, i, NULL);
@@ -349,15 +369,16 @@
 
             /* validate and store the value of the metadata */
             lyplg_ext_get_storage(meta->annotation, LY_STMT_TYPE, sizeof type, (const void **)&type);
-            ret = lyd_value_validate_incomplete(LYD_CTX(meta->parent), type, &meta->value, meta->parent, *tree);
-            LY_CHECK_RET(ret);
+            r = lyd_value_validate_incomplete(LYD_CTX(meta->parent), type, &meta->value, meta->parent, *tree);
+            LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
 
             /* remove this attr from the set */
             ly_set_rm_index(meta_types, i, NULL);
         } while (i);
     }
 
-    return ret;
+cleanup:
+    return rc;
 }
 
 /**
@@ -733,40 +754,46 @@
  * @param[in,out] first First sibling.
  * @param[in] sparent Schema parent of the siblings, NULL for top-level siblings.
  * @param[in] mod Module of the siblings, NULL for nested siblings.
+ * @param[in] val_opts Validation options.
  * @param[in,out] diff Validation diff.
  * @return LY_ERR value.
  */
 static LY_ERR
 lyd_validate_choice_r(struct lyd_node **first, const struct lysc_node *sparent, const struct lys_module *mod,
-        struct lyd_node **diff)
+        uint32_t val_opts, struct lyd_node **diff)
 {
+    LY_ERR r, rc = LY_SUCCESS;
     const struct lysc_node *snode = NULL;
 
     while (*first && (snode = lys_getnext(snode, sparent, mod ? mod->compiled : NULL, LYS_GETNEXT_WITHCHOICE))) {
         /* check case duplicites */
         if (snode->nodetype == LYS_CHOICE) {
-            LY_CHECK_RET(lyd_validate_cases(first, mod, (struct lysc_node_choice *)snode, diff));
+            r = lyd_validate_cases(first, mod, (struct lysc_node_choice *)snode, diff);
+            LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
 
             /* check for nested choice */
-            LY_CHECK_RET(lyd_validate_choice_r(first, snode, mod, diff));
+            r = lyd_validate_choice_r(first, snode, mod, val_opts, diff);
+            LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
         }
     }
 
-    return LY_SUCCESS;
+cleanup:
+    return rc;
 }
 
 LY_ERR
 lyd_validate_new(struct lyd_node **first, const struct lysc_node *sparent, const struct lys_module *mod,
-        struct lyd_node **diff)
+        uint32_t val_opts, struct lyd_node **diff)
 {
-    LY_ERR r;
+    LY_ERR r, rc = LY_SUCCESS;
     struct lyd_node *node;
     const struct lysc_node *last_dflt_schema = NULL;
 
     assert(first && (sparent || mod));
 
     /* validate choices */
-    LY_CHECK_RET(lyd_validate_choice_r(first, sparent, mod, diff));
+    r = lyd_validate_choice_r(first, sparent, mod, val_opts, diff);
+    LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
 
     node = *first;
     while (node) {
@@ -800,7 +827,7 @@
             LOG_LOCSET(NULL, node, NULL, NULL);
             r = lyd_validate_duplicates(*first, node);
             LOG_LOCBACK(0, 1, 0, 0);
-            LY_CHECK_RET(r);
+            LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
 
             /* this node is valid */
             node->flags &= ~LYD_NEW;
@@ -817,7 +844,8 @@
         node = node->next;
     }
 
-    return LY_SUCCESS;
+cleanup:
+    return rc;
 }
 
 /**
@@ -833,7 +861,7 @@
 lyd_validate_dummy_when(const struct lyd_node *first, const struct lyd_node *parent, const struct lysc_node *snode,
         const struct lysc_when **disabled)
 {
-    LY_ERR ret = LY_SUCCESS;
+    LY_ERR rc = LY_SUCCESS;
     struct lyd_node *tree, *dummy = NULL;
     uint32_t xp_opts;
 
@@ -850,8 +878,8 @@
     }
 
     /* create dummy opaque node */
-    ret = lyd_new_opaq((struct lyd_node *)parent, snode->module->ctx, snode->name, NULL, NULL, snode->module->name, &dummy);
-    LY_CHECK_GOTO(ret, cleanup);
+    rc = lyd_new_opaq((struct lyd_node *)parent, snode->module->ctx, snode->name, NULL, NULL, snode->module->name, &dummy);
+    LY_CHECK_GOTO(rc, cleanup);
 
     /* connect it if needed */
     if (!parent) {
@@ -871,20 +899,20 @@
     }
 
     /* evaluate all when */
-    ret = lyd_validate_node_when(tree, dummy, snode, xp_opts, disabled);
-    if (ret == LY_EINCOMPLETE) {
+    rc = lyd_validate_node_when(tree, dummy, snode, xp_opts, disabled);
+    if (rc == LY_EINCOMPLETE) {
         /* all other when must be resolved by now */
         LOGINT(snode->module->ctx);
-        ret = LY_EINT;
+        rc = LY_EINT;
         goto cleanup;
-    } else if (ret) {
+    } else if (rc) {
         /* error */
         goto cleanup;
     }
 
 cleanup:
     lyd_free_tree(dummy);
-    return ret;
+    return rc;
 }
 
 /**
@@ -1268,7 +1296,7 @@
 lyd_validate_siblings_schema_r(const struct lyd_node *first, const struct lyd_node *parent,
         const struct lysc_node *sparent, const struct lysc_module *mod, uint32_t val_opts, uint32_t int_opts)
 {
-    LY_ERR ret = LY_SUCCESS;
+    LY_ERR r, rc = LY_SUCCESS;
     const struct lysc_node *snode = NULL, *scase;
     struct lysc_node_list *slist;
     struct lysc_node_leaflist *sllist;
@@ -1288,28 +1316,28 @@
         if (snode->nodetype == LYS_LIST) {
             slist = (struct lysc_node_list *)snode;
             if (slist->min || slist->max) {
-                ret = lyd_validate_minmax(first, parent, snode, slist->min, slist->max);
-                LY_CHECK_GOTO(ret, error);
+                r = lyd_validate_minmax(first, parent, snode, slist->min, slist->max);
+                LY_VAL_ERR_GOTO(r, rc = r, val_opts, error);
             }
         } else if (snode->nodetype == LYS_LEAFLIST) {
             sllist = (struct lysc_node_leaflist *)snode;
             if (sllist->min || sllist->max) {
-                ret = lyd_validate_minmax(first, parent, snode, sllist->min, sllist->max);
-                LY_CHECK_GOTO(ret, error);
+                r = lyd_validate_minmax(first, parent, snode, sllist->min, sllist->max);
+                LY_VAL_ERR_GOTO(r, rc = r, val_opts, error);
             }
 
         } else if (snode->flags & LYS_MAND_TRUE) {
             /* check generic mandatory existence */
-            ret = lyd_validate_mandatory(first, parent, snode);
-            LY_CHECK_GOTO(ret, error);
+            r = lyd_validate_mandatory(first, parent, snode);
+            LY_VAL_ERR_GOTO(r, rc = r, val_opts, error);
         }
 
         /* check unique */
         if (snode->nodetype == LYS_LIST) {
             slist = (struct lysc_node_list *)snode;
             if (slist->uniques) {
-                ret = lyd_validate_unique(first, snode, (const struct lysc_node_leaf ***)slist->uniques);
-                LY_CHECK_GOTO(ret, error);
+                r = lyd_validate_unique(first, snode, (const struct lysc_node_leaf ***)slist->uniques);
+                LY_VAL_ERR_GOTO(r, rc = r, val_opts, error);
             }
         }
 
@@ -1318,8 +1346,8 @@
             LY_LIST_FOR(lysc_node_child(snode), scase) {
                 if (lys_getnext_data(NULL, first, NULL, scase, NULL)) {
                     /* validate only this case */
-                    ret = lyd_validate_siblings_schema_r(first, parent, scase, mod, val_opts, int_opts);
-                    LY_CHECK_GOTO(ret, error);
+                    r = lyd_validate_siblings_schema_r(first, parent, scase, mod, val_opts, int_opts);
+                    LY_VAL_ERR_GOTO(r, rc = r, val_opts, error);
                     break;
                 }
             }
@@ -1328,11 +1356,11 @@
         LOG_LOCBACK(1, 0, 0, 0);
     }
 
-    return LY_SUCCESS;
+    return rc;
 
 error:
     LOG_LOCBACK(1, 0, 0, 0);
-    return ret;
+    return rc;
 }
 
 /**
@@ -1360,14 +1388,15 @@
  * @brief Validate must conditions of a data node.
  *
  * @param[in] node Node to validate.
+ * @param[in] val_opts Validation options.
  * @param[in] int_opts Internal parser options.
  * @param[in] xpath_options Additional XPath options to use.
  * @return LY_ERR value.
  */
 static LY_ERR
-lyd_validate_must(const struct lyd_node *node, uint32_t int_opts, uint32_t xpath_options)
+lyd_validate_must(const struct lyd_node *node, uint32_t val_opts, uint32_t int_opts, uint32_t xpath_options)
 {
-    LY_ERR ret;
+    LY_ERR r, rc = LY_SUCCESS;
     struct lyxp_set xp_set;
     struct lysc_must *musts;
     const struct lyd_node *tree;
@@ -1403,13 +1432,13 @@
         memset(&xp_set, 0, sizeof xp_set);
 
         /* evaluate must */
-        ret = lyxp_eval(LYD_CTX(node), musts[u].cond, node->schema->module, LY_VALUE_SCHEMA_RESOLVED,
+        r = lyxp_eval(LYD_CTX(node), musts[u].cond, node->schema->module, LY_VALUE_SCHEMA_RESOLVED,
                 musts[u].prefixes, node, node, tree, NULL, &xp_set, LYXP_SCHEMA | xpath_options);
-        if (ret == LY_EINCOMPLETE) {
-            LOGINT_RET(LYD_CTX(node));
-        } else if (ret) {
-            return ret;
+        if (r == LY_EINCOMPLETE) {
+            LOGINT(LYD_CTX(node));
+            r = LY_EINT;
         }
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
 
         /* check the result */
         lyxp_set_cast(&xp_set, LYXP_SET_BOOLEAN);
@@ -1422,11 +1451,13 @@
             } else {
                 LOGVAL_APPTAG(LYD_CTX(node), eapptag, LY_VCODE_NOMUST, musts[u].cond->expr);
             }
-            return LY_EVALID;
+            r = LY_EVALID;
+            LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
         }
     }
 
-    return LY_SUCCESS;
+cleanup:
+    return rc;
 }
 
 /**
@@ -1445,7 +1476,7 @@
 lyd_validate_final_r(struct lyd_node *first, const struct lyd_node *parent, const struct lysc_node *sparent,
         const struct lys_module *mod, uint32_t val_opts, uint32_t int_opts, uint32_t must_xp_opts)
 {
-    LY_ERR r;
+    LY_ERR r, rc = LY_SUCCESS;
     const char *innode;
     struct lyd_node *next = NULL, *node;
 
@@ -1461,8 +1492,7 @@
         /* opaque data */
         if (!node->schema) {
             r = lyd_parse_opaq_error(node);
-            LOG_LOCBACK(0, 1, 0, 0);
-            return r;
+            goto next_iter;
         }
 
         if (!node->parent && mod && (lyd_owner_module(node) != mod)) {
@@ -1488,27 +1518,28 @@
         }
         if (innode) {
             LOGVAL(LYD_CTX(node), LY_VCODE_UNEXPNODE, innode, node->schema->name);
-            LOG_LOCBACK(1, 1, 0, 0);
-            return LY_EVALID;
+            r = LY_EVALID;
+            goto next_iter;
         }
 
         /* obsolete data */
         lyd_validate_obsolete(node);
 
         /* node's musts */
-        if ((r = lyd_validate_must(node, int_opts, must_xp_opts))) {
-            LOG_LOCBACK(1, 1, 0, 0);
-            return r;
+        if ((r = lyd_validate_must(node, val_opts, int_opts, must_xp_opts))) {
+            goto next_iter;
         }
 
         /* node value was checked by plugins */
 
-        /* next iter */
-        LOG_LOCBACK(1, 1, 0, 0);
+next_iter:
+        LOG_LOCBACK(node->schema ? 1 : 0, 1, 0, 0);
+        LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
     }
 
     /* validate schema-based restrictions */
-    LY_CHECK_RET(lyd_validate_siblings_schema_r(first, parent, sparent, mod ? mod->compiled : NULL, val_opts, int_opts));
+    r = lyd_validate_siblings_schema_r(first, parent, sparent, mod ? mod->compiled : NULL, val_opts, int_opts);
+    LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
 
     LY_LIST_FOR(first, node) {
         if (!node->parent && mod && (lyd_owner_module(node) != mod)) {
@@ -1517,13 +1548,15 @@
         }
 
         /* validate all children recursively */
-        LY_CHECK_RET(lyd_validate_final_r(lyd_child(node), node, node->schema, NULL, val_opts, int_opts, must_xp_opts));
+        r = lyd_validate_final_r(lyd_child(node), node, node->schema, NULL, val_opts, int_opts, must_xp_opts);
+        LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
 
         /* set default for containers */
         lyd_cont_set_dflt(node);
     }
 
-    return LY_SUCCESS;
+cleanup:
+    return rc;
 }
 
 /**
@@ -1609,15 +1642,16 @@
  * @param[in,out] meta_types Set for unres metadata types.
  * @param[in,out] ext_node Set with nodes with extensions to validate.
  * @param[in,out] ext_val Set for parsed extension data to validate.
- * @param[in] impl_opts Implicit options, see @ref implicitoptions.
+ * @param[in] val_opts Validation options.
  * @param[in,out] diff Validation diff.
  * @return LY_ERR value.
  */
 static LY_ERR
 lyd_validate_subtree(struct lyd_node *root, struct ly_set *node_when, struct ly_set *node_types,
-        struct ly_set *meta_types, struct ly_set *ext_node, struct ly_set *ext_val, uint32_t impl_opts,
+        struct ly_set *meta_types, struct ly_set *ext_node, struct ly_set *ext_val, uint32_t val_opts,
         struct lyd_node **diff)
 {
+    LY_ERR r, rc = LY_SUCCESS;
     const struct lyd_meta *meta;
     const struct lysc_type *type;
     struct lyd_node *node;
@@ -1637,34 +1671,42 @@
             lyplg_ext_get_storage(meta->annotation, LY_STMT_TYPE, sizeof type, (const void **)&type);
             if (type->plugin->validate) {
                 /* metadata type resolution */
-                LY_CHECK_RET(ly_set_add(meta_types, (void *)meta, 1, NULL));
+                r = ly_set_add(meta_types, (void *)meta, 1, NULL);
+                LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
             }
         }
 
         if ((node->schema->nodetype & LYD_NODE_TERM) && ((struct lysc_node_leaf *)node->schema)->type->plugin->validate) {
             /* node type resolution */
-            LY_CHECK_RET(ly_set_add(node_types, (void *)node, 1, NULL));
+            r = ly_set_add(node_types, (void *)node, 1, NULL);
+            LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
         } else if (node->schema->nodetype & LYD_NODE_INNER) {
             /* new node validation, autodelete */
-            LY_CHECK_RET(lyd_validate_new(lyd_node_child_p(node), node->schema, NULL, diff));
+            r = lyd_validate_new(lyd_node_child_p(node), node->schema, NULL, val_opts, diff);
+            LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
 
             /* add nested defaults */
-            LY_CHECK_RET(lyd_new_implicit_r(node, lyd_node_child_p(node), NULL, NULL, NULL, NULL, NULL, impl_opts, diff));
+            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);
+            LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
         }
 
         if (lysc_has_when(node->schema)) {
             /* when evaluation */
-            LY_CHECK_RET(ly_set_add(node_when, (void *)node, 1, NULL));
+            r = ly_set_add(node_when, (void *)node, 1, NULL);
+            LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
         }
 
         /* store for ext instance node validation, if needed */
-        LY_CHECK_RET(lyd_validate_node_ext(node, ext_node));
+        r = lyd_validate_node_ext(node, ext_node);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
 
 next_node:
         LYD_TREE_DFS_END(root, node);
     }
 
-    return LY_SUCCESS;
+cleanup:
+    return rc;
 }
 
 LY_ERR
@@ -1672,7 +1714,7 @@
         ly_bool validate_subtree, struct ly_set *node_when_p, struct ly_set *node_types_p, struct ly_set *meta_types_p,
         struct ly_set *ext_node_p, struct ly_set *ext_val_p, struct lyd_node **diff)
 {
-    LY_ERR ret = LY_SUCCESS;
+    LY_ERR r, rc = LY_SUCCESS;
     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};
@@ -1708,15 +1750,15 @@
         }
 
         /* validate new top-level nodes of this module, autodelete */
-        ret = lyd_validate_new(first2, NULL, mod, diff);
-        LY_CHECK_GOTO(ret, cleanup);
+        r = lyd_validate_new(first2, NULL, mod, val_opts, diff);
+        LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
 
         /* 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) */
-        ret = lyd_new_implicit_r(NULL, first2, NULL, mod, validate_subtree ? NULL : node_when_p,
+        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);
-        LY_CHECK_GOTO(ret, cleanup);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
 
         /* our first module node pointer may no longer be the first */
         first = *first2;
@@ -1734,20 +1776,20 @@
                     break;
                 }
 
-                ret = lyd_validate_subtree(iter, node_when_p, node_types_p, meta_types_p, ext_node_p, ext_val_p,
-                        (val_opts & LYD_VALIDATE_NO_STATE) ? LYD_IMPLICIT_NO_STATE : 0, diff);
-                LY_CHECK_GOTO(ret, cleanup);
+                r = lyd_validate_subtree(iter, node_when_p, node_types_p, meta_types_p, ext_node_p, ext_val_p,
+                        val_opts, diff);
+                LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
             }
         }
 
         /* finish incompletely validated terminal values/attributes and when conditions */
-        ret = lyd_validate_unres(first2, mod, LYD_TYPE_DATA_YANG, node_when_p, 0, node_types_p, meta_types_p,
+        r = lyd_validate_unres(first2, mod, LYD_TYPE_DATA_YANG, node_when_p, 0, node_types_p, meta_types_p,
                 ext_node_p, ext_val_p, val_opts, diff);
-        LY_CHECK_GOTO(ret, cleanup);
+        LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
 
         /* perform final validation that assumes the data tree is final */
-        ret = lyd_validate_final_r(*first2, NULL, NULL, mod, val_opts, 0, 0);
-        LY_CHECK_GOTO(ret, cleanup);
+        r = lyd_validate_final_r(*first2, NULL, NULL, mod, val_opts, 0, 0);
+        LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup);
     }
 
 cleanup:
@@ -1756,7 +1798,7 @@
     ly_set_erase(&meta_types, NULL);
     ly_set_erase(&ext_node, free);
     ly_set_erase(&ext_val, free);
-    return ret;
+    return rc;
 }
 
 LIBYANG_API_DEF LY_ERR
@@ -1931,7 +1973,7 @@
 
     /* perform final validation of the operation/notification */
     lyd_validate_obsolete(op_node);
-    LY_CHECK_GOTO(rc = lyd_validate_must(op_node, int_opts, LYXP_IGNORE_WHEN), cleanup);
+    LY_CHECK_GOTO(rc = lyd_validate_must(op_node, 0, int_opts, LYXP_IGNORE_WHEN), cleanup);
 
     /* final validation of all the descendants */
     rc = lyd_validate_final_r(lyd_child(op_node), op_node, op_node->schema, NULL, 0, int_opts, LYXP_IGNORE_WHEN);
diff --git a/src/validation.h b/src/validation.h
index c9f5da0..db24ef6 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -69,11 +69,12 @@
  * @param[in,out] first First sibling.
  * @param[in] sparent Schema parent of the siblings, NULL for top-level siblings.
  * @param[in] mod Module of the siblings, NULL for nested siblings.
+ * @param[in] val_opts Validation options.
  * @param[in,out] diff Validation diff.
  * @return LY_ERR value.
  */
 LY_ERR lyd_validate_new(struct lyd_node **first, const struct lysc_node *sparent, const struct lys_module *mod,
-        struct lyd_node **diff);
+        uint32_t val_opts, struct lyd_node **diff);
 
 /**
  * @brief Validate data node with an extension instance, if any, by storing it in its unres set.
diff --git a/tests/utests/data/test_validation.c b/tests/utests/data/test_validation.c
index 7bc3dc8..bdc223f 100644
--- a/tests/utests/data/test_validation.c
+++ b/tests/utests/data/test_validation.c
@@ -1154,6 +1154,93 @@
     CHECK_LOG_CTX_APPTAG("l leaf is not left", "Data location \"/i:cont/l3\".", "not-left");
 }
 
+static void
+test_multi_error(void **state)
+{
+    struct lyd_node *tree;
+    const char *schema =
+            "module ii {\n"
+            "    namespace urn:tests:ii;\n"
+            "    prefix ii;\n"
+            "    yang-version 1.1;\n"
+            "\n"
+            "    container cont {\n"
+            "        leaf l {\n"
+            "            type string;\n"
+            "        }\n"
+            "        leaf l2 {\n"
+            "            must \"../l = 'right'\";\n"
+            "            type string;\n"
+            "        }\n"
+            "        leaf l3 {\n"
+            "            must \"../l = 'left'\" {\n"
+            "                error-app-tag \"not-left\";\n"
+            "                error-message \"l leaf is not left\";\n"
+            "            }\n"
+            "            type string;\n"
+            "        }\n"
+            "        leaf-list ll {\n"
+            "            type uint32;\n"
+            "            min-elements 2;\n"
+            "        }\n"
+            "    }\n"
+            "}";
+    const char *data;
+
+    UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, NULL);
+
+    /* xml */
+    data =
+            "<cont xmlns=\"urn:tests:ii\">\n"
+            "  <l>wrong</l>\n"
+            "  <l>wrong2</l>\n"
+            "  <l2>val</l2>\n"
+            "  <l3>val</l3>\n"
+            "  <ll>ahoy</ll>\n"
+            "</cont>\n";
+    CHECK_PARSE_LYD_PARAM(data, LYD_XML, 0, LYD_VALIDATE_PRESENT | LYD_VALIDATE_MULTI_ERROR, LY_EVALID, tree);
+    CHECK_LOG_CTX_APPTAG("Too few \"ll\" instances.", "Schema location \"/ii:cont/ll\".", "too-few-elements");
+    CHECK_LOG_CTX_APPTAG("l leaf is not left", "Data location \"/ii:cont/l3\".", "not-left");
+    CHECK_LOG_CTX_APPTAG("Must condition \"../l = 'right'\" not satisfied.", "Data location \"/ii:cont/l2\".", "must-violation");
+    CHECK_LOG_CTX_APPTAG("Duplicate instance of \"l\".", "Data location \"/ii:cont/l\", line number 6.", NULL);
+    CHECK_LOG_CTX_APPTAG("Invalid type uint32 value \"ahoy\".", "Data location \"/ii:cont/ll\", line number 6.", NULL);
+
+    /* json */
+    data = "{\n"
+            "  \"ii:cont\": {\n"
+            "    \"l\": \"wrong\",\n"
+            "    \"l\": \"wrong2\",\n"
+            "    \"l2\": \"val\",\n"
+            "    \"l3\": \"val\",\n"
+            "    \"ll\": [\"ahoy\"]\n"
+            "  }\n"
+            "}\n";
+    CHECK_PARSE_LYD_PARAM(data, LYD_JSON, 0, LYD_VALIDATE_PRESENT | LYD_VALIDATE_MULTI_ERROR, LY_EVALID, tree);
+    CHECK_LOG_CTX_APPTAG("Too few \"ll\" instances.", "Schema location \"/ii:cont/ll\".", "too-few-elements");
+    CHECK_LOG_CTX_APPTAG("l leaf is not left", "Data location \"/ii:cont/l3\".", "not-left");
+    CHECK_LOG_CTX_APPTAG("Must condition \"../l = 'right'\" not satisfied.", "Data location \"/ii:cont/l2\".", "must-violation");
+    CHECK_LOG_CTX_APPTAG("Duplicate instance of \"l\".", "Data location \"/ii:cont/l\", line number 8.", NULL);
+    CHECK_LOG_CTX_APPTAG("Invalid non-number-encoded uint32 value \"ahoy\".", "Data location \"/ii:cont/ll\", line number 7.", NULL);
+
+    /* validation */
+    data = "{\n"
+            "  \"ii:cont\": {\n"
+            "    \"l\": \"wrong\",\n"
+            "    \"l\": \"wrong2\",\n"
+            "    \"l2\": \"val\",\n"
+            "    \"l3\": \"val\",\n"
+            "    \"ll\": [25]\n"
+            "  }\n"
+            "}\n";
+    CHECK_PARSE_LYD_PARAM(data, LYD_JSON, LYD_PARSE_ONLY, 0, LY_SUCCESS, tree);
+    assert_int_equal(LY_EVALID, lyd_validate_all(&tree, NULL, LYD_VALIDATE_PRESENT | LYD_VALIDATE_MULTI_ERROR, NULL));
+    lyd_free_tree(tree);
+    CHECK_LOG_CTX_APPTAG("Too few \"ll\" instances.", "Schema location \"/ii:cont/ll\".", "too-few-elements");
+    CHECK_LOG_CTX_APPTAG("l leaf is not left", "Data location \"/ii:cont/l3\".", "not-left");
+    CHECK_LOG_CTX_APPTAG("Must condition \"../l = 'right'\" not satisfied.", "Data location \"/ii:cont/l2\".", "must-violation");
+    CHECK_LOG_CTX_APPTAG("Duplicate instance of \"l\".", "Data location \"/ii:cont/l\".", NULL);
+}
+
 const char *schema_j =
         "module j {\n"
         "    namespace urn:tests:j;\n"
@@ -1452,6 +1539,7 @@
         UTEST(test_defaults),
         UTEST(test_state),
         UTEST(test_must),
+        UTEST(test_multi_error),
         UTEST(test_action),
         UTEST(test_rpc),
         UTEST(test_reply),