lyd_validate(struct lyd_node **node, int options, void *var_arg, ...)
{
    struct lyd_node *iter, *data_tree = NULL;
    struct lyd_difflist **diff = NULL;
    struct ly_ctx *ctx = NULL;
    va_list ap;

    if (!node) {
        LOGARG;
        return EXIT_FAILURE;
    }

    if (lyp_data_check_options(NULL, options, __func__)) {
        return EXIT_FAILURE;
    }

    data_tree = *node;

    if ((!(options & LYD_OPT_TYPEMASK)
            || (options & (LYD_OPT_CONFIG | LYD_OPT_GET | LYD_OPT_GETCONFIG | LYD_OPT_EDIT))) && !(*node)) {
        /* get context with schemas from the var_arg */
        ctx = (struct ly_ctx *)var_arg;
        if (!ctx) {
            LOGERR(NULL, LY_EINVAL, "%s: invalid variable parameter (struct ly_ctx *ctx).", __func__);
            return EXIT_FAILURE;
        }

        /* LYD_OPT_NOSIBLINGS has no meaning here */
        options &= ~LYD_OPT_NOSIBLINGS;
    } else if (options & (LYD_OPT_RPC | LYD_OPT_RPCREPLY | LYD_OPT_NOTIF)) {
        /* LYD_OPT_NOSIBLINGS cannot be set in this case */
        if (options & LYD_OPT_NOSIBLINGS) {
            LOGERR(NULL, LY_EINVAL, "%s: invalid parameter (variable arg const struct lyd_node *data_tree with LYD_OPT_NOSIBLINGS).", __func__);
            return EXIT_FAILURE;
        } else if (!(*node)) {
            LOGARG;
            return EXIT_FAILURE;
        }

        /* get the additional data tree if given */
        data_tree = (struct lyd_node *)var_arg;
        if (data_tree) {
            if (options & LYD_OPT_NOEXTDEPS) {
                LOGERR(NULL, LY_EINVAL, "%s: invalid parameter (variable arg const struct lyd_node *data_tree and LYD_OPT_NOEXTDEPS set).",
                       __func__);
                return EXIT_FAILURE;
            }

            LY_TREE_FOR(data_tree, iter) {
                if (iter->parent) {
                    /* a sibling is not top-level */
                    LOGERR(NULL, LY_EINVAL, "%s: invalid variable parameter (const struct lyd_node *data_tree).", __func__);
                    return EXIT_FAILURE;
                }
            }

            /* move it to the beginning */
            for (; data_tree->prev->next; data_tree = data_tree->prev);
        }
    } else if (options & LYD_OPT_DATA_TEMPLATE) {
        /* get context with schemas from the var_arg */
        if (*node && ((*node)->prev->next || (*node)->next)) {
            /* not allow sibling in top-level */
            LOGERR(NULL, LY_EINVAL, "%s: invalid variable parameter (struct lyd_node *node).", __func__);
            return EXIT_FAILURE;
        }
    }

    if (options & LYD_OPT_VAL_DIFF) {
        va_start(ap, var_arg);
        diff = va_arg(ap, struct lyd_difflist **);
        va_end(ap);
        if (!diff) {
            LOGERR(ctx, LY_EINVAL, "%s: invalid variable parameter (struct lyd_difflist **).", __func__);
            return EXIT_FAILURE;
        }
    }

    if (*node) {
        if (!ctx) {
            ctx = (*node)->schema->module->ctx;
        }
        if (!(options & LYD_OPT_NOSIBLINGS)) {
            /* check that the node is the first sibling */
            while ((*node)->prev->next) {
                *node = (*node)->prev;
            }
        }
    }

    return _lyd_validate(node, data_tree, ctx, NULL, 0, diff, options);
}