Fix yang-data extension definition 50/102650/1
authorPeter Suna <peter.suna@pantheon.tech>
Thu, 26 May 2022 09:58:49 +0000 (11:58 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Wed, 12 Oct 2022 15:09:22 +0000 (17:09 +0200)
The definition on ietf-restconf's yang-data is being rather exploited in
the wild, with a license to contain choices and anydata. These are
essentially allowed by other tooling, hence we need to also relax our
requirements.

This implies we cannot really expose the container directly, but rather
have to implement DataNodeContainer instead -- with the implied API
breakage. This is fine due to @Beta-level contract of all interfaces
involved.

Since we are breaking semantics here, also ditch the notion that the
yang-data argument can be parsed into a QName and instead reuse the
schema tree child's QName as the value required by SchemaNode contract.

JIRA: YANGTOOLS-1443
Change-Id: I3f78f71ea2980bf84f409d1ec89979e9e7b200ae
Signed-off-by: Peter Suna <peter.suna@pantheon.tech>
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
(cherry picked from commit 6dec3b24ef9055b777e7ea09b2006a1f867da827)

model/rfc8040-model-api/src/main/java/org/opendaylight/yangtools/rfc8040/model/api/YangDataEffectiveStatement.java
model/rfc8040-model-api/src/main/java/org/opendaylight/yangtools/rfc8040/model/api/YangDataSchemaNode.java
parser/rfc8040-parser-support/src/main/java/org/opendaylight/yangtools/rfc8040/parser/YangDataArgumentNamespace.java [deleted file]
parser/rfc8040-parser-support/src/main/java/org/opendaylight/yangtools/rfc8040/parser/YangDataEffectiveStatementImpl.java
parser/rfc8040-parser-support/src/main/java/org/opendaylight/yangtools/rfc8040/parser/YangDataStatementSupport.java
parser/rfc8040-parser-support/src/test/java/org/opendaylight/yangtools/rfc8040/parser/AbstractYangDataTest.java
parser/rfc8040-parser-support/src/test/java/org/opendaylight/yangtools/rfc8040/parser/YT1443Test.java [new file with mode: 0644]
parser/rfc8040-parser-support/src/test/java/org/opendaylight/yangtools/rfc8040/parser/YangDataExtensionTest.java
parser/rfc8040-parser-support/src/test/resources/yt1443.yang [new file with mode: 0644]
parser/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/impl/DefaultReactors.java

index b10ebb7c354785cbdf5648ada67b691f79693d21..e437cf2135678fa75fded867ee4f37d674948d2f 100644 (file)
@@ -8,9 +8,7 @@
 package org.opendaylight.yangtools.rfc8040.model.api;
 
 import com.google.common.annotations.Beta;
-import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
-import org.opendaylight.yangtools.yang.model.api.stmt.ContainerEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeAwareEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.UnknownEffectiveStatement;
 
@@ -25,11 +23,4 @@ public interface YangDataEffectiveStatement extends UnknownEffectiveStatement<St
     default StatementDefinition statementDefinition() {
         return YangDataStatements.YANG_DATA;
     }
-
-    /**
-     * Return the container statement defined in this yang-data statement instance.
-     *
-     * @return container statement
-     */
-    @NonNull ContainerEffectiveStatement getContainer();
 }
index 8dfdc6fcb977836ea67b2343ec6908cf7ef9e98a..0e1224020c8655b8cfb7b60cbee27e5252897987 100644 (file)
@@ -8,24 +8,16 @@
 package org.opendaylight.yangtools.rfc8040.model.api;
 
 import com.google.common.annotations.Beta;
-import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
 import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
 
 /**
  * Represents 'yang-data' extension statement defined in
  * <a href="https://tools.ietf.org/html/rfc8040#section-8">RFC8040</a>. This statement must appear as a top-level
- * statement, otherwise it is ignored and does not appear in the final schema context. It must contain exactly one
- * top-level container node (directly or indirectly via a uses statement).
+ * statement, otherwise it is ignored and does not appear in the final schema context.
  */
 @Beta
-public interface YangDataSchemaNode extends UnknownSchemaNode {
-    /**
-     * Returns container schema node container within this yang-data definition.
-     *
-     * @return container schema node
-     */
-    ContainerSchemaNode getContainerSchemaNode();
-
+public interface YangDataSchemaNode extends UnknownSchemaNode, DataNodeContainer {
     @Override
     YangDataEffectiveStatement asEffectiveStatement();
 }
diff --git a/parser/rfc8040-parser-support/src/main/java/org/opendaylight/yangtools/rfc8040/parser/YangDataArgumentNamespace.java b/parser/rfc8040-parser-support/src/main/java/org/opendaylight/yangtools/rfc8040/parser/YangDataArgumentNamespace.java
deleted file mode 100644 (file)
index c051872..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others.  All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.yangtools.rfc8040.parser;
-
-import com.google.common.annotations.Beta;
-import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.yangtools.yang.common.Empty;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour;
-import org.opendaylight.yangtools.yang.parser.spi.meta.ParserNamespace;
-
-/**
- * Namespace for remembering the {@code yang-data} argument's QName.
- */
-@Beta
-// FIXME: We should not be needing this namespace, as yang-data's argument is not documented anywhere to be compatible
-//        with 'identifier', hence we cannot safely form a QName.
-public interface YangDataArgumentNamespace extends ParserNamespace<Empty, QName> {
-    NamespaceBehaviour<Empty, QName, @NonNull YangDataArgumentNamespace> BEHAVIOUR =
-        NamespaceBehaviour.statementLocal(YangDataArgumentNamespace.class);
-
-}
index 3b083715141837d907ccb6af7c1ab1afaa53a134..13440a836790e82955fadd63b711aea78c3976ef 100644 (file)
@@ -19,45 +19,34 @@ import org.opendaylight.yangtools.rfc8040.model.api.YangDataEffectiveStatement;
 import org.opendaylight.yangtools.rfc8040.model.api.YangDataSchemaNode;
 import org.opendaylight.yangtools.rfc8040.model.api.YangDataStatement;
 import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.meta.IdentifierNamespace;
-import org.opendaylight.yangtools.yang.model.api.stmt.ContainerEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeAwareEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.spi.meta.AbstractEffectiveUnknownSchmemaNode;
+import org.opendaylight.yangtools.yang.model.spi.meta.EffectiveStatementMixins.DataNodeContainerMixin;
 import org.opendaylight.yangtools.yang.parser.spi.meta.EffectiveStmtCtx.Current;
 
 @Beta
 final class YangDataEffectiveStatementImpl extends AbstractEffectiveUnknownSchmemaNode<String, YangDataStatement>
-        implements YangDataEffectiveStatement, YangDataSchemaNode {
-    private final @NonNull QName argumentQName;
-    private final @NonNull ContainerEffectiveStatement container;
+        implements YangDataEffectiveStatement, YangDataSchemaNode, DataNodeContainerMixin<String, YangDataStatement> {
+    private final @NonNull DataSchemaNode child;
 
     YangDataEffectiveStatementImpl(final Current<String, YangDataStatement> stmt,
-             final ImmutableList<? extends EffectiveStatement<?, ?>> substatements, final QName qname) {
+             final ImmutableList<? extends EffectiveStatement<?, ?>> substatements, final DataSchemaNode child) {
         super(stmt.declared(), stmt.argument(), stmt.history(), substatements);
-        argumentQName = requireNonNull(qname);
-
-        container = findFirstEffectiveSubstatement(ContainerEffectiveStatement.class).get();
-
-        // TODO: this is strong binding of two API contracts. Unfortunately ContainerEffectiveStatement design is
-        //       incomplete.
-        verify(container instanceof ContainerSchemaNode, "Incompatible container %s", container);
+        this.child = requireNonNull(child);
     }
 
     @Override
     public QName getQName() {
-        return argumentQName;
-    }
-
-    @Override
-    public ContainerEffectiveStatement getContainer() {
-        return container;
+        return child.getQName();
     }
 
     @Override
-    public ContainerSchemaNode getContainerSchemaNode() {
-        // Verified in the constructor
-        return (ContainerSchemaNode) container;
+    public DataSchemaNode dataChildByName(final QName name) {
+        return name.equals(child.getQName()) ? child : null;
     }
 
     @Override
@@ -68,11 +57,23 @@ final class YangDataEffectiveStatementImpl extends AbstractEffectiveUnknownSchme
     @Override
     protected <K, V, N extends IdentifierNamespace<K, V>> Optional<? extends Map<K, V>> getNamespaceContents(
             final Class<N> namespace) {
-        if (SchemaTreeNamespace.class.equals(namespace) || DataTreeNamespace.class.equals(namespace)) {
-            @SuppressWarnings("unchecked")
-            final Map<K, V> ns = (Map<K, V>)Map.of(container.argument(), container);
-            return Optional.of(ns);
+        if (SchemaTreeNamespace.class.equals(namespace)) {
+            return castChild();
+        }
+        if (DataTreeNamespace.class.equals(namespace)) {
+            if (child instanceof DataTreeEffectiveStatement) {
+                return castChild();
+            }
+
+            // A schema tree statement which *has to* know about data tree -- just forward it
+            verify(child instanceof DataTreeAwareEffectiveStatement, "Unexpected child %s", child);
+            return Optional.of(((DataTreeAwareEffectiveStatement<?, ?>) child).getAll(namespace));
         }
         return super.getNamespaceContents(namespace);
     }
+
+    @SuppressWarnings("unchecked")
+    private <K, V> Optional<Map<K, V>> castChild() {
+        return Optional.of((Map<K, V>) Map.of(child.getQName(), child));
+    }
 }
index 6c18978c6cf8ca86666ff5ddd662e6e1013a6d54..7b316290cc92310ed3fe0c48fe7be9f30e1bdcbf 100644 (file)
@@ -7,20 +7,22 @@
  */
 package org.opendaylight.yangtools.rfc8040.parser;
 
-import static com.google.common.base.Verify.verifyNotNull;
+import static com.google.common.base.Verify.verify;
 
 import com.google.common.annotations.Beta;
 import com.google.common.collect.ImmutableList;
+import java.util.stream.Collectors;
 import org.opendaylight.yangtools.rfc8040.model.api.YangDataEffectiveStatement;
 import org.opendaylight.yangtools.rfc8040.model.api.YangDataStatement;
 import org.opendaylight.yangtools.rfc8040.model.api.YangDataStatements;
-import org.opendaylight.yangtools.yang.common.Empty;
-import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
 import org.opendaylight.yangtools.yang.model.api.meta.DeclarationReference;
 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.ChoiceEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.UsesEffectiveStatement;
 import org.opendaylight.yangtools.yang.parser.api.YangParserConfiguration;
 import org.opendaylight.yangtools.yang.parser.spi.meta.AbstractStringStatementSupport;
@@ -29,16 +31,39 @@ import org.opendaylight.yangtools.yang.parser.spi.meta.EffectiveStmtCtx.Current;
 import org.opendaylight.yangtools.yang.parser.spi.meta.InvalidSubstatementException;
 import org.opendaylight.yangtools.yang.parser.spi.meta.MissingSubstatementException;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.Mutable;
-import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
 import org.opendaylight.yangtools.yang.parser.spi.meta.SubstatementValidator;
 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
 
 @Beta
 public final class YangDataStatementSupport
         extends AbstractStringStatementSupport<YangDataStatement, YangDataEffectiveStatement> {
+    // As per RFC8040 page 81:
+    //
+    //    The substatements of this extension MUST follow the
+    //    'data-def-stmt' rule in the YANG ABNF.
+    //
+    // As per RFC7950 page 185:
+    //
+    //    data-def-stmt = container-stmt /
+    //                    leaf-stmt /
+    //                    leaf-list-stmt /
+    //                    list-stmt /
+    //                    choice-stmt /
+    //                    anydata-stmt /
+    //                    anyxml-stmt /
+    //                    uses-stmt
+    //
+    // The cardinality is not exactly constrained, but the entirety of substatements are required to resolve to a single
+    // XML document (page 80). This is enforced when we arrive at full declaration.
     private static final SubstatementValidator VALIDATOR = SubstatementValidator.builder(YangDataStatements.YANG_DATA)
-        .addMandatory(YangStmtMapping.CONTAINER)
-        .addOptional(YangStmtMapping.USES)
+        .addAny(YangStmtMapping.CONTAINER)
+        .addAny(YangStmtMapping.LEAF)
+        .addAny(YangStmtMapping.LEAF_LIST)
+        .addAny(YangStmtMapping.LIST)
+        .addAny(YangStmtMapping.CHOICE)
+        .addAny(YangStmtMapping.ANYDATA)
+        .addAny(YangStmtMapping.ANYXML)
+        .addAny(YangStmtMapping.USES)
         .build();
 
     public YangDataStatementSupport(final YangParserConfiguration config) {
@@ -56,10 +81,13 @@ public final class YangDataStatementSupport
 
     @Override
     public void onFullDefinitionDeclared(final Mutable<String, YangDataStatement, YangDataEffectiveStatement> ctx) {
-        // Parse and populate our argument to be picked up when we build the effective statement
-        final String argument = SourceException.throwIfNull(ctx.argument(), ctx, "yang-data requires an argument");
-        final QName qname = StmtContextUtils.parseIdentifier(ctx, argument);
-        ctx.addToNs(YangDataArgumentNamespace.class, Empty.value(), qname);
+        // If we are declared in an illegal place, this becomes a no-op
+        if (!ctx.isSupportedToBuildEffective()) {
+            return;
+        }
+
+        // Run SubstatementValidator-based validation first
+        super.onFullDefinitionDeclared(ctx);
 
         // Support for 'operations' container semantics. For this we need to recognize when the model at hand matches
         // RFC8040 ietf-restconf module. In ordered to do that we hook onto this particular definition:
@@ -69,7 +97,7 @@ public final class YangDataStatementSupport
         //   }
         //
         // If we find it, we hook an inference action which performs the next step when the module is fully declared.
-        if (ctx.isSupportedToBuildEffective() && "yang-api".equals(ctx.argument())) {
+        if ("yang-api".equals(ctx.argument())) {
             final var stmts = ctx.declaredSubstatements();
             if (stmts.size() == 1) {
                 final var stmt = stmts.iterator().next();
@@ -107,19 +135,36 @@ public final class YangDataStatementSupport
     @Override
     protected YangDataEffectiveStatement createEffective(final Current<String, YangDataStatement> stmt,
             final ImmutableList<? extends EffectiveStatement<?, ?>> substatements) {
-        // So now we need to deal with effective validation. The requirement is that:
-        //        It MUST contain data definition statements
-        //        that result in exactly one container data node definition.
-        final long dataDefs = substatements.stream().filter(DataTreeEffectiveStatement.class::isInstance).count();
-        if (dataDefs == 0) {
-            throw new MissingSubstatementException("yang-data requires exactly one container", stmt.sourceReference());
-        }
-        if (dataDefs > 1) {
-            throw new InvalidSubstatementException(stmt,
-                "yang-data requires exactly one data definition node, found %s", dataDefs);
-        }
+        // RFC8040 page 80 requires that:
+        //    It MUST contain data definition statements
+        //    that result in exactly one container data node definition.
+        //    An instance of a YANG data template can thus be translated
+        //    into an XML instance document, whose top-level element
+        //    corresponds to the top-level container.
+        //
+        // We validate this additional constraint when we arrive at the effective model, with the view that
+        // 'container data node definition' is really meant to say 'XML element'.
+        //
+        // This really boils down to the requirement to have a single schema tree substatement, which needs to either
+        // be a data tree statement or a choice statement.
+        final var schemaSub = substatements.stream()
+            .filter(SchemaTreeEffectiveStatement.class::isInstance)
+            .map(SchemaTreeEffectiveStatement.class::cast)
+            .collect(Collectors.toUnmodifiableList());
+        final var child = switch (schemaSub.size()) {
+            case 0 -> throw new MissingSubstatementException(stmt, "yang-data requires at least one substatement");
+            case 1 -> {
+                final SchemaTreeEffectiveStatement<?> substmt = schemaSub.get(0);
+                SourceException.throwIf(
+                    !(substmt instanceof ChoiceEffectiveStatement) && !(substmt instanceof DataTreeEffectiveStatement),
+                    stmt, "%s is not a recognized container data node definition", substmt);
+                verify(substmt instanceof DataSchemaNode, "Unexpected single child %s", substmt);
+                yield (DataSchemaNode) substmt;
+            }
+            default -> throw new InvalidSubstatementException(stmt,
+                "yang-data requires exactly one container data node definition, found %s", schemaSub);
+        };
 
-        return new YangDataEffectiveStatementImpl(stmt, substatements,
-            verifyNotNull(stmt.namespaceItem(YangDataArgumentNamespace.class, Empty.value())));
+        return new YangDataEffectiveStatementImpl(stmt, substatements, child);
     }
 }
index 5a67d5972708b146d22b17ab7669aad09ddb4b0c..43943ebd274c1369f9fe7eb96257637a8d1c3e3d 100644 (file)
@@ -28,7 +28,6 @@ public abstract class AbstractYangDataTest {
     @BeforeClass
     public static void createReactor() {
         REACTOR = RFC7950Reactors.vanillaReactorBuilder()
-                .addNamespaceSupport(ModelProcessingPhase.FULL_DECLARATION, YangDataArgumentNamespace.BEHAVIOUR)
                 .addStatementSupport(ModelProcessingPhase.FULL_DECLARATION,
                     new YangDataStatementSupport(YangParserConfiguration.DEFAULT))
                 .build();
diff --git a/parser/rfc8040-parser-support/src/test/java/org/opendaylight/yangtools/rfc8040/parser/YT1443Test.java b/parser/rfc8040-parser-support/src/test/java/org/opendaylight/yangtools/rfc8040/parser/YT1443Test.java
new file mode 100644 (file)
index 0000000..622cb51
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2022 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.rfc8040.parser;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
+
+public class YT1443Test extends AbstractYangDataTest {
+    @Test
+    public void buildEffectiveModelTest() throws ReactorException {
+        final var module = REACTOR.newBuild()
+            .addSources(IETF_RESTCONF_MODULE, sourceForResource("/yt1443.yang"))
+            .buildEffective()
+            .findModuleStatement(QName.create("yt1443", "yt1443"))
+            .orElseThrow();
+        assertNotNull(module);
+    }
+}
index f06eb4cf757329fe6e8c8f859a5701fe57c1f6c2..6fc35f83daf48ce86e30773598379095342dd17c 100644 (file)
@@ -13,7 +13,6 @@ import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.assertTrue;
 
 import com.google.common.collect.ImmutableSet;
 import java.util.Collection;
@@ -25,6 +24,7 @@ import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.common.Revision;
 import org.opendaylight.yangtools.yang.common.XMLNamespace;
 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ExtensionDefinition;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
@@ -54,8 +54,6 @@ public class YangDataExtensionTest extends AbstractYangDataTest {
 
     private static final Revision REVISION = Revision.of("2017-06-01");
     private static final QNameModule FOO_QNAMEMODULE = QNameModule.create(XMLNamespace.of("foo"), REVISION);
-    private static final QName MY_YANG_DATA_A = QName.create(FOO_QNAMEMODULE, "my-yang-data-a");
-    private static final QName MY_YANG_DATA_B = QName.create(FOO_QNAMEMODULE, "my-yang-data-b");
 
     @Test
     public void testYangData() throws Exception {
@@ -73,20 +71,17 @@ public class YangDataExtensionTest extends AbstractYangDataTest {
         YangDataSchemaNode myYangDataANode = null;
         YangDataSchemaNode myYangDataBNode = null;
         for (final UnknownSchemaNode unknownSchemaNode : unknownSchemaNodes) {
-            assertTrue(unknownSchemaNode instanceof YangDataSchemaNode);
+            assertThat(unknownSchemaNode, instanceOf(YangDataSchemaNode.class));
             final YangDataSchemaNode yangDataSchemaNode = (YangDataSchemaNode) unknownSchemaNode;
-            if (MY_YANG_DATA_A.equals(yangDataSchemaNode.getQName())) {
+            if ("my-yang-data-a".equals(yangDataSchemaNode.getNodeParameter())) {
                 myYangDataANode = yangDataSchemaNode;
-            } else if (MY_YANG_DATA_B.equals(yangDataSchemaNode.getQName())) {
+            } else if ("my-yang-data-b".equals(yangDataSchemaNode.getNodeParameter())) {
                 myYangDataBNode = yangDataSchemaNode;
             }
         }
 
         assertNotNull(myYangDataANode);
         assertNotNull(myYangDataBNode);
-
-        assertNotNull(myYangDataANode.getContainerSchemaNode());
-        assertNotNull(myYangDataBNode.getContainerSchemaNode());
     }
 
     @Test
@@ -100,19 +95,23 @@ public class YangDataExtensionTest extends AbstractYangDataTest {
         assertEquals(1, unknownSchemaNodes.size());
 
         final UnknownSchemaNode unknownSchemaNode = unknownSchemaNodes.iterator().next();
-        assertTrue(unknownSchemaNode instanceof YangDataSchemaNode);
+        assertThat(unknownSchemaNode, instanceOf(YangDataSchemaNode.class));
         final YangDataSchemaNode myYangDataNode = (YangDataSchemaNode) unknownSchemaNode;
         assertNotNull(myYangDataNode);
 
-        final ContainerSchemaNode contInYangData = myYangDataNode.getContainerSchemaNode();
-        assertNotNull(contInYangData);
+        final Collection<? extends DataSchemaNode> yangDataChildren = myYangDataNode.getChildNodes();
+        assertEquals(1, yangDataChildren.size());
+
+        final DataSchemaNode childInYangData = yangDataChildren.iterator().next();
+        assertThat(childInYangData, instanceOf(ContainerSchemaNode.class));
+        final ContainerSchemaNode contInYangData = (ContainerSchemaNode) childInYangData;
         assertEquals(Optional.empty(), contInYangData.effectiveConfig());
-        final ContainerSchemaNode innerCont = (ContainerSchemaNode) contInYangData.findDataChildByName(
-                QName.create(baz.getQNameModule(), "inner-cont")).get();
+        final ContainerSchemaNode innerCont = (ContainerSchemaNode) contInYangData.getDataChildByName(
+                QName.create(baz.getQNameModule(), "inner-cont"));
         assertNotNull(innerCont);
         assertEquals(Optional.empty(), innerCont.effectiveConfig());
-        final ContainerSchemaNode grpCont = (ContainerSchemaNode) contInYangData.findDataChildByName(
-                QName.create(baz.getQNameModule(), "grp-cont")).get();
+        final ContainerSchemaNode grpCont = (ContainerSchemaNode) contInYangData.getDataChildByName(
+                QName.create(baz.getQNameModule(), "grp-cont"));
         assertNotNull(grpCont);
         assertEquals(Optional.empty(), grpCont.effectiveConfig());
     }
@@ -128,17 +127,21 @@ public class YangDataExtensionTest extends AbstractYangDataTest {
         assertEquals(1, unknownSchemaNodes.size());
 
         final UnknownSchemaNode unknownSchemaNode = unknownSchemaNodes.iterator().next();
-        assertTrue(unknownSchemaNode instanceof YangDataSchemaNode);
+        assertThat(unknownSchemaNode, instanceOf(YangDataSchemaNode.class));
         final YangDataSchemaNode myYangDataNode = (YangDataSchemaNode) unknownSchemaNode;
         assertNotNull(myYangDataNode);
 
-        final ContainerSchemaNode contInYangData = myYangDataNode.getContainerSchemaNode();
-        assertNotNull(contInYangData);
-        final ContainerSchemaNode innerCont = (ContainerSchemaNode) contInYangData.findDataChildByName(
-                QName.create(foobar.getQNameModule(), "inner-cont")).get();
+        final Collection<? extends DataSchemaNode> yangDataChildren = myYangDataNode.getChildNodes();
+        assertEquals(1, yangDataChildren.size());
+
+        final DataSchemaNode childInYangData = yangDataChildren.iterator().next();
+        assertThat(childInYangData, instanceOf(ContainerSchemaNode.class));
+        final ContainerSchemaNode contInYangData = (ContainerSchemaNode) childInYangData;
+        final ContainerSchemaNode innerCont = (ContainerSchemaNode) contInYangData.getDataChildByName(
+                QName.create(foobar.getQNameModule(), "inner-cont"));
         assertNotNull(innerCont);
-        final ContainerSchemaNode grpCont = (ContainerSchemaNode) contInYangData.findDataChildByName(
-                QName.create(foobar.getQNameModule(), "grp-cont")).get();
+        final ContainerSchemaNode grpCont = (ContainerSchemaNode) contInYangData.getDataChildByName(
+                QName.create(foobar.getQNameModule(), "grp-cont"));
         assertNotNull(grpCont);
     }
 
@@ -151,8 +154,8 @@ public class YangDataExtensionTest extends AbstractYangDataTest {
         assertNotNull(schemaContext);
 
         final Module bar = schemaContext.findModule("bar", REVISION).get();
-        final ContainerSchemaNode cont = (ContainerSchemaNode) bar.findDataChildByName(
-                QName.create(bar.getQNameModule(), "cont")).get();
+        final ContainerSchemaNode cont = (ContainerSchemaNode) bar.getDataChildByName(
+                QName.create(bar.getQNameModule(), "cont"));
         assertNotNull(cont);
 
         final Collection<? extends ExtensionDefinition> extensions = schemaContext.getExtensions();
@@ -168,7 +171,7 @@ public class YangDataExtensionTest extends AbstractYangDataTest {
         final ReactorException ex = assertThrows(ReactorException.class, () -> build.buildEffective());
         final Throwable cause = ex.getCause();
         assertThat(cause, instanceOf(MissingSubstatementException.class));
-        assertThat(cause.getMessage(), startsWith("yang-data requires exactly one container"));
+        assertThat(cause.getMessage(), startsWith("yang-data requires at least one substatement [at "));
     }
 
     @Test
@@ -177,6 +180,7 @@ public class YangDataExtensionTest extends AbstractYangDataTest {
         final ReactorException ex = assertThrows(ReactorException.class, () -> build.buildEffective());
         final Throwable cause = ex.getCause();
         assertThat(cause, instanceOf(InvalidSubstatementException.class));
-        assertThat(cause.getMessage(), startsWith("yang-data requires exactly one data definition node, found 2"));
+        assertThat(cause.getMessage(),
+            startsWith("yang-data requires exactly one container data node definition, found ["));
     }
 }
diff --git a/parser/rfc8040-parser-support/src/test/resources/yt1443.yang b/parser/rfc8040-parser-support/src/test/resources/yt1443.yang
new file mode 100644 (file)
index 0000000..2892502
--- /dev/null
@@ -0,0 +1,11 @@
+module yt1443 {
+  yang-version 1.1;
+  namespace "yt1443";
+  prefix "yt1443";
+
+  import ietf-restconf { prefix rc; }
+
+  rc:yang-data support-save-data {
+    anydata support-save-data;
+  }
+}
index cced7ae9dc3c10f61c57f27eb122041486bf6785..d4c68034197ac7c367f6e0c32db43f5dcc27840c 100644 (file)
@@ -28,7 +28,6 @@ import org.opendaylight.yangtools.rfc6643.parser.MaxAccessStatementSupport;
 import org.opendaylight.yangtools.rfc6643.parser.OidStatementSupport;
 import org.opendaylight.yangtools.rfc6643.parser.SubIdStatementSupport;
 import org.opendaylight.yangtools.rfc7952.parser.AnnotationStatementSupport;
-import org.opendaylight.yangtools.rfc8040.parser.YangDataArgumentNamespace;
 import org.opendaylight.yangtools.rfc8040.parser.YangDataStatementSupport;
 import org.opendaylight.yangtools.rfc8528.parser.MountPointStatementSupport;
 import org.opendaylight.yangtools.rfc8639.parser.SubscriptionStateNotificationStatementSupport;
@@ -150,7 +149,6 @@ public final class DefaultReactors {
             .addStatementSupport(ModelProcessingPhase.FULL_DECLARATION, new AnnotationStatementSupport(config))
 
             // RFC8040 yang-data support
-            .addNamespaceSupport(ModelProcessingPhase.FULL_DECLARATION, YangDataArgumentNamespace.BEHAVIOUR)
             .addStatementSupport(ModelProcessingPhase.FULL_DECLARATION, new YangDataStatementSupport(config))
 
             // RFC8528 mount-point support