2 * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.yangtools.yang.data.codec.gson;
10 import static org.junit.jupiter.api.Assertions.assertEquals;
11 import static org.junit.jupiter.api.Assertions.assertThrows;
13 import java.io.ByteArrayInputStream;
14 import java.io.InputStream;
15 import java.nio.charset.StandardCharsets;
16 import java.util.List;
18 import org.eclipse.jdt.annotation.NonNull;
19 import org.junit.jupiter.api.Test;
20 import org.junit.jupiter.api.function.Executable;
21 import org.opendaylight.yangtools.yang.common.ErrorSeverity;
22 import org.opendaylight.yangtools.yang.common.ErrorTag;
23 import org.opendaylight.yangtools.yang.common.ErrorType;
24 import org.opendaylight.yangtools.yang.common.QName;
25 import org.opendaylight.yangtools.yang.common.Uint32;
26 import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
29 import org.opendaylight.yangtools.yang.data.api.YangNetconfError;
30 import org.opendaylight.yangtools.yang.data.api.schema.stream.InputStreamNormalizer;
31 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizationException;
32 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
33 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
34 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
35 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
36 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
37 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
39 class InputStreamNormalizerTest {
40 private static final EffectiveModelContext MODEL_CONTEXT = YangParserTestUtils.parseYang("""
94 private static final InputStreamNormalizer PARSER = JSONCodecFactorySupplier.RFC7951.getShared(MODEL_CONTEXT);
95 private static final QName FOO = QName.create("foo", "foo");
96 private static final QName BAR = QName.create("foo", "bar");
97 private static final QName BAZ = QName.create("foo", "baz");
98 private static final QName QUX = QName.create("foo", "qux");
99 private static final QName THUD = QName.create("foo", "thud");
100 private static final QName ONE = QName.create("foo", "one");
101 private static final QName TWO = QName.create("foo", "two");
102 private static final QName STR = QName.create("foo", "str");
103 private static final QName UINT = QName.create("foo", "uint");
105 private static final @NonNull NodeIdentifier DATA_NID = new NodeIdentifier(
106 QName.create("urn:ietf:params:xml:ns:yang:ietf-restconf", "2017-01-26", "data"));
107 private static final @NonNull Unqualified RESTCONF_MODULE = Unqualified.of("ietf-restconf");
110 void parseDatastore() throws Exception {
111 assertEquals(Builders.containerBuilder()
112 .withNodeIdentifier(DATA_NID)
113 .withChild(Builders.containerBuilder()
114 .withNodeIdentifier(new NodeIdentifier(FOO))
115 .withChild(ImmutableNodes.leafNode(STR, "str"))
117 .withChild(Builders.containerBuilder()
118 .withNodeIdentifier(new NodeIdentifier(BAR))
119 .withChild(ImmutableNodes.leafNode(UINT, Uint32.TWO))
122 PARSER.parseDatastore(DATA_NID, RESTCONF_MODULE, stream("""
124 "ietf-restconf:data" : {
136 void parseData() throws Exception {
137 assertEquals(Builders.containerBuilder()
138 .withNodeIdentifier(new NodeIdentifier(FOO))
139 .withChild(ImmutableNodes.leafNode(STR, "str"))
141 PARSER.parseData(Inference.ofDataTreePath(MODEL_CONTEXT, FOO), stream("""
150 void parseDataBadType() throws Exception {
151 final var error = assertError(() -> PARSER.parseData(Inference.ofDataTreePath(MODEL_CONTEXT, FOO), stream("""
157 assertEquals(ErrorType.APPLICATION, error.type());
158 assertEquals(ErrorTag.INVALID_VALUE, error.tag());
162 void parseDataBadRootElement() throws Exception {
163 assertMismatchedError("(foo)foo", "(foo)bar",
164 () -> PARSER.parseData(Inference.ofDataTreePath(MODEL_CONTEXT, FOO), stream("""
173 void parseDataBadInference() throws Exception {
174 final var stack = SchemaInferenceStack.of(MODEL_CONTEXT);
175 stack.enterSchemaTree(THUD);
177 final var ex = assertThrows(IllegalArgumentException.class,
178 () -> PARSER.parseData(stack.toInference(), stream("")));
179 assertEquals("Invalid inference statement RpcEffectiveStatementImpl{argument=(foo)thud}", ex.getMessage());
183 void parseDataEmptyInference() throws Exception {
184 final var inference = Inference.of(MODEL_CONTEXT);
186 final var ex = assertThrows(IllegalArgumentException.class, () -> PARSER.parseData(inference, stream("")));
187 assertEquals("Inference must not be empty", ex.getMessage());
191 void parseChildData() throws Exception {
192 final var prefixAndNode = PARSER.parseChildData(Inference.of(MODEL_CONTEXT), stream("""
199 assertEquals(List.of(), prefixAndNode.prefix());
200 assertEquals(Builders.containerBuilder()
201 .withNodeIdentifier(new NodeIdentifier(FOO))
202 .withChild(ImmutableNodes.leafNode(STR, "str"))
203 .build(), prefixAndNode.result().data());
207 void parseChildDataChoices() throws Exception {
208 final var prefixAndNode = PARSER.parseChildData(Inference.of(MODEL_CONTEXT), stream("""
212 assertEquals(List.of(
213 new NodeIdentifier(QName.create("foo", "ch1")),
214 new NodeIdentifier(QName.create("foo", "ch2"))), prefixAndNode.prefix());
215 assertEquals(ImmutableNodes.leafNode(STR, "str"), prefixAndNode.result().data());
219 void parseChildDataListEntry() throws Exception {
220 final var prefixAndNode = PARSER.parseChildData(Inference.of(MODEL_CONTEXT), stream("""
229 assertEquals(List.of(new NodeIdentifier(BAZ)), prefixAndNode.prefix());
230 assertEquals(Builders.mapEntryBuilder()
231 .withNodeIdentifier(NodeIdentifierWithPredicates.of(BAZ, Map.of(ONE, Boolean.TRUE, TWO, "two")))
232 .withChild(ImmutableNodes.leafNode(ONE, Boolean.TRUE))
233 .withChild(ImmutableNodes.leafNode(TWO, "two"))
234 .build(), prefixAndNode.result().data());
238 void parseChildDataListEntryOnly() throws Exception {
239 // FIXME: this needs to be rejected, as it is an illegal format for a list resource, as per:
241 // https://www.rfc-editor.org/rfc/rfc8040#section-4.4.1:
243 // The message-body is expected to contain the
244 // content of a child resource to create within the parent (target
245 // resource). The message-body MUST contain exactly one instance of the
246 // expected data resource. The data model for the child tree is the
247 // subtree, as defined by YANG for the child resource.
249 // https://www.rfc-editor.org/rfc/rfc7951#section-5.4:
251 // the following is a valid JSON-encoded instance:
263 final var prefixAndNode = PARSER.parseChildData(Inference.of(MODEL_CONTEXT), stream("""
270 assertEquals(List.of(new NodeIdentifier(BAZ)), prefixAndNode.prefix());
271 assertEquals(Builders.mapEntryBuilder()
272 .withNodeIdentifier(NodeIdentifierWithPredicates.of(BAZ, Map.of(ONE, Boolean.TRUE, TWO, "two")))
273 .withChild(ImmutableNodes.leafNode(ONE, Boolean.TRUE))
274 .withChild(ImmutableNodes.leafNode(TWO, "two"))
275 .build(), prefixAndNode.result().data());
279 void parseChildDataListEntryNone() throws Exception {
280 final var error = assertError(() -> PARSER.parseChildData(Inference.of(MODEL_CONTEXT), stream("""
285 assertEquals(ErrorType.PROTOCOL, error.type());
286 assertEquals(ErrorTag.MALFORMED_MESSAGE, error.tag());
287 assertEquals("Exactly one instance of (foo)baz is required, 0 supplied", error.message());
291 void parseChildDataListEntryTwo() throws Exception {
292 final var error = assertError(() -> PARSER.parseChildData(Inference.of(MODEL_CONTEXT), stream("""
305 assertEquals(ErrorType.PROTOCOL, error.type());
306 assertEquals(ErrorTag.MALFORMED_MESSAGE, error.tag());
307 assertEquals("Exactly one instance of (foo)baz is required, 2 supplied", error.message());
311 void parseInputRpc() throws Exception {
312 final var stack = SchemaInferenceStack.of(MODEL_CONTEXT);
313 stack.enterSchemaTree(THUD);
315 assertEquals(Builders.containerBuilder()
316 .withNodeIdentifier(new NodeIdentifier(QName.create("foo", "input")))
317 .withChild(ImmutableNodes.leafNode(UINT, Uint32.TWO))
319 PARSER.parseInput(stack.toInference(), stream("""
328 void parseInputRpcBadRootElement() throws Exception {
329 final var stack = SchemaInferenceStack.of(MODEL_CONTEXT);
330 stack.enterSchemaTree(THUD);
332 assertMismatchedError("(foo)input", "(foo)output", () -> PARSER.parseInput(stack.toInference(), stream("""
340 void parseInputAction() throws Exception {
341 final var stack = SchemaInferenceStack.of(MODEL_CONTEXT);
342 stack.enterSchemaTree(BAZ);
343 stack.enterSchemaTree(QUX);
345 assertEquals(Builders.containerBuilder()
346 .withNodeIdentifier(new NodeIdentifier(QName.create("foo", "input")))
347 .withChild(ImmutableNodes.leafNode(STR, "str"))
349 PARSER.parseInput(stack.toInference(), stream("""
358 void parseInputBadInference() {
359 final var stack = SchemaInferenceStack.of(MODEL_CONTEXT);
360 stack.enterSchemaTree(BAZ);
362 final var ex = assertThrows(IllegalArgumentException.class,
363 () -> PARSER.parseInput(stack.toInference(), stream("")));
364 assertEquals("Invalid inference statement EmptyListEffectiveStatement{argument=(foo)baz}", ex.getMessage());
368 void parseOutputRpc() throws Exception {
369 final var stack = SchemaInferenceStack.of(MODEL_CONTEXT);
370 stack.enterSchemaTree(THUD);
372 assertEquals(Builders.containerBuilder()
373 .withNodeIdentifier(new NodeIdentifier(QName.create("foo", "output")))
375 PARSER.parseOutput(stack.toInference(), stream("""
383 void parseOutputRpcBadRootElement() throws Exception {
384 final var stack = SchemaInferenceStack.of(MODEL_CONTEXT);
385 stack.enterSchemaTree(THUD);
387 assertMismatchedError("(foo)output", "(foo)input", () -> PARSER.parseOutput(stack.toInference(), stream("""
395 void parseOutputAction() throws Exception {
396 final var stack = SchemaInferenceStack.of(MODEL_CONTEXT);
397 stack.enterSchemaTree(BAZ);
398 stack.enterSchemaTree(QUX);
400 assertEquals(Builders.containerBuilder()
401 .withNodeIdentifier(new NodeIdentifier(QName.create("foo", "output")))
403 PARSER.parseOutput(stack.toInference(), stream("""
411 void parseOutputBadInference() {
412 final var stack = SchemaInferenceStack.of(MODEL_CONTEXT);
413 stack.enterSchemaTree(BAZ);
415 final var ex = assertThrows(IllegalArgumentException.class,
416 () -> PARSER.parseOutput(stack.toInference(), stream("")));
417 assertEquals("Invalid inference statement EmptyListEffectiveStatement{argument=(foo)baz}", ex.getMessage());
420 private static @NonNull InputStream stream(final String str) {
421 return new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
424 private static void assertMismatchedError(final String expected, final String actual, final Executable executable) {
425 final var error = assertError(executable);
426 assertEquals(ErrorType.PROTOCOL, error.type());
427 assertEquals(ErrorTag.MALFORMED_MESSAGE, error.tag());
428 assertEquals("Payload name " + actual + " is different from identifier name " + expected, error.message());
431 private static YangNetconfError assertError(final Executable executable) {
432 final var ex = assertThrows(NormalizationException.class, executable);
433 final var errors = ex.getNetconfErrors();
434 assertEquals(1, errors.size());
435 final var error = errors.get(0);
436 assertEquals(ErrorSeverity.ERROR, error.severity());