data tree FEATURE support for NETCONF messages
diff --git a/src/validation.c b/src/validation.c
index 6427727..cb24d54 100644
--- a/src/validation.c
+++ b/src/validation.c
@@ -1057,12 +1057,12 @@
  * @param[in] sparent Schema parent of the nodes to check.
  * @param[in] mod Module of the nodes to check.
  * @param[in] val_opts Validation options, see @ref datavalidationoptions.
- * @param[in] op Operation being validated, if any.
+ * @param[in] int_opts Internal parser options.
  * @return LY_ERR value.
  */
 static LY_ERR
 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, LYD_VALIDATE_OP op)
+        const struct lysc_node *sparent, const struct lysc_module *mod, uint32_t val_opts, uint32_t int_opts)
 {
     LY_ERR ret = LY_SUCCESS;
     const struct lysc_node *snode = NULL, *scase;
@@ -1070,7 +1070,7 @@
     struct lysc_node_leaflist *sllist;
     uint32_t getnext_opts;
 
-    getnext_opts = LYS_GETNEXT_WITHCHOICE | (op == LYD_VALIDATE_OP_REPLY ? LYS_GETNEXT_OUTPUT : 0);
+    getnext_opts = LYS_GETNEXT_WITHCHOICE | (int_opts & LYD_INTOPT_REPLY ? LYS_GETNEXT_OUTPUT : 0);
 
     /* disabled nodes are skipped by lys_getnext */
     while ((snode = lys_getnext(snode, sparent, mod, getnext_opts))) {
@@ -1114,7 +1114,7 @@
             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, op);
+                    ret = lyd_validate_siblings_schema_r(first, parent, scase, mod, val_opts, int_opts);
                     LY_CHECK_GOTO(ret, error);
                     break;
                 }
@@ -1156,11 +1156,11 @@
  * @brief Validate must conditions of a data node.
  *
  * @param[in] node Node to validate.
- * @param[in] op Operation being validated, if any.
+ * @param[in] int_opts Internal parser options.
  * @return LY_ERR value.
  */
 static LY_ERR
-lyd_validate_must(const struct lyd_node *node, LYD_VALIDATE_OP op)
+lyd_validate_must(const struct lyd_node *node, uint32_t int_opts)
 {
     struct lyxp_set xp_set;
     struct lysc_must *musts;
@@ -1168,10 +1168,13 @@
     const struct lysc_node *schema;
     LY_ARRAY_COUNT_TYPE u;
 
+    assert((int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_REPLY)) != (LYD_INTOPT_RPC | LYD_INTOPT_REPLY));
+    assert((int_opts & (LYD_INTOPT_ACTION | LYD_INTOPT_REPLY)) != (LYD_INTOPT_ACTION | LYD_INTOPT_REPLY));
+
     if (node->schema->nodetype & (LYS_ACTION | LYS_RPC)) {
-        if (op == LYD_VALIDATE_OP_RPC) {
+        if (int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION)) {
             schema = &((struct lysc_node_action *)node->schema)->input.node;
-        } else if (op == LYD_VALIDATE_OP_REPLY) {
+        } else if (int_opts & LYD_INTOPT_REPLY) {
             schema = &((struct lysc_node_action *)node->schema)->output.node;
         } else {
             LOGINT(LYD_CTX(node));
@@ -1208,9 +1211,20 @@
     return LY_SUCCESS;
 }
 
-LY_ERR
+/**
+ * @brief Perform all remaining validation tasks, the data tree must be final when calling this function.
+ *
+ * @param[in] first First sibling.
+ * @param[in] parent Data parent.
+ * @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 (@ref datavalidationoptions).
+ * @param[in] int_opts Internal parser options.
+ * @return LY_ERR value.
+ */
+static LY_ERR
 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, LYD_VALIDATE_OP op)
+        const struct lys_module *mod, uint32_t val_opts, uint32_t int_opts)
 {
     const char *innode = NULL;
     struct lyd_node *next = NULL, *node;
@@ -1235,10 +1249,10 @@
         if ((val_opts & LYD_VALIDATE_NO_STATE) && (node->schema->flags & LYS_CONFIG_R)) {
             innode = "state";
             goto invalid_node;
-        } else if ((op == LYD_VALIDATE_OP_RPC) && (node->schema->flags & LYS_IS_OUTPUT)) {
+        } else if ((int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION)) && (node->schema->flags & LYS_IS_OUTPUT)) {
             innode = "output";
             goto invalid_node;
-        } else if ((op == LYD_VALIDATE_OP_REPLY) && (node->schema->flags & LYS_IS_INPUT)) {
+        } else if ((int_opts & LYD_INTOPT_REPLY) && (node->schema->flags & LYS_IS_INPUT)) {
             innode = "input";
             goto invalid_node;
         }
@@ -1247,7 +1261,7 @@
         lyd_validate_obsolete(node);
 
         /* node's musts */
-        LY_CHECK_RET(lyd_validate_must(node, op));
+        LY_CHECK_RET(lyd_validate_must(node, int_opts));
 
         /* node value was checked by plugins */
 
@@ -1255,11 +1269,11 @@
     }
 
     /* validate schema-based restrictions */
-    LY_CHECK_RET(lyd_validate_siblings_schema_r(first, parent, sparent, mod ? mod->compiled : NULL, val_opts, op));
+    LY_CHECK_RET(lyd_validate_siblings_schema_r(first, parent, sparent, mod ? mod->compiled : NULL, val_opts, int_opts));
 
     LY_LIST_FOR(first, node) {
         /* validate all children recursively */
-        LY_CHECK_RET(lyd_validate_final_r(lyd_child(node), node, node->schema, NULL, val_opts, op));
+        LY_CHECK_RET(lyd_validate_final_r(lyd_child(node), node, node->schema, NULL, val_opts, int_opts));
 
         /* set default for containers */
         if ((node->schema->nodetype == LYS_CONTAINER) && !(node->schema->flags & LYS_PRESENCE)) {
@@ -1286,16 +1300,16 @@
  * @brief Validate the whole data subtree.
  *
  * @param[in] root Subtree root.
+ * @param[in,out] node_when Set for nodes with when conditions.
  * @param[in,out] node_types Set for unres node types.
  * @param[in,out] meta_types Set for unres metadata types.
- * @param[in,out] node_when Set for nodes with when conditions.
  * @param[in] impl_opts Implicit options, see @ref implicitoptions.
  * @param[in,out] diff Validation diff.
  * @return LY_ERR value.
  */
 static LY_ERR
-lyd_validate_subtree(struct lyd_node *root, struct ly_set *node_types, struct ly_set *meta_types,
-        struct ly_set *node_when, uint32_t impl_opts, struct lyd_node **diff)
+lyd_validate_subtree(struct lyd_node *root, struct ly_set *node_when, struct ly_set *node_types,
+        struct ly_set *meta_types, uint32_t impl_opts, struct lyd_node **diff)
 {
     const struct lyd_meta *meta;
     struct lyd_node *node;
@@ -1313,10 +1327,10 @@
             LY_CHECK_RET(ly_set_add(node_types, (void *)node, 1, NULL));
         } else if (node->schema->nodetype & LYD_NODE_INNER) {
             /* new node validation, autodelete */
-            LY_CHECK_RET(lyd_validate_new(lyd_node_children_p(node), node->schema, NULL, diff));
+            LY_CHECK_RET(lyd_validate_new(lyd_node_child_p(node), node->schema, NULL, diff));
 
             /* add nested defaults */
-            LY_CHECK_RET(lyd_new_implicit_r(node, lyd_node_children_p(node), NULL, NULL, NULL, NULL, impl_opts, diff));
+            LY_CHECK_RET(lyd_new_implicit_r(node, lyd_node_child_p(node), NULL, NULL, NULL, NULL, impl_opts, diff));
         }
 
         if (lysc_node_when(node->schema)) {
@@ -1330,19 +1344,9 @@
     return LY_SUCCESS;
 }
 
-/**
- * @brief Validate data tree.
- *
- * @param[in,out] tree Data tree to validate, nodes may be autodeleted.
- * @param[in] modules Array of modules to validate, NULL for all modules.
- * @param[in] mod_count Count of @p modules.
- * @param[in] ly_ctx libyang context.
- * @param[in] val_opts Validation options, see @ref datavalidationoptions.
- * @param[out] diff Generated validation diff, not generated if NULL.
- * @return LY_ERR value.
- */
-static LY_ERR
+LY_ERR
 lyd_validate(struct lyd_node **tree, const struct lys_module *module, const struct ly_ctx *ctx, uint32_t val_opts,
+        ly_bool validate_subtree, struct ly_set *node_when_p, struct ly_set *node_types_p, struct ly_set *meta_types_p,
         struct lyd_node **diff)
 {
     LY_ERR ret = LY_SUCCESS;
@@ -1351,12 +1355,13 @@
     struct ly_set node_types = {0}, meta_types = {0}, node_when = {0};
     uint32_t i = 0;
 
-    LY_CHECK_ARG_RET(NULL, tree, *tree || ctx || module, LY_EINVAL);
-    if (!ctx && !module) {
-        ctx = LYD_CTX(*tree);
-    }
-    if (diff) {
-        *diff = NULL;
+    assert(tree && ctx);
+    assert((node_when_p && node_types_p && meta_types_p) || (!node_when_p && !node_types_p && !meta_types_p));
+
+    if (!node_when_p) {
+        node_when_p = &node_when;
+        node_types_p = &node_types;
+        meta_types_p = &meta_types;
     }
 
     next = *tree;
@@ -1381,7 +1386,7 @@
         LY_CHECK_GOTO(ret, cleanup);
 
         /* add all top-level defaults for this module, do not add into unres sets, will occur in the next step */
-        ret = lyd_new_implicit_r(NULL, first2, NULL, mod, NULL, NULL, val_opts & LYD_VALIDATE_NO_STATE ?
+        ret = lyd_new_implicit_r(NULL, first2, NULL, mod, NULL, NULL, (val_opts & LYD_VALIDATE_NO_STATE) ?
                 LYD_IMPLICIT_NO_STATE : 0, diff);
         LY_CHECK_GOTO(ret, cleanup);
 
@@ -1390,15 +1395,17 @@
             *first2 = (*first2)->prev;
         }
 
-        /* process nested nodes */
-        LY_LIST_FOR(*first2, iter) {
-            ret = lyd_validate_subtree(iter, &node_types, &meta_types, &node_when, val_opts & LYD_VALIDATE_NO_STATE ?
-                    LYD_IMPLICIT_NO_STATE : 0, diff);
-            LY_CHECK_GOTO(ret, cleanup);
+        if (validate_subtree) {
+            /* process nested nodes */
+            LY_LIST_FOR(*first2, iter) {
+                ret = lyd_validate_subtree(iter, node_when_p, node_types_p, meta_types_p,
+                        (val_opts & LYD_VALIDATE_NO_STATE) ? LYD_IMPLICIT_NO_STATE : 0, diff);
+                LY_CHECK_GOTO(ret, cleanup);
+            }
         }
 
         /* finish incompletely validated terminal values/attributes and when conditions */
-        ret = lyd_validate_unres(first2, mod, &node_when, &node_types, &meta_types, diff);
+        ret = lyd_validate_unres(first2, mod, node_when_p, node_types_p, meta_types_p, diff);
         LY_CHECK_GOTO(ret, cleanup);
 
         /* perform final validation that assumes the data tree is final */
@@ -1407,22 +1414,35 @@
     }
 
 cleanup:
+    ly_set_erase(&node_when, NULL);
     ly_set_erase(&node_types, NULL);
     ly_set_erase(&meta_types, NULL);
-    ly_set_erase(&node_when, NULL);
     return ret;
 }
 
 API LY_ERR
 lyd_validate_all(struct lyd_node **tree, const struct ly_ctx *ctx, uint32_t val_opts, struct lyd_node **diff)
 {
-    return lyd_validate(tree, NULL, ctx, val_opts, diff);
+    LY_CHECK_ARG_RET(NULL, tree, *tree || ctx, LY_EINVAL);
+    if (!ctx) {
+        ctx = LYD_CTX(*tree);
+    }
+    if (diff) {
+        *diff = NULL;
+    }
+
+    return lyd_validate(tree, NULL, ctx, val_opts, 1, NULL, NULL, NULL, diff);
 }
 
 API LY_ERR
 lyd_validate_module(struct lyd_node **tree, const struct lys_module *module, uint32_t val_opts, struct lyd_node **diff)
 {
-    return lyd_validate(tree, module, NULL, val_opts, diff);
+    LY_CHECK_ARG_RET(NULL, tree, *tree || module, LY_EINVAL);
+    if (diff) {
+        *diff = NULL;
+    }
+
+    return lyd_validate(tree, module, (*tree) ? LYD_CTX(*tree) : module->ctx, val_opts, 1, NULL, NULL, NULL, diff);
 }
 
 /**
@@ -1488,29 +1508,124 @@
     }
 }
 
-API LY_ERR
-lyd_validate_op(struct lyd_node *op_tree, const struct lyd_node *tree, LYD_VALIDATE_OP op, struct lyd_node **diff)
+/**
+ * @brief Validate an RPC/action request, reply, or notification.
+ *
+ * @param[in] op_tree Full operation data tree.
+ * @param[in] op_node Operation node itself.
+ * @param[in] dep_tree Tree to be used for validating references from the operation subtree.
+ * @param[in] int_opts Internal parser options.
+ * @param[in] validate_subtree Whether subtree was already validated (as part of data parsing) or not (separate validation).
+ * @param[in] node_when_p Set of nodes with when conditions, if NULL a local set is used.
+ * @param[in] node_types_p Set of unres node types, if NULL a local set is used.
+ * @param[in] meta_types_p Set of unres metadata types, if NULL a local set is used.
+ * @param[out] diff Optional diff with any changes made by the validation.
+ * @return LY_SUCCESS on success.
+ * @return LY_ERR error on error.
+ */
+static LY_ERR
+_lyd_validate_op(struct lyd_node *op_tree, struct lyd_node *op_node, const struct lyd_node *dep_tree,
+        uint32_t int_opts, ly_bool validate_subtree, struct ly_set *node_when_p, struct ly_set *node_types_p,
+        struct ly_set *meta_types_p, struct lyd_node **diff)
 {
-    LY_ERR ret;
-    struct lyd_node *tree_sibling, *tree_parent, *op_subtree, *op_node, *op_parent, *child;
-    struct ly_set type_check = {0}, type_meta_check = {0}, when_check = {0};
+    LY_ERR rc = LY_SUCCESS;
+    struct lyd_node *tree_sibling, *tree_parent, *op_subtree, *op_parent, *child;
+    struct ly_set node_types = {0}, meta_types = {0}, node_when = {0};
 
-    LY_CHECK_ARG_RET(NULL, op_tree, !op_tree->parent, !tree || !tree->parent,
-            (op == LYD_VALIDATE_OP_NOTIF) || (op == LYD_VALIDATE_OP_RPC) || (op == LYD_VALIDATE_OP_REPLY), LY_EINVAL);
+    assert(op_tree && op_node);
+    assert((node_when_p && node_types_p && meta_types_p) || (!node_when_p && !node_types_p && !meta_types_p));
+
+    if (!node_when_p) {
+        node_when_p = &node_when;
+        node_types_p = &node_types;
+        meta_types_p = &meta_types;
+    }
+
+    /* merge op_tree into dep_tree */
+    lyd_val_op_merge_find(op_tree, op_node, dep_tree, &op_subtree, &tree_sibling, &tree_parent);
+    op_parent = lyd_parent(op_subtree);
+    lyd_unlink_tree(op_subtree);
+    lyd_insert_node(tree_parent, &tree_sibling, op_subtree);
+    if (!dep_tree) {
+        dep_tree = tree_sibling;
+    }
+
+    LOG_LOCSET(NULL, op_node, NULL, NULL);
+
+    if (int_opts & LYD_INTOPT_REPLY) {
+        /* add output children defaults */
+        rc = lyd_new_implicit_r(op_node, lyd_node_child_p(op_node), NULL, NULL, node_types_p, node_when_p,
+                LYD_IMPLICIT_OUTPUT, diff);
+        LY_CHECK_GOTO(rc, cleanup);
+
+        if (validate_subtree) {
+            /* skip validating the operation itself, go to children directly */
+            LY_LIST_FOR(lyd_child(op_node), child) {
+                LY_CHECK_GOTO(rc = lyd_validate_subtree(child, node_when_p, node_types_p, meta_types_p, 0, diff), cleanup);
+            }
+        }
+    } else {
+        if (validate_subtree) {
+            /* prevalidate whole operation subtree */
+            LY_CHECK_GOTO(rc = lyd_validate_subtree(op_node, node_when_p, node_types_p, meta_types_p, 0, diff), cleanup);
+        }
+    }
+
+    /* finish incompletely validated terminal values/attributes and when conditions on the full tree */
+    LY_CHECK_GOTO(rc = lyd_validate_unres((struct lyd_node **)&dep_tree, NULL, node_when_p, node_types_p, meta_types_p,
+            diff), cleanup);
+
+    /* perform final validation of the operation/notification */
+    lyd_validate_obsolete(op_node);
+    LY_CHECK_GOTO(rc = lyd_validate_must(op_node, int_opts), cleanup);
+
+    /* final validation of all the descendants */
+    LY_CHECK_GOTO(rc = lyd_validate_final_r(lyd_child(op_node), op_node, op_node->schema, NULL, 0, int_opts), cleanup);
+
+cleanup:
+    LOG_LOCBACK(0, 1, 0, 0);
+    /* restore operation tree */
+    lyd_unlink_tree(op_subtree);
+    if (op_parent) {
+        lyd_insert_node(op_parent, NULL, op_subtree);
+    }
+
+    ly_set_erase(&node_when, NULL);
+    ly_set_erase(&node_types, NULL);
+    ly_set_erase(&meta_types, NULL);
+    return rc;
+}
+
+API LY_ERR
+lyd_validate_op(struct lyd_node *op_tree, const struct lyd_node *dep_tree, enum lyd_type data_type, struct lyd_node **diff)
+{
+    struct lyd_node *op_node;
+    uint32_t int_opts;
+
+    LY_CHECK_ARG_RET(NULL, op_tree, !op_tree->parent, !dep_tree || !dep_tree->parent, (data_type == LYD_TYPE_YANG_RPC) ||
+            (data_type == LYD_TYPE_YANG_NOTIF) || (data_type == LYD_TYPE_YANG_REPLY), LY_EINVAL);
     if (diff) {
         *diff = NULL;
     }
+    if (data_type == LYD_TYPE_YANG_RPC) {
+        int_opts = LYD_INTOPT_RPC | LYD_INTOPT_ACTION;
+    } else if (data_type == LYD_TYPE_YANG_NOTIF) {
+        int_opts = LYD_INTOPT_NOTIF;
+    } else {
+        int_opts = LYD_INTOPT_REPLY;
+    }
 
     /* find the operation/notification */
     LYD_TREE_DFS_BEGIN(op_tree, op_node) {
-        if (((op == LYD_VALIDATE_OP_RPC) || (op == LYD_VALIDATE_OP_REPLY)) && (op_node->schema->nodetype & (LYS_RPC | LYS_ACTION))) {
+        if ((int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION | LYD_INTOPT_REPLY)) &&
+                (op_node->schema->nodetype & (LYS_RPC | LYS_ACTION))) {
             break;
-        } else if ((op == LYD_VALIDATE_OP_NOTIF) && (op_node->schema->nodetype == LYS_NOTIF)) {
+        } else if ((int_opts & LYD_INTOPT_NOTIF) && (op_node->schema->nodetype == LYS_NOTIF)) {
             break;
         }
         LYD_TREE_DFS_END(op_tree, op_node);
     }
-    if ((op == LYD_VALIDATE_OP_RPC) || (op == LYD_VALIDATE_OP_REPLY)) {
+    if (int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION | LYD_INTOPT_REPLY)) {
         if (!(op_node->schema->nodetype & (LYS_RPC | LYS_ACTION))) {
             LOGERR(LYD_CTX(op_tree), LY_EINVAL, "No RPC/action to validate found.");
             return LY_EINVAL;
@@ -1522,56 +1637,6 @@
         }
     }
 
-    /* move op_tree to top-level node */
-    while (op_tree->parent) {
-        op_tree = lyd_parent(op_tree);
-    }
-
-    /* merge op_tree into tree */
-    lyd_val_op_merge_find(op_tree, op_node, tree, &op_subtree, &tree_sibling, &tree_parent);
-    op_parent = lyd_parent(op_subtree);
-    lyd_unlink_tree(op_subtree);
-    lyd_insert_node(tree_parent, &tree_sibling, op_subtree);
-    if (!tree) {
-        tree = tree_sibling;
-    }
-
-    LOG_LOCSET(NULL, op_node, NULL, NULL);
-
-    if (op == LYD_VALIDATE_OP_REPLY) {
-        /* add output children defaults */
-        LY_CHECK_RET(lyd_new_implicit_r(op_node, lyd_node_children_p(op_node), NULL, NULL, NULL, NULL, LYD_IMPLICIT_OUTPUT, diff));
-
-        /* skip validating the operation itself, go to children directly */
-        LY_LIST_FOR(lyd_child(op_node), child) {
-            LY_CHECK_GOTO(ret = lyd_validate_subtree(child, &type_check, &type_meta_check, &when_check, 0, diff), cleanup);
-        }
-    } else {
-        /* prevalidate whole operation subtree */
-        LY_CHECK_GOTO(ret = lyd_validate_subtree(op_node, &type_check, &type_meta_check, &when_check, 0, diff), cleanup);
-    }
-
-    /* finish incompletely validated terminal values/attributes and when conditions on the full tree */
-    LY_CHECK_GOTO(ret = lyd_validate_unres((struct lyd_node **)&tree, NULL, &when_check, &type_check, &type_meta_check,
-            diff), cleanup);
-
-    /* perform final validation of the operation/notification */
-    lyd_validate_obsolete(op_node);
-    LY_CHECK_GOTO(ret = lyd_validate_must(op_node, op), cleanup);
-
-    /* final validation of all the descendants */
-    LY_CHECK_GOTO(ret = lyd_validate_final_r(lyd_child(op_node), op_node, op_node->schema, NULL, 0, op), cleanup);
-
-cleanup:
-    LOG_LOCBACK(0, 1, 0, 0);
-    /* restore operation tree */
-    lyd_unlink_tree(op_subtree);
-    if (op_parent) {
-        lyd_insert_node(op_parent, NULL, op_subtree);
-    }
-
-    ly_set_erase(&type_check, NULL);
-    ly_set_erase(&type_meta_check, NULL);
-    ly_set_erase(&when_check, NULL);
-    return ret;
+    /* validate */
+    return _lyd_validate_op(op_tree, op_node, dep_tree, int_opts, 1, NULL, NULL, NULL, diff);
 }