Enforce range constraints in DecimalStringCodec 56/102056/8
authorOleksandrZharov <Oleksandr.Zharov@pantheon.tech>
Thu, 11 Aug 2022 10:16:49 +0000 (12:16 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Mon, 3 Oct 2022 16:14:07 +0000 (18:14 +0200)
Added check which will report YangInvalidValueException if the parsed
value doesn't match range constraints specified in DecimalTypeDefinition.
Added explicit test for this change.

JIRA: YANGTOOLS-1442
Change-Id: I7a00fb540ae64e8f5bdde58aa7297e0e5f91d780
Signed-off-by: OleksandrZharov <Oleksandr.Zharov@pantheon.tech>
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
data/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/codec/DecimalStringCodec.java
data/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/codec/YT1442Test.java [new file with mode: 0644]
data/yang-data-impl/src/test/resources/yt1442.yang [new file with mode: 0644]

index a684e478f146887d204731179e007ceb025c58d4..2c764e63c48becae84b41268e60736b96295928d 100644 (file)
@@ -12,7 +12,9 @@ import static java.util.Objects.requireNonNull;
 import com.google.common.annotations.Beta;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.yang.common.Decimal64;
+import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.data.api.codec.DecimalCodec;
+import org.opendaylight.yangtools.yang.data.api.codec.YangInvalidValueException;
 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
 
 /**
@@ -40,7 +42,13 @@ public final class DecimalStringCodec extends TypeDefinitionAwareCodec<Decimal64
             throw new IllegalArgumentException("Value '" + product + "' does not match required fraction-digits", e);
         }
 
-        // FIXME: check ranges
+        typeDef.getRangeConstraint().ifPresent(constraint -> {
+            final var ranges = constraint.getAllowedRanges();
+            if (!ranges.contains(value)) {
+                throw new YangInvalidValueException(ErrorType.APPLICATION, constraint,
+                        "Value '" + value + "' is not in required ranges " + ranges);
+            }
+        });
         return value;
     }
 
diff --git a/data/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/codec/YT1442Test.java b/data/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/codec/YT1442Test.java
new file mode 100644 (file)
index 0000000..3836bbb
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * 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.impl.codec;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import java.util.List;
+import org.eclipse.jdt.annotation.NonNull;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.common.Decimal64;
+import org.opendaylight.yangtools.yang.common.ErrorSeverity;
+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.data.api.codec.YangInvalidValueException;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
+
+public class YT1442Test {
+    private static DecimalStringCodec codec;
+
+    @BeforeClass
+    public static void beforeClass() {
+        final var foo = YangParserTestUtils.parseYangResource("/yt1442.yang")
+            .getDataChildByName(QName.create("yt1442", "foo"));
+        assertThat(foo, instanceOf(LeafSchemaNode.class));
+
+        final TypeDefinitionAwareCodec<?, ?> tmp = TypeDefinitionAwareCodec.from(((LeafSchemaNode) foo).getType());
+        assertThat(tmp, instanceOf(DecimalStringCodec.class));
+        codec = (DecimalStringCodec) tmp;
+    }
+
+    @Test
+    public void testTen() {
+        final var ten = codec.deserialize("10.00");
+        assertDecimal(1000, ten);
+        assertEquals("10.0", codec.serialize(ten));
+    }
+
+    @Test
+    public void testFifty() {
+        final var fifty = codec.deserialize("50.00");
+        assertDecimal(5000, fifty);
+        assertEquals("50.0", codec.serialize(fifty));
+    }
+
+    @Test
+    public void testHundred() {
+        final var hundred = codec.deserialize("100.00");
+        assertDecimal(10000, hundred);
+        assertEquals("100.0", codec.serialize(hundred));
+    }
+
+    @Test
+    public void testNegativeOutOfRange() {
+        assertYIVE("Value '-10.0' is not in required ranges [[10.0..100.0]]", "-10.00");
+        assertYIVE("Value '-50.0' is not in required ranges [[10.0..100.0]]", "-50.00");
+        assertYIVE("Value '-100.0' is not in required ranges [[10.0..100.0]]", "-100.00");
+    }
+
+    @Test
+    public void testPositiveOutOfRange() {
+        assertYIVE("Value '9.99' is not in required ranges [[10.0..100.0]]", "9.99");
+        assertYIVE("Value '100.01' is not in required ranges [[10.0..100.0]]", "100.01");
+    }
+
+    @Test
+    public void testTooLargeFractionPart() {
+        final var ex = assertThrows(IllegalArgumentException.class, () -> codec.deserialize("100.001"));
+        assertEquals("Value '100.001' does not match required fraction-digits", ex.getMessage());
+        final var cause = ex.getCause();
+        assertThat(cause, instanceOf(ArithmeticException.class));
+        assertEquals("Decreasing scale of 100.001 to 2 requires rounding", cause.getMessage());
+    }
+
+    @Test
+    public void testTooLargeIntegralPart() {
+        final var ex = assertThrows(IllegalArgumentException.class, () -> codec.deserialize("92233720368547759.0"));
+        assertEquals("Value '92233720368547759.0' does not match required fraction-digits", ex.getMessage());
+        final var cause = ex.getCause();
+        assertThat(cause, instanceOf(ArithmeticException.class));
+        assertEquals("Increasing scale of 92233720368547759.0 to 2 would overflow", cause.getMessage());
+    }
+
+    private static void assertDecimal(final long unscaledValue, final Object obj) {
+        assertThat(obj, instanceOf(Decimal64.class));
+        final var actual = (Decimal64) obj;
+        assertEquals(2, actual.scale());
+        assertEquals(unscaledValue, actual.unscaledValue());
+    }
+
+    private static void assertYIVE(final String expectedMessage, final @NonNull String input) {
+        final var ex = assertThrows(YangInvalidValueException.class, () -> codec.deserialize(input));
+        assertEquals(expectedMessage, ex.getMessage());
+
+        final var errors = ex.getNetconfErrors();
+        assertEquals(1, errors.size());
+        final var error = errors.get(0);
+        assertEquals(ErrorSeverity.ERROR, error.severity());
+        assertEquals(ErrorType.APPLICATION, error.type());
+        assertEquals(ErrorTag.INVALID_VALUE, error.tag());
+        assertEquals("model-defined-app-tag", error.appTag());
+        assertEquals("model-defined-message", error.message());
+        assertEquals(List.of(), error.info());
+    }
+}
diff --git a/data/yang-data-impl/src/test/resources/yt1442.yang b/data/yang-data-impl/src/test/resources/yt1442.yang
new file mode 100644 (file)
index 0000000..1a3cd61
--- /dev/null
@@ -0,0 +1,14 @@
+module yt1442 {
+  namespace yt1442;
+  prefix yt1442;
+
+  leaf foo {
+    type decimal64 {
+      fraction-digits 2;
+      range 10.0..100.0 {
+        error-app-tag model-defined-app-tag;
+        error-message model-defined-message;
+      }
+    }
+  }
+}