2 * Copyright (c) 2021 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.restconf.nb.rfc8040;
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.assertThrows;
13 import java.text.ParseException;
14 import java.util.List;
15 import org.junit.Test;
16 import org.opendaylight.restconf.nb.rfc8040.ApiPath.ApiIdentifier;
17 import org.opendaylight.restconf.nb.rfc8040.FieldsParam.NodeSelector;
19 public class FieldsParameterTest {
20 // https://datatracker.ietf.org/doc/html/rfc8040#section-4.8.3:
21 // ";" is used to select multiple nodes. For example, to retrieve only
22 // the "genre" and "year" of an album, use "fields=genre;year".
24 public void testGenreYear() {
25 final var selectors = assertValidFields("genre;year");
26 assertEquals(2, selectors.size());
28 var selector = selectors.get(0);
29 assertEquals(List.of(new ApiIdentifier(null, "genre")), selector.path());
30 assertEquals(List.of(), selector.subSelectors());
32 selector = selectors.get(1);
33 assertEquals(List.of(new ApiIdentifier(null, "year")), selector.path());
34 assertEquals(List.of(), selector.subSelectors());
37 // https://datatracker.ietf.org/doc/html/rfc8040#section-4.8.3:
38 // "/" is used in a path to retrieve a child node of a node. For
39 // example, to retrieve only the "label" of an album, use
40 // "fields=admin/label".
42 public void testAdminLabel() throws ParseException {
43 final var selectors = assertValidFields("admin/label");
44 assertEquals(1, selectors.size());
46 final var selector = selectors.get(0);
47 assertEquals(List.of(new ApiIdentifier(null, "admin"), new ApiIdentifier(null, "label")), selector.path());
48 assertEquals(List.of(), selector.subSelectors());
51 // https://datatracker.ietf.org/doc/html/rfc8040#section-4.8.3:
52 // For example, assume that the target resource is the "album" list. To
53 // retrieve only the "label" and "catalogue-number" of the "admin"
54 // container within an album, use
55 // "fields=admin(label;catalogue-number)".
57 public void testAdminLabelCatalogueNumber() throws ParseException {
58 final var selectors = assertValidFields("admin(label;catalogue-number)");
59 assertEquals(1, selectors.size());
61 final var selector = selectors.get(0);
62 assertEquals(List.of(new ApiIdentifier(null, "admin")), selector.path());
64 final var subSelectors = selector.subSelectors();
65 assertEquals(2, subSelectors.size());
67 var subSelector = subSelectors.get(0);
68 assertEquals(List.of(new ApiIdentifier(null, "label")), subSelector.path());
69 assertEquals(List.of(), subSelector.subSelectors());
72 subSelector = subSelectors.get(1);
73 assertEquals(List.of(new ApiIdentifier(null, "catalogue-number")), subSelector.path());
74 assertEquals(List.of(), subSelector.subSelectors());
77 // https://datatracker.ietf.org/doc/html/rfc8040#appendix-B.3.3:
78 // In this example, the client is retrieving the datastore resource in
79 // JSON format, but retrieving only the "modules-state/module" list, and
80 // only the "name" and "revision" nodes from each list entry. Note that
81 // the top node returned by the server matches the target resource node
82 // (which is "data" in this example). The "module-set-id" leaf is not
83 // returned because it is not selected in the fields expression.
85 // GET /restconf/data?fields=ietf-yang-library:modules-state/\
86 // module(name;revision) HTTP/1.1
88 public void testModulesModuleNameRevision() {
89 final var selectors = assertValidFields("ietf-yang-library:modules-state/module(name;revision)");
90 assertEquals(1, selectors.size());
92 final var selector = selectors.get(0);
94 List.of(new ApiIdentifier("ietf-yang-library", "modules-state"), new ApiIdentifier(null, "module")),
97 final var subSelectors = selector.subSelectors();
98 assertEquals(2, subSelectors.size());
100 var subSelector = subSelectors.get(0);
101 assertEquals(List.of(new ApiIdentifier(null, "name")), subSelector.path());
102 assertEquals(List.of(), subSelector.subSelectors());
104 subSelector = subSelectors.get(1);
105 assertEquals(List.of(new ApiIdentifier(null, "revision")), subSelector.path());
106 assertEquals(List.of(), subSelector.subSelectors());
110 public void testModulesSimple() {
111 final var selectors = assertValidFields("ietf-yang-library:modules-state");
112 assertEquals(1, selectors.size());
114 final var selector = selectors.get(0);
115 assertEquals(List.of(new ApiIdentifier("ietf-yang-library", "modules-state")), selector.path());
116 assertEquals(List.of(), selector.subSelectors());
120 public void testUnqualifiedSubQualified() {
121 final var selectors = assertValidFields("a(b:c)");
122 assertEquals(1, selectors.size());
124 final var selector = selectors.get(0);
125 assertEquals(List.of(new ApiIdentifier(null, "a")), selector.path());
127 final var subSelectors = selector.subSelectors();
128 assertEquals(1, subSelectors.size());
130 final var subSelector = subSelectors.get(0);
131 assertEquals(List.of(new ApiIdentifier("b", "c")), subSelector.path());
132 assertEquals(List.of(), subSelector.subSelectors());
136 public void testQualifiedSubUnqualified() {
137 final var selectors = assertValidFields("a:b(c)");
138 assertEquals(1, selectors.size());
140 final var selector = selectors.get(0);
141 assertEquals(List.of(new ApiIdentifier("a", "b")), selector.path());
143 final var subSelectors = selector.subSelectors();
144 assertEquals(1, subSelectors.size());
146 final var subSelector = subSelectors.get(0);
147 assertEquals(List.of(new ApiIdentifier(null, "c")), subSelector.path());
148 assertEquals(List.of(), subSelector.subSelectors());
152 public void testDeepNesting() {
153 final var selectors = assertValidFields("a(b(c(d)));e(f(g(h)));i(j(k(l)))");
154 assertEquals(3, selectors.size());
158 public void testInvalidIdentifier() {
159 assertInvalidFields(".", "Expecting [a-ZA-Z_], not '.'", 0);
160 assertInvalidFields("a+", "Expecting [a-zA-Z_.-/(:;], not '+'", 1);
161 assertInvalidFields("a:.", "Expecting [a-ZA-Z_], not '.'", 2);
162 assertInvalidFields("a:b+", "Expecting [a-zA-Z_.-/(:;], not '+'", 3);
163 assertInvalidFields("a;)", "Expecting [a-ZA-Z_], not ')'", 2);
167 public void testUnexpectedEnds() {
168 assertInvalidFields("a;", "Unexpected end of input", 2);
169 assertInvalidFields("a(", "Unexpected end of input", 2);
170 assertInvalidFields("a(a", "Unexpected end of input", 3);
174 public void testUnexpectedRightParent() {
175 assertInvalidFields("a)", "Expecting ';', not ')'", 1);
178 private static void assertInvalidFields(final String str, final String message, final int errorOffset) {
179 final var ex = assertThrows(ParseException.class, () -> FieldsParam.parse(str));
180 assertEquals(message, ex.getMessage());
181 assertEquals(errorOffset, ex.getErrorOffset());
184 private static List<NodeSelector> assertValidFields(final String str) {
186 return FieldsParam.parse(str).nodeSelectors();
187 } catch (ParseException e) {
188 throw new AssertionError(e);