Improve ListEntryNodeDataWithSchema error reporting 16/109116/3
authorRobert Varga <robert.varga@pantheon.tech>
Tue, 28 Nov 2023 23:49:13 +0000 (00:49 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Wed, 29 Nov 2023 08:29:36 +0000 (09:29 +0100)
When we encounter a keyed list entry which does not have all keys
specified we should report which keys are missing through an
IOException.

JIRA: YANGTOOLS-1550
Change-Id: I40a7c04970b244c893f4541469efe7e562b49e71
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
codec/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/YT1550Test.java [new file with mode: 0644]
data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/ListEntryNodeDataWithSchema.java

diff --git a/codec/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/YT1550Test.java b/codec/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/YT1550Test.java
new file mode 100644 (file)
index 0000000..35eb87e
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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.yang.data.codec.gson;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.google.gson.JsonIOException;
+import com.google.gson.stream.JsonReader;
+import java.io.IOException;
+import java.io.StringReader;
+import org.junit.jupiter.api.Test;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizationResultHolder;
+import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
+
+class YT1550Test {
+    @Test
+    void testMissingKey() throws Exception {
+        final var modelContext = YangParserTestUtils.parseYang("""
+            module foo {
+              namespace foons;
+              prefix foo;
+
+              list foo {
+                key "one two";
+                leaf one {
+                  type string {
+                    base one;
+                  }
+                }
+                leaf two {
+                  type string {
+                    base one;
+                  }
+                }
+              }
+            }""");
+
+        final var holder = new NormalizationResultHolder();
+        final var factory = JSONCodecFactorySupplier.RFC7951.getShared(modelContext);
+        try (var writer = ImmutableNormalizedNodeStreamWriter.from(holder)) {
+            try (var jsonParser = JsonParserStream.create(writer, factory)) {
+                try (var reader = new JsonReader(new StringReader("""
+                    {
+                      "foo" : {
+                        "one" : "one"
+                      }
+                    }"""))) {
+
+                    final var ex = assertThrows(JsonIOException.class, () -> jsonParser.parse(reader));
+                    assertEquals("java.io.IOException: List entry (foons)foo is missing leaf values for [two]",
+                        ex.getMessage());
+                    final var cause = assertInstanceOf(IOException.class, ex.getCause());
+                    assertEquals("List entry (foons)foo is missing leaf values for [two]", cause.getMessage());
+                }
+            }
+        }
+    }
+}
index e765b5c43e16a88695f2309e31ae594a33a9054d..6f1e2f3227ac8b076416b136e023c9e39985d07e 100644 (file)
@@ -7,8 +7,7 @@
  */
 package org.opendaylight.yangtools.yang.data.util;
 
-import static com.google.common.base.Verify.verify;
-
+import com.google.common.base.VerifyException;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.List;
@@ -19,7 +18,6 @@ import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.MetadataExtension;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
 
@@ -43,12 +41,14 @@ public abstract sealed class ListEntryNodeDataWithSchema extends AbstractMountPo
 
         @Override
         void addChild(final AbstractNodeDataWithSchema<?> newChild) {
-            final DataSchemaNode childSchema = newChild.getSchema();
-            if (childSchema instanceof LeafSchemaNode) {
-                final QName childName = childSchema.getQName();
+            if (newChild.getSchema() instanceof LeafSchemaNode leaf) {
+                final var childName = leaf.getQName();
                 if (predicateTemplate.keySet().contains(childName)) {
-                    verify(newChild instanceof SimpleNodeDataWithSchema);
-                    keyValues.put(childName, (SimpleNodeDataWithSchema<?>)newChild);
+                    if (newChild instanceof SimpleNodeDataWithSchema<?> simpleChild) {
+                        keyValues.put(childName, simpleChild);
+                    } else {
+                        throw new VerifyException("Unexpected child " + newChild);
+                    }
                 }
             }
             super.addChild(newChild);
@@ -57,11 +57,25 @@ public abstract sealed class ListEntryNodeDataWithSchema extends AbstractMountPo
         @Override
         public void write(final NormalizedNodeStreamWriter writer, final MetadataExtension metaWriter)
                 throws IOException {
-            writer.nextDataSchemaNode(getSchema());
-            final NodeIdentifierWithPredicates identifier = NodeIdentifierWithPredicates.of(getSchema().getQName(),
-                predicateTemplate.instantiateTransformed(keyValues, (key, node) -> node.getValue()));
+            final var schema = getSchema();
+            writer.nextDataSchemaNode(schema);
+
+            final var nodeType = schema.getQName();
+            final Map<QName, Object> predicates;
+            try {
+                predicates = predicateTemplate.instantiateTransformed(keyValues, (key, node) -> node.getValue());
+            } catch (IllegalArgumentException e) {
+                final var present = keyValues.keySet();
+                final var module = nodeType.getModule();
+                final var missing = predicateTemplate.keySet().stream()
+                    .filter(key -> !present.contains(key))
+                    .map(key -> module.equals(key.getModule()) ? key.getLocalName() : key)
+                    .distinct()
+                    .toList();
+                throw new IOException("List entry " + nodeType + " is missing leaf values for " + missing, e);
+            }
 
-            writer.startMapEntryNode(identifier, childSizeHint());
+            writer.startMapEntryNode(NodeIdentifierWithPredicates.of(nodeType, predicates), childSizeHint());
             writeMetadata(metaWriter);
             super.write(writer, metaWriter);
             writer.endNode();
@@ -88,7 +102,7 @@ public abstract sealed class ListEntryNodeDataWithSchema extends AbstractMountPo
     }
 
     static @NonNull ListEntryNodeDataWithSchema forSchema(final ListSchemaNode schema) {
-        final List<QName> keyDef = schema.getKeyDefinition();
-        return keyDef.isEmpty() ? new Unkeyed(schema) :  new Keyed(schema, keyDef);
+        final var keyDef = schema.getKeyDefinition();
+        return keyDef.isEmpty() ? new Unkeyed(schema) : new Keyed(schema, keyDef);
     }
 }