Do not use RestUtil.resolveBaseTypeFrom()
[netconf.git] / restconf / restconf-nb-rfc8040 / src / main / java / org / opendaylight / restconf / nb / rfc8040 / utils / parser / YangInstanceIdentifierDeserializer.java
index 7136efbe1785ba151276891c6fd1b86face3e16a..9f73482f41aa55cc19fa14d575e6b422a1e61283 100644 (file)
  */
 package org.opendaylight.restconf.nb.rfc8040.utils.parser;
 
-import com.google.common.base.CharMatcher;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
+import static com.google.common.base.Verify.verify;
+import static com.google.common.base.Verify.verifyNotNull;
+import static java.util.Objects.requireNonNull;
+
 import com.google.common.collect.ImmutableMap;
-import java.util.Iterator;
-import java.util.LinkedList;
+import java.text.ParseException;
+import java.util.ArrayList;
 import java.util.List;
-import org.opendaylight.restconf.common.errors.RestconfError;
-import org.opendaylight.restconf.common.util.RestUtil;
-import org.opendaylight.restconf.common.util.RestconfSchemaUtil;
-import org.opendaylight.restconf.common.validation.RestconfValidationUtils;
-import org.opendaylight.restconf.nb.rfc8040.codecs.RestCodec;
-import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
-import org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants;
-import org.opendaylight.yangtools.concepts.Codec;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.nb.rfc8040.ApiPath;
+import org.opendaylight.restconf.nb.rfc8040.ApiPath.ListInstance;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
-import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
+import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec;
 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
-import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ActionNodeContainer;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.stmt.IdentityEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
-import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
 
 /**
- * Deserializer for {@link String} to {@link YangInstanceIdentifier} for
- * restconf.
- *
+ * Deserializer for {@link String} to {@link YangInstanceIdentifier} for restconf.
  */
 public final class YangInstanceIdentifierDeserializer {
+    public static final class Result {
+        public final @NonNull YangInstanceIdentifier path;
+        public final @NonNull SchemaInferenceStack stack;
+        public final @NonNull SchemaNode node;
+
+        Result(final EffectiveModelContext context) {
+            path = YangInstanceIdentifier.empty();
+            node = requireNonNull(context);
+            stack = SchemaInferenceStack.of(context);
+        }
+
+        Result(final EffectiveModelContext context, final QName qname) {
+            // Legacy behavior: RPCs do not really have a YangInstanceIdentifier, but the rest of the code expects it
+            path = YangInstanceIdentifier.of(qname);
+            stack = SchemaInferenceStack.of(context);
+
+            final var stmt = stack.enterSchemaTree(qname);
+            verify(stmt instanceof RpcDefinition, "Unexpected statement %s", stmt);
+            node = (RpcDefinition) stmt;
+        }
 
-    private YangInstanceIdentifierDeserializer() {
-        throw new UnsupportedOperationException("Util class.");
+        Result(final List<PathArgument> steps, final SchemaInferenceStack stack, final SchemaNode node) {
+            path = YangInstanceIdentifier.create(steps);
+            this.stack = requireNonNull(stack);
+            this.node = requireNonNull(node);
+        }
+    }
+
+    private final @NonNull EffectiveModelContext schemaContext;
+    private final @NonNull ApiPath apiPath;
+
+    private YangInstanceIdentifierDeserializer(final EffectiveModelContext schemaContext, final ApiPath apiPath) {
+        this.schemaContext = requireNonNull(schemaContext);
+        this.apiPath = requireNonNull(apiPath);
     }
 
     /**
-     * Method to create {@link Iterable} from {@link PathArgument} which are
-     * parsing from data by {@link SchemaContext}.
+     * Method to create {@link List} from {@link PathArgument} which are parsing from data by {@link SchemaContext}.
      *
-     * @param schemaContext
-     *             for validate of parsing path arguments
-     * @param data
-     *             path to data
+     * @param schemaContext for validate of parsing path arguments
+     * @param data path to data, in URL string form
      * @return {@link Iterable} of {@link PathArgument}
+     * @throws RestconfDocumentedException the path is not valid
      */
-    public static Iterable<PathArgument> create(final SchemaContext schemaContext, final String data) {
-        final List<PathArgument> path = new LinkedList<>();
-        final MainVarsWrapper variables = new YangInstanceIdentifierDeserializer.MainVarsWrapper(
-                data, DataSchemaContextTree.from(schemaContext).getRoot(),
-                YangInstanceIdentifierDeserializer.MainVarsWrapper.STARTING_OFFSET, schemaContext);
-
-        while (!allCharsConsumed(variables)) {
-            validArg(variables);
-            final QName qname = prepareQName(variables);
-
-            // this is the last identifier (input is consumed) or end of identifier (slash)
-            if (allCharsConsumed(variables)
-                    || currentChar(variables.getOffset(), variables.getData()) == RestconfConstants.SLASH) {
-                prepareIdentifier(qname, path, variables);
-                if (variables.getCurrent() == null) {
-                    path.add(NodeIdentifier.create(qname));
-                } else {
-                    path.add(variables.getCurrent().getIdentifier());
-                }
-            } else if (currentChar(variables.getOffset(),
-                    variables.getData()) == ParserBuilderConstants.Deserializer.EQUAL) {
-                if (nextContextNode(qname, path, variables).getDataSchemaNode() instanceof ListSchemaNode) {
-                    prepareNodeWithPredicates(qname, path, variables);
-                } else {
-                    prepareNodeWithValue(qname, path, variables);
-                }
-            } else {
-                throw new IllegalArgumentException(
-                        "Bad char " + currentChar(variables.getOffset(), variables.getData()) + " on position "
-                                + variables.getOffset() + ".");
-            }
+    public static Result create(final EffectiveModelContext schemaContext, final String data) {
+        final ApiPath path;
+        try {
+            path = ApiPath.parse(requireNonNull(data));
+        } catch (ParseException e) {
+            throw new RestconfDocumentedException("Invalid path '" + data + "' at offset " + e.getErrorOffset(),
+                ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, e);
         }
-
-        return ImmutableList.copyOf(path);
+        return create(schemaContext, path);
     }
 
-    private static void prepareNodeWithPredicates(final QName qname, final List<PathArgument> path,
-                                                  final MainVarsWrapper variables) {
-
-        final DataSchemaNode dataSchemaNode = variables.getCurrent().getDataSchemaNode();
-        checkValid(dataSchemaNode != null, "Data schema node is null", variables.getData(), variables.getOffset());
-
-        final Iterator<QName> keys = ((ListSchemaNode) dataSchemaNode).getKeyDefinition().iterator();
-        final ImmutableMap.Builder<QName, Object> values = ImmutableMap.builder();
-
-        // skip already expected equal sign
-        skipCurrentChar(variables);
+    public static Result create(final EffectiveModelContext schemaContext, final ApiPath path) {
+        return new YangInstanceIdentifierDeserializer(schemaContext, path).parse();
+    }
 
-        // read key value separated by comma
-        while (keys.hasNext() && !allCharsConsumed(variables) && currentChar(variables.getOffset(),
-                variables.getData()) != RestconfConstants.SLASH) {
+    // FIXME: NETCONF-818: this method really needs to report an Inference and optionally a YangInstanceIdentifier
+    // - we need the inference for discerning the correct context
+    // - RPCs do not have a YangInstanceIdentifier
+    // - Actions always have a YangInstanceIdentifier, but it points to their parent
+    // - we need to discern the cases RPC invocation, Action invocation and data tree access quickly
+    //
+    // All of this really is an utter mess because we end up calling into this code from various places which,
+    // for example, should not allow RPCs to be valid targets
+    private Result parse() {
+        final var it = apiPath.steps().iterator();
+        if (!it.hasNext()) {
+            return new Result(schemaContext);
+        }
 
-            // empty key value
-            if (currentChar(variables.getOffset(), variables.getData()) == ParserBuilderConstants.Deserializer.COMMA) {
-                values.put(keys.next(), ParserBuilderConstants.Deserializer.EMPTY_STRING);
-                skipCurrentChar(variables);
-                continue;
+        // First step is somewhat special:
+        // - it has to contain a module qualifier
+        // - it has to consider RPCs, for which we need SchemaContext
+        //
+        // We therefore peel that first iteration here and not worry about those details in further iterations
+        var step = it.next();
+        final var firstModule = RestconfDocumentedException.throwIfNull(step.module(),
+            ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE,
+            "First member must use namespace-qualified form, '%s' does not", step.identifier());
+        var namespace = resolveNamespace(firstModule);
+        var qname = step.identifier().bindTo(namespace);
+
+        // We go through more modern APIs here to get this special out of the way quickly
+        final var optRpc = schemaContext.findModuleStatement(namespace).orElseThrow()
+            .findSchemaTreeNode(RpcEffectiveStatement.class, qname);
+        if (optRpc.isPresent()) {
+            // We have found an RPC match,
+            if (it.hasNext()) {
+                throw new RestconfDocumentedException("First step in the path resolves to RPC '" + qname + "' and "
+                    + "therefore it must be the only step present", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
             }
-
-            // check if next value is parsable
-            RestconfValidationUtils.checkDocumentedError(
-                    ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE
-                            .matches(currentChar(variables.getOffset(), variables.getData())),
-                    RestconfError.ErrorType.PROTOCOL,
-                    RestconfError.ErrorTag.MALFORMED_MESSAGE,
-                    ""
-            );
-
-            // parse value
-            final QName key = keys.next();
-            DataSchemaNode leafSchemaNode = null;
-            if (dataSchemaNode instanceof ListSchemaNode) {
-                leafSchemaNode = ((ListSchemaNode) dataSchemaNode).getDataChildByName(key);
-            } else if (dataSchemaNode instanceof LeafListSchemaNode) {
-                leafSchemaNode = dataSchemaNode;
+            if (step instanceof ListInstance) {
+                throw new RestconfDocumentedException("First step in the path resolves to RPC '" + qname + "' and "
+                    + "therefore it must not contain key values", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
             }
-            final String value = findAndParsePercentEncoded(nextIdentifierFromNextSequence(
-                    ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE, variables));
-            final Object valueByType = prepareValueByType(leafSchemaNode, value, variables);
-            values.put(key, valueByType);
 
-
-            // skip comma
-            if (keys.hasNext() && !allCharsConsumed(variables) && currentChar(
-                    variables.getOffset(), variables.getData()) == ParserBuilderConstants.Deserializer.COMMA) {
-                skipCurrentChar(variables);
-            }
+            return new Result(schemaContext, optRpc.orElseThrow().argument());
         }
 
-        // the last key is considered to be empty
-        if (keys.hasNext()) {
-            if (allCharsConsumed(variables)
-                    || currentChar(variables.getOffset(), variables.getData()) == RestconfConstants.SLASH) {
-                values.put(keys.next(), ParserBuilderConstants.Deserializer.EMPTY_STRING);
+        final var stack = SchemaInferenceStack.of(schemaContext);
+        final var path = new ArrayList<PathArgument>();
+        final SchemaNode node;
+
+        var parentNode = DataSchemaContextTree.from(schemaContext).getRoot();
+        while (true) {
+            final var parentSchema = parentNode.getDataSchemaNode();
+            if (parentSchema instanceof ActionNodeContainer) {
+                final var optAction = ((ActionNodeContainer) parentSchema).findAction(qname);
+                if (optAction.isPresent()) {
+                    if (it.hasNext()) {
+                        throw new RestconfDocumentedException("Request path resolves to action '" + qname + "' and "
+                            + "therefore it must not continue past it", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+                    }
+                    if (step instanceof ListInstance) {
+                        throw new RestconfDocumentedException("Request path resolves to action '" + qname + "' and "
+                            + "therefore it must not contain key values",
+                            ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+                    }
+
+                    // Legacy behavior: Action's path should not include its path, but the rest of the code expects it
+                    path.add(new NodeIdentifier(qname));
+                    stack.enterSchemaTree(qname);
+                    node = optAction.orElseThrow();
+                    break;
+                }
             }
 
-            // there should be no more missing keys
-            RestconfValidationUtils.checkDocumentedError(
-                    !keys.hasNext(),
-                    RestconfError.ErrorType.PROTOCOL,
-                    RestconfError.ErrorTag.MISSING_ATTRIBUTE,
-                    "Key value missing for: " + qname
-            );
-        }
-
-        path.add(new YangInstanceIdentifier.NodeIdentifierWithPredicates(qname, values.build()));
-    }
+            // Resolve the child step with respect to data schema tree
+            final var found = RestconfDocumentedException.throwIfNull(parentNode.enterChild(stack, qname),
+                ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, "Schema for '%s' not found", qname);
 
-    private static Object prepareValueByType(final DataSchemaNode schemaNode, final String value,
-            final MainVarsWrapper vars) {
-        Object decoded = null;
-
-        TypeDefinition<? extends TypeDefinition<?>> typedef = null;
-        if (schemaNode instanceof LeafListSchemaNode) {
-            typedef = ((LeafListSchemaNode) schemaNode).getType();
-        } else {
-            typedef = ((LeafSchemaNode) schemaNode).getType();
-        }
-        final TypeDefinition<?> baseType = RestUtil.resolveBaseTypeFrom(typedef);
-        if (baseType instanceof LeafrefTypeDefinition) {
-            typedef = SchemaContextUtil.getBaseTypeForLeafRef((LeafrefTypeDefinition) baseType, vars.getSchemaContext(),
-                    schemaNode);
-        }
-        final Codec<Object, Object> codec = RestCodec.from(typedef, null, vars.getSchemaContext());
-        decoded = codec.deserialize(value);
-        if (decoded == null) {
-            if (baseType instanceof IdentityrefTypeDefinition) {
-                decoded = toQName(value, schemaNode, vars.getSchemaContext());
+            // Now add all mixins encountered to the path
+            var childNode = found;
+            while (childNode.isMixin()) {
+                path.add(childNode.getIdentifier());
+                childNode = verifyNotNull(childNode.enterChild(stack, qname),
+                    "Mixin %s is missing child for %s while resolving %s", childNode, qname, found);
             }
-        }
-        return decoded;
-    }
 
-    private static Object toQName(final String value, final DataSchemaNode schemaNode,
-            final SchemaContext schemaContext) {
-        final String moduleName = toModuleName(value);
-        final String nodeName = toNodeName(value);
-        final Module module = schemaContext.findModules(moduleName).iterator().next();
-        for (final IdentitySchemaNode identitySchemaNode : module.getIdentities()) {
-            final QName qName = identitySchemaNode.getQName();
-            if (qName.getLocalName().equals(nodeName)) {
-                return qName;
+            final PathArgument pathArg;
+            if (step instanceof ListInstance) {
+                final var values = ((ListInstance) step).keyValues();
+                final var schema = childNode.getDataSchemaNode();
+                pathArg = schema instanceof ListSchemaNode
+                    ? prepareNodeWithPredicates(stack, qname, (ListSchemaNode) schema, values)
+                        : prepareNodeWithValue(stack, qname, schema, values);
+            } else {
+                RestconfDocumentedException.throwIf(childNode.isKeyedEntry(),
+                    ErrorType.PROTOCOL, ErrorTag.MISSING_ATTRIBUTE,
+                    "Entry '%s' requires key or value predicate to be present.", qname);
+                pathArg = childNode.getIdentifier();
             }
-        }
-        return QName.create(schemaNode.getQName().getNamespace(), schemaNode.getQName().getRevision(), nodeName);
-    }
 
-    private static String toNodeName(final String str) {
-        final int idx = str.indexOf(':');
-        if (idx == -1) {
-            return str;
-        }
-
-        if (str.indexOf(':', idx + 1) != -1) {
-            return str;
-        }
+            path.add(pathArg);
 
-        return str.substring(idx + 1);
-    }
+            if (!it.hasNext()) {
+                node = childNode.getDataSchemaNode();
+                break;
+            }
 
-    private static String toModuleName(final String str) {
-        final int idx = str.indexOf(':');
-        if (idx == -1) {
-            return null;
-        }
+            parentNode = childNode;
+            step = it.next();
+            final var module = step.module();
+            if (module != null) {
+                namespace = resolveNamespace(module);
+            }
 
-        if (str.indexOf(':', idx + 1) != -1) {
-            return null;
+            qname = step.identifier().bindTo(namespace);
         }
 
-        return str.substring(0, idx);
+        return new Result(path, stack, node);
     }
 
-    private static QName prepareQName(final MainVarsWrapper variables) {
-        checkValid(
-                ParserBuilderConstants.Deserializer.IDENTIFIER_FIRST_CHAR
-                        .matches(currentChar(variables.getOffset(), variables.getData())),
-                "Identifier must start with character from set 'a-zA-Z_'", variables.getData(), variables.getOffset());
-        final String preparedPrefix = nextIdentifierFromNextSequence(
-                ParserBuilderConstants.Deserializer.IDENTIFIER, variables);
-        final String prefix;
-        final String localName;
-
-        if (allCharsConsumed(variables)) {
-            return getQNameOfDataSchemaNode(preparedPrefix, variables);
+    private NodeIdentifierWithPredicates prepareNodeWithPredicates(final SchemaInferenceStack stack, final QName qname,
+            final @NonNull ListSchemaNode schema, final List<@NonNull String> keyValues) {
+        final var keyDef = schema.getKeyDefinition();
+        final var keySize = keyDef.size();
+        final var varSize = keyValues.size();
+        if (keySize != varSize) {
+            throw new RestconfDocumentedException(
+                "Schema for " + qname + " requires " + keySize + " key values, " + varSize + " supplied",
+                ErrorType.PROTOCOL, keySize > varSize ? ErrorTag.MISSING_ATTRIBUTE : ErrorTag.UNKNOWN_ATTRIBUTE);
         }
 
-        switch (currentChar(variables.getOffset(), variables.getData())) {
-            case RestconfConstants.SLASH:
-                prefix = preparedPrefix;
-                return getQNameOfDataSchemaNode(prefix, variables);
-            case ParserBuilderConstants.Deserializer.COLON:
-                prefix = preparedPrefix;
-                skipCurrentChar(variables);
-                checkValid(
-                        ParserBuilderConstants.Deserializer.IDENTIFIER_FIRST_CHAR
-                                .matches(currentChar(variables.getOffset(), variables.getData())),
-                        "Identifier must start with character from set 'a-zA-Z_'", variables.getData(),
-                        variables.getOffset());
-                localName = nextIdentifierFromNextSequence(ParserBuilderConstants.Deserializer.IDENTIFIER, variables);
-
-                if (!allCharsConsumed(variables) && currentChar(
-                        variables.getOffset(), variables.getData()) == ParserBuilderConstants.Deserializer.EQUAL) {
-                    return getQNameOfDataSchemaNode(localName, variables);
-                } else {
-                    final Module module = moduleForPrefix(prefix, variables.getSchemaContext());
-                    Preconditions.checkArgument(module != null, "Failed to lookup prefix %s", prefix);
-                    return QName.create(module.getQNameModule(), localName);
-                }
-            case ParserBuilderConstants.Deserializer.EQUAL:
-                prefix = preparedPrefix;
-                return getQNameOfDataSchemaNode(prefix, variables);
-            default:
-                throw new IllegalArgumentException("Failed build path.");
-        }
-    }
-
-    private static String nextIdentifierFromNextSequence(final CharMatcher matcher, final MainVarsWrapper variables) {
-        final int start = variables.getOffset();
-        nextSequenceEnd(matcher, variables);
-        return variables.getData().substring(start, variables.getOffset());
-    }
-
-    private static void nextSequenceEnd(final CharMatcher matcher, final MainVarsWrapper variables) {
-        while (!allCharsConsumed(variables)
-                && matcher.matches(variables.getData().charAt(variables.getOffset()))) {
-            variables.setOffset(variables.getOffset() + 1);
+        final var values = ImmutableMap.<QName, Object>builderWithExpectedSize(keySize);
+        final var tmp = stack.copy();
+        for (int i = 0; i < keySize; ++i) {
+            final QName keyName = keyDef.get(i);
+            final var child = schema.getDataChildByName(keyName);
+            tmp.enterSchemaTree(keyName);
+            values.put(keyName, prepareValueByType(tmp, child, keyValues.get(i)));
+            tmp.exit();
         }
-    }
 
-    private static void prepareNodeWithValue(final QName qname, final List<PathArgument> path,
-            final MainVarsWrapper variables) {
-        skipCurrentChar(variables);
-        final String value = nextIdentifierFromNextSequence(
-                ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE, variables);
-
-        // exception if value attribute is missing
-        RestconfValidationUtils.checkDocumentedError(
-                !value.isEmpty(),
-                RestconfError.ErrorType.PROTOCOL,
-                RestconfError.ErrorTag.MISSING_ATTRIBUTE,
-                "Value missing for: " + qname
-        );
-        final DataSchemaNode dataSchemaNode = variables.getCurrent().getDataSchemaNode();
-        final Object valueByType = prepareValueByType(dataSchemaNode, findAndParsePercentEncoded(value), variables);
-        path.add(new YangInstanceIdentifier.NodeWithValue<>(qname, valueByType));
+        return NodeIdentifierWithPredicates.of(qname, values.build());
     }
 
-    private static void prepareIdentifier(final QName qname, final List<PathArgument> path,
-            final MainVarsWrapper variables) {
-        final DataSchemaContextNode<?> currentNode = nextContextNode(qname, path, variables);
-        if (currentNode == null) {
-            return;
-        }
-        checkValid(!currentNode.isKeyedEntry(), "Entry " + qname + " requires key or value predicate to be present",
-                variables.getData(), variables.getOffset());
-    }
+    private Object prepareValueByType(final SchemaInferenceStack stack, final DataSchemaNode schemaNode,
+            final @NonNull String value) {
 
-    private static DataSchemaContextNode<?> nextContextNode(final QName qname, final List<PathArgument> path,
-            final MainVarsWrapper variables) {
-        variables.setCurrent(variables.getCurrent().getChild(qname));
-        DataSchemaContextNode<?> current = variables.getCurrent();
-        if (current == null) {
-            for (final RpcDefinition rpcDefinition : variables.getSchemaContext()
-                    .findModule(qname.getModule()).orElse(null).getRpcs()) {
-                if (rpcDefinition.getQName().getLocalName().equals(qname.getLocalName())) {
-                    return null;
-                }
-            }
-        }
-        checkValid(current != null, qname + " is not correct schema node identifier.", variables.getData(),
-                variables.getOffset());
-        while (current.isMixin()) {
-            path.add(current.getIdentifier());
-            current = current.getChild(qname);
-            variables.setCurrent(current);
+        TypeDefinition<? extends TypeDefinition<?>> typedef;
+        if (schemaNode instanceof LeafListSchemaNode) {
+            typedef = ((LeafListSchemaNode) schemaNode).getType();
+        } else {
+            typedef = ((LeafSchemaNode) schemaNode).getType();
         }
-        return current;
-    }
-
-    private static String findAndParsePercentEncoded(final String preparedPrefix) {
-        if (!preparedPrefix.contains(String.valueOf(ParserBuilderConstants.Deserializer.PERCENT_ENCODING))) {
-            return preparedPrefix;
+        if (typedef instanceof LeafrefTypeDefinition) {
+            typedef = stack.resolveLeafref((LeafrefTypeDefinition) typedef);
         }
 
-        final StringBuilder parsedPrefix = new StringBuilder(preparedPrefix);
-        final CharMatcher matcher = CharMatcher.is(ParserBuilderConstants.Deserializer.PERCENT_ENCODING);
-
-        while (matcher.matchesAnyOf(parsedPrefix)) {
-            final int percentCharPosition = matcher.indexIn(parsedPrefix);
-            parsedPrefix.replace(
-                    percentCharPosition,
-                    percentCharPosition + ParserBuilderConstants.Deserializer.LAST_ENCODED_CHAR,
-                    String.valueOf((char) Integer.parseInt(parsedPrefix.substring(
-                            percentCharPosition + ParserBuilderConstants.Deserializer.FIRST_ENCODED_CHAR,
-                            percentCharPosition + ParserBuilderConstants.Deserializer.LAST_ENCODED_CHAR),
-                            ParserBuilderConstants.Deserializer.PERCENT_ENCODED_RADIX)));
+        if (typedef instanceof IdentityrefTypeDefinition) {
+            return toIdentityrefQName(value, schemaNode);
         }
 
-        return parsedPrefix.toString();
-    }
+        try {
+            if (typedef instanceof InstanceIdentifierTypeDefinition) {
+                return new StringModuleInstanceIdentifierCodec(schemaContext).deserialize(value);
+            }
 
-    private static QName getQNameOfDataSchemaNode(final String nodeName, final MainVarsWrapper variables) {
-        final DataSchemaNode dataSchemaNode = variables.getCurrent().getDataSchemaNode();
-        if (dataSchemaNode instanceof ContainerSchemaNode) {
-            final ContainerSchemaNode contSchemaNode = (ContainerSchemaNode) dataSchemaNode;
-            final DataSchemaNode node = RestconfSchemaUtil.findSchemaNodeInCollection(contSchemaNode.getChildNodes(),
-                    nodeName);
-            return node.getQName();
-        } else if (dataSchemaNode instanceof ListSchemaNode) {
-            final ListSchemaNode listSchemaNode = (ListSchemaNode) dataSchemaNode;
-            final DataSchemaNode node = RestconfSchemaUtil.findSchemaNodeInCollection(listSchemaNode.getChildNodes(),
-                    nodeName);
-            return node.getQName();
+            return verifyNotNull(TypeDefinitionAwareCodec.from(typedef),
+                "Unhandled type %s decoding %s", typedef, value).deserialize(value);
+        } catch (IllegalArgumentException e) {
+            throw new RestconfDocumentedException("Invalid value '" + value + "' for " + schemaNode.getQName(),
+                ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e);
         }
-        throw new UnsupportedOperationException();
     }
 
-    private static Module moduleForPrefix(final String prefix, final SchemaContext schemaContext) {
-        return schemaContext.findModules(prefix).stream().findFirst().orElse(null);
+    private NodeWithValue<?> prepareNodeWithValue(final SchemaInferenceStack stack, final QName qname,
+            final DataSchemaNode schema, final List<String> keyValues) {
+        // TODO: qname should be always equal to schema.getQName(), right?
+        return new NodeWithValue<>(qname, prepareValueByType(stack, schema,
+            // FIXME: ahem: we probably want to do something differently here
+            keyValues.get(0)));
     }
 
-    private static void validArg(final MainVarsWrapper variables) {
-        // every identifier except of the first MUST start with slash
-        if (variables.getOffset() != MainVarsWrapper.STARTING_OFFSET) {
-            checkValid(RestconfConstants.SLASH == currentChar(variables.getOffset(), variables.getData()),
-                    "Identifier must start with '/'.", variables.getData(), variables.getOffset());
-
-            // skip slash
-            skipCurrentChar(variables);
-
-            // check if slash is not also the last char in identifier
-            checkValid(!allCharsConsumed(variables), "Identifier cannot end with '/'.",
-                    variables.getData(), variables.getOffset());
+    private QName toIdentityrefQName(final String value, final DataSchemaNode schemaNode) {
+        final QNameModule namespace;
+        final String localName;
+        final int firstColon = value.indexOf(':');
+        if (firstColon != -1) {
+            namespace = resolveNamespace(value.substring(0, firstColon));
+            localName = value.substring(firstColon + 1);
+        } else {
+            namespace = schemaNode.getQName().getModule();
+            localName = value;
         }
-    }
-
-    private static void skipCurrentChar(final MainVarsWrapper variables) {
-        variables.setOffset(variables.getOffset() + 1);
-    }
-
-    private static char currentChar(final int offset, final String data) {
-        return data.charAt(offset);
-    }
-
-    private static void checkValid(final boolean condition, final String errorMsg, final String data,
-            final int offset) {
-        Preconditions.checkArgument(condition, "Could not parse Instance Identifier '%s'. Offset: %s : Reason: %s",
-                data, offset, errorMsg);
-    }
 
-    private static boolean allCharsConsumed(final MainVarsWrapper variables) {
-        return variables.getOffset() == variables.getData().length();
+        return schemaContext.getModuleStatement(namespace)
+            .streamEffectiveSubstatements(IdentityEffectiveStatement.class)
+            .map(IdentityEffectiveStatement::argument)
+            .filter(qname -> localName.equals(qname.getLocalName()))
+            .findFirst()
+            .orElseThrow(() -> new RestconfDocumentedException(
+                "No identity found for '" + localName + "' in namespace " + namespace,
+                ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE));
     }
 
-    private static final class MainVarsWrapper {
-        private static final int STARTING_OFFSET = 0;
-
-        private final SchemaContext schemaContext;
-        private final String data;
-
-        private DataSchemaContextNode<?> current;
-        private int offset;
-
-        MainVarsWrapper(final String data, final DataSchemaContextNode<?> current, final int offset,
-                final SchemaContext schemaContext) {
-            this.data = data;
-            this.current = current;
-            this.offset = offset;
-            this.schemaContext = schemaContext;
-        }
-
-        public String getData() {
-            return this.data;
-        }
-
-        public DataSchemaContextNode<?> getCurrent() {
-            return this.current;
-        }
-
-        public void setCurrent(final DataSchemaContextNode<?> current) {
-            this.current = current;
-        }
-
-        public int getOffset() {
-            return this.offset;
-        }
-
-        public void setOffset(final int offset) {
-            this.offset = offset;
-        }
-
-        public SchemaContext getSchemaContext() {
-            return this.schemaContext;
-        }
+    private @NonNull QNameModule resolveNamespace(final String moduleName) {
+        final var modules = schemaContext.findModules(moduleName);
+        RestconfDocumentedException.throwIf(modules.isEmpty(), ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT,
+            "Failed to lookup for module with name '%s'.", moduleName);
+        return modules.iterator().next().getQNameModule();
     }
 }