Add support for parsing JSON NormalizedYangData 76/106076/4
authorRobert Varga <robert.varga@pantheon.tech>
Sat, 20 May 2023 14:43:07 +0000 (16:43 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Sun, 21 May 2023 18:33:47 +0000 (20:33 +0200)
This adds the basic tools and a simple test for parsing yang-data when
the inference stack points to it.

JIRA: YANGTOOLS-1472
Change-Id: If27ec5c36f3686324f5ec66c9ea5a920a3c0a509
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
codec/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JsonParserStream.java
codec/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/YT1472Test.java [new file with mode: 0644]
codec/yang-data-codec-gson/src/test/resources/yt1472/ietf-restconf.yang [new file with mode: 0644]
model/rfc8040-model-api/src/main/java/org/opendaylight/yangtools/rfc8040/model/api/YangDataAsContainer.java [new file with mode: 0644]
model/rfc8040-model-api/src/main/java/org/opendaylight/yangtools/rfc8040/model/api/YangDataSchemaNode.java
model/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/ContainerLikeCompat.java

index ae48012c19df2f0f40764e16ecff9163080060d5..7c09bdaf977efbf241eba86b7d5442ee4d6572fb 100644 (file)
@@ -30,6 +30,7 @@ import java.util.Map.Entry;
 import java.util.Set;
 import javax.xml.transform.dom.DOMSource;
 import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.rfc8040.model.api.YangDataSchemaNode;
 import org.opendaylight.yangtools.util.xml.UntrustedXML;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.XMLNamespace;
@@ -95,6 +96,8 @@ public final class JsonParserStream implements Closeable, Flushable {
                 parentNode = oper.toContainerLike();
             } else if (parent instanceof NotificationDefinition notif) {
                 parentNode = notif.toContainerLike();
+            } else if (parent instanceof YangDataSchemaNode yangData) {
+                parentNode = yangData.toContainerLike();
             } else {
                 throw new IllegalArgumentException("Illegal parent node " + parent);
             }
diff --git a/codec/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/YT1472Test.java b/codec/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/YT1472Test.java
new file mode 100644 (file)
index 0000000..5bb9260
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2023 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.yang.data.codec.gson;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.gson.stream.JsonReader;
+import java.io.StringReader;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.common.QName;
+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.common.YangDataName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizationResultHolder;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
+import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
+
+public class YT1472Test {
+    private static final QNameModule RESTCONF_MODULE =
+        QNameModule.create(XMLNamespace.of("urn:ietf:params:xml:ns:yang:ietf-restconf"), Revision.of("2017-01-26"));
+    private static final YangDataName ERRORS_NAME = new YangDataName(RESTCONF_MODULE, "yang-errors");
+    private static final NodeIdentifier ERROR_NID = NodeIdentifier.create(QName.create(RESTCONF_MODULE, "error"));
+
+    private static EffectiveModelContext CONTEXT;
+    private static JSONCodecFactory CODEC_FACTORY;
+    private static Inference ERRORS_INFERENCE;
+
+    @BeforeClass
+    public static void beforeClass() {
+        CONTEXT = YangParserTestUtils.parseYangResourceDirectory("/yt1472");
+        CODEC_FACTORY = JSONCodecFactorySupplier.RFC7951.getShared(CONTEXT);
+
+        final var stack = SchemaInferenceStack.of(CONTEXT);
+        stack.enterYangData(ERRORS_NAME);
+        ERRORS_INFERENCE = stack.toInference();
+    }
+
+    @Test
+    public void testErrorsParsing() {
+        final var result = new NormalizationResultHolder();
+        final var streamWriter = ImmutableNormalizedNodeStreamWriter.from(result);
+        final var jsonParser = JsonParserStream.create(streamWriter, CODEC_FACTORY, ERRORS_INFERENCE);
+        // https://www.rfc-editor.org/rfc/rfc8040#page-77
+        jsonParser.parse(new JsonReader(new StringReader("""
+            {
+              "ietf-restconf:errors" : {
+                "error" : [
+                  {
+                    "error-type" : "protocol",
+                    "error-tag" : "lock-denied",
+                    "error-message" : "Lock failed; lock already held"
+                  }
+                ]
+              }
+            }""")));
+        assertEquals(Builders.containerBuilder()
+            .withNodeIdentifier(NodeIdentifier.create(QName.create(RESTCONF_MODULE, "errors")))
+            .withChild(Builders.unkeyedListBuilder()
+                .withNodeIdentifier(ERROR_NID)
+                .withChild(Builders.unkeyedListEntryBuilder()
+                    .withNodeIdentifier(ERROR_NID)
+                    .withChild(ImmutableNodes.leafNode(QName.create(RESTCONF_MODULE, "error-type"), "protocol"))
+                    .withChild(ImmutableNodes.leafNode(QName.create(RESTCONF_MODULE, "error-tag"), "lock-denied"))
+                    .withChild(ImmutableNodes.leafNode(QName.create(RESTCONF_MODULE, "error-message"),
+                        "Lock failed; lock already held"))
+                    .build())
+                .build())
+            .build(), result.getResult().data());
+    }
+}
diff --git a/codec/yang-data-codec-gson/src/test/resources/yt1472/ietf-restconf.yang b/codec/yang-data-codec-gson/src/test/resources/yt1472/ietf-restconf.yang
new file mode 100644 (file)
index 0000000..3008664
--- /dev/null
@@ -0,0 +1,253 @@
+module ietf-restconf {
+  yang-version 1.1;
+  namespace "urn:ietf:params:xml:ns:yang:ietf-restconf";
+  prefix "rc";
+
+  organization
+    "IETF NETCONF (Network Configuration) Working Group";
+
+  contact
+    "WG Web:   <https://datatracker.ietf.org/wg/netconf/>
+     WG List:  <mailto:netconf@ietf.org>
+     Author:   Andy Bierman
+               <mailto:andy@yumaworks.com>
+     Author:   Martin Bjorklund
+               <mailto:mbj@tail-f.com>
+     Author:   Kent Watsen
+               <mailto:kwatsen@juniper.net>";
+
+  description
+    "This module contains conceptual YANG specifications
+     for basic RESTCONF media type definitions used in
+     RESTCONF protocol messages.
+     Note that the YANG definitions within this module do not
+     represent configuration data of any kind.
+     The 'restconf-media-type' YANG extension statement
+     provides a normative syntax for XML and JSON
+     message-encoding purposes.
+     Copyright (c) 2017 IETF Trust and the persons identified as
+     authors of the code.  All rights reserved.
+     Redistribution and use in source and binary forms, with or
+     without modification, is permitted pursuant to, and subject
+     to the license terms contained in, the Simplified BSD License
+     set forth in Section 4.c of the IETF Trust's Legal Provisions
+     Relating to IETF Documents
+     (http://trustee.ietf.org/license-info).
+     This version of this YANG module is part of RFC 8040; see
+     the RFC itself for full legal notices.";
+
+  revision 2017-01-26 {
+    description
+      "Initial revision.";
+    reference
+      "RFC 8040: RESTCONF Protocol.";
+  }
+
+  extension yang-data {
+    argument name {
+      yin-element true;
+    }
+    description
+      "This extension is used to specify a YANG data template that
+       represents conceptual data defined in YANG.  It is
+       intended to describe hierarchical data independent of
+       protocol context or specific message-encoding format.
+       Data definition statements within a yang-data extension
+       specify the generic syntax for the specific YANG data
+       template, whose name is the argument of the 'yang-data'
+       extension statement.
+       Note that this extension does not define a media type.
+       A specification using this extension MUST specify the
+       message-encoding rules, including the content media type.
+       The mandatory 'name' parameter value identifies the YANG
+       data template that is being defined.  It contains the
+       template name.
+       This extension is ignored unless it appears as a top-level
+       statement.  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.
+       The module name and namespace values for the YANG module using
+       the extension statement are assigned to instance document data
+       conforming to the data definition statements within
+       this extension.
+       The substatements of this extension MUST follow the
+       'data-def-stmt' rule in the YANG ABNF.
+       The XPath document root is the extension statement itself,
+       such that the child nodes of the document root are
+       represented by the data-def-stmt substatements within
+       this extension.  This conceptual document is the context
+       for the following YANG statements:
+         - must-stmt
+         - when-stmt
+         - path-stmt
+         - min-elements-stmt
+         - max-elements-stmt
+         - mandatory-stmt
+         - unique-stmt
+         - ordered-by
+         - instance-identifier data type
+       The following data-def-stmt substatements are constrained
+       when used within a 'yang-data' extension statement.
+         - The list-stmt is not required to have a key-stmt defined.
+         - The if-feature-stmt is ignored if present.
+         - The config-stmt is ignored if present.
+         - The available identity values for any 'identityref'
+           leaf or leaf-list nodes are limited to the module
+           containing this extension statement and the modules
+           imported into that module.
+      ";
+  }
+
+  rc:yang-data yang-errors {
+    uses errors;
+  }
+
+  rc:yang-data yang-api {
+    uses restconf;
+  }
+
+  grouping errors {
+    description
+      "A grouping that contains a YANG container
+       representing the syntax and semantics of a
+       YANG Patch error report within a response message.";
+
+    container errors {
+      description
+        "Represents an error report returned by the server if
+         a request results in an error.";
+
+      list error {
+        description
+          "An entry containing information about one
+           specific error that occurred while processing
+           a RESTCONF request.";
+        reference
+          "RFC 6241, Section 4.3.";
+
+        leaf error-type {
+          type enumeration {
+            enum transport {
+              description
+                "The transport layer.";
+            }
+            enum rpc {
+              description
+                "The rpc or notification layer.";
+            }
+            enum protocol {
+              description
+                "The protocol operation layer.";
+            }
+            enum application {
+              description
+                "The server application layer.";
+            }
+          }
+          mandatory true;
+          description
+            "The protocol layer where the error occurred.";
+        }
+
+        leaf error-tag {
+          type string;
+          mandatory true;
+          description
+            "The enumerated error-tag.";
+        }
+
+        leaf error-app-tag {
+          type string;
+          description
+            "The application-specific error-tag.";
+        }
+
+        leaf error-path {
+          type instance-identifier;
+          description
+            "The YANG instance identifier associated
+             with the error node.";
+        }
+
+        leaf error-message {
+          type string;
+          description
+            "A message describing the error.";
+        }
+
+        anydata error-info {
+           description
+             "This anydata value MUST represent a container with
+              zero or more data nodes representing additional
+              error information.";
+        }
+      }
+    }
+  }
+
+  grouping restconf {
+    description
+      "Conceptual grouping representing the RESTCONF
+       root resource.";
+
+    container restconf {
+      description
+        "Conceptual container representing the RESTCONF
+         root resource.";
+
+      container data {
+        description
+          "Container representing the datastore resource.
+           Represents the conceptual root of all state data
+           and configuration data supported by the server.
+           The child nodes of this container can be any data
+           resources that are defined as top-level data nodes
+           from the YANG modules advertised by the server in
+           the 'ietf-yang-library' module.";
+      }
+
+      container operations {
+        description
+          "Container for all operation resources.
+           Each resource is represented as an empty leaf with the
+           name of the RPC operation from the YANG 'rpc' statement.
+           For example, the 'system-restart' RPC operation defined
+           in the 'ietf-system' module would be represented as
+           an empty leaf in the 'ietf-system' namespace.  This is
+           a conceptual leaf and will not actually be found in
+           the module:
+              module ietf-system {
+                leaf system-reset {
+                  type empty;
+                }
+              }
+           To invoke the 'system-restart' RPC operation:
+              POST /restconf/operations/ietf-system:system-restart
+           To discover the RPC operations supported by the server:
+              GET /restconf/operations
+           In XML, the YANG module namespace identifies the module:
+             <system-restart
+                xmlns='urn:ietf:params:xml:ns:yang:ietf-system'/>
+           In JSON, the YANG module name identifies the module:
+             { 'ietf-system:system-restart' : [null] }
+          ";
+      }
+
+      leaf yang-library-version {
+        type string {
+          pattern '\d{4}-\d{2}-\d{2}';
+        }
+        config false;
+        mandatory true;
+        description
+          "Identifies the revision date of the 'ietf-yang-library'
+           module that is implemented by this RESTCONF server.
+           Indicates the year, month, and day in YYYY-MM-DD
+           numeric format.";
+      }
+    }
+  }
+
+}
\ No newline at end of file
diff --git a/model/rfc8040-model-api/src/main/java/org/opendaylight/yangtools/rfc8040/model/api/YangDataAsContainer.java b/model/rfc8040-model-api/src/main/java/org/opendaylight/yangtools/rfc8040/model/api/YangDataAsContainer.java
new file mode 100644 (file)
index 0000000..b5b493b
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2023 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.model.api;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Collection;
+import java.util.Set;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ContainerLikeCompat;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
+import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+
+final class YangDataAsContainer extends ContainerLikeCompat {
+    private final @NonNull YangDataSchemaNode delegate;
+
+    YangDataAsContainer(final YangDataSchemaNode delegate) {
+        this.delegate = requireNonNull(delegate);
+    }
+
+    @Override
+    protected YangDataSchemaNode delegate() {
+        return delegate;
+    }
+
+    @Override
+    public Collection<? extends TypeDefinition<?>> getTypeDefinitions() {
+        return delegate.getTypeDefinitions();
+    }
+
+    @Override
+    public Collection<? extends DataSchemaNode> getChildNodes() {
+        return delegate.getChildNodes();
+    }
+
+    @Override
+    public Collection<? extends GroupingDefinition> getGroupings() {
+        return delegate.getGroupings();
+    }
+
+    @Override
+    public DataSchemaNode dataChildByName(final QName name) {
+        return delegate.getDataChildByName(name);
+    }
+
+    @Override
+    public Set<AugmentationSchemaNode> getAvailableAugmentations() {
+        return Set.of();
+    }
+}
index e480f2ffdf4a782f8f5cd582354e76654500aafd..11fda02d77444b43fa497ae3ced9f72d01f2ec86 100644 (file)
@@ -8,6 +8,8 @@
 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.ContainerLikeCompat;
 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
 import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
 
@@ -20,4 +22,8 @@ import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
 public interface YangDataSchemaNode extends UnknownSchemaNode, DataNodeContainer {
     @Override
     YangDataEffectiveStatement asEffectiveStatement();
+
+    default @NonNull ContainerLikeCompat toContainerLike() {
+        return new YangDataAsContainer(this);
+    }
 }
index d3cc6407d30fb38537a2423014eab560c9d14561..7ff3e8eee5bb22780f335b2b4db56f4f3f2c9bda 100644 (file)
@@ -7,6 +7,7 @@
  */
 package org.opendaylight.yangtools.yang.model.api;
 
+import com.google.common.annotations.Beta;
 import com.google.common.collect.ForwardingObject;
 import java.util.Optional;
 import java.util.Set;
@@ -19,12 +20,8 @@ import org.opendaylight.yangtools.yang.xpath.api.YangXPathExpression.QualifiedBo
  * {@link NotificationDefinition#toContainerLike()}. This class is exposed only for the unlikely need users of those
  * methods need to do tricks when encountering those containers.
  */
-public abstract sealed class ContainerLikeCompat extends ForwardingObject implements ContainerLike
-        permits NotificationAsContainer, OperationAsContainer {
-    ContainerLikeCompat() {
-        // Hidden on purpose
-    }
-
+@Beta
+public abstract class ContainerLikeCompat extends ForwardingObject implements ContainerLike {
     @Override
     protected abstract @NonNull SchemaNode delegate();