Integrate ParserFieldsParameter with FieldsParameter 26/98126/4
authorRobert Varga <robert.varga@pantheon.tech>
Mon, 25 Oct 2021 17:20:05 +0000 (19:20 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Mon, 25 Oct 2021 18:57:19 +0000 (20:57 +0200)
We have the baseline string parser reimplemented, which means we can
just interpret the list of selection nodes. Take out all the string
parsing code and reimplement the core loop, making things a lot clearer.

JIRA: NETCONF-820
Change-Id: I915794323a75937996a6a3ba693a10ab2878d910
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/jaxrs/QueryParams.java
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/utils/parser/ParserFieldsParameter.java
restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/FieldsParameterTest.java
restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/utils/parser/ParserFieldsParameterTest.java

index db7bb59c3fdb82ac2ef0e4312fead2e317af7ae1..d1fe9ec092b9db8e068e77b9c4ad6fe547506d17 100644 (file)
@@ -112,8 +112,8 @@ public final class QueryParams {
         }
 
         return identifier.getMountPoint() != null
-            ? QueryParameters.ofFieldPaths(params, parseFieldsPaths(identifier, fields.paramValue()))
-                : QueryParameters.ofFields(params, parseFieldsParameter(identifier, fields.paramValue()));
+            ? QueryParameters.ofFieldPaths(params, parseFieldsPaths(identifier, fields))
+                : QueryParameters.ofFields(params, parseFieldsParameter(identifier, fields));
     }
 
     /**
index f4b7b77e9bb5d9d57065bd6d21181a65c45c904a..f2325315bb685b46991d6ebb92cfd9d234441f3e 100644 (file)
@@ -21,6 +21,8 @@ import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.nb.rfc8040.FieldsParam;
+import org.opendaylight.restconf.nb.rfc8040.FieldsParam.NodeSelector;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -29,9 +31,9 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
 /**
  * Utilities used for parsing of fields query parameter content.
@@ -53,7 +55,7 @@ public abstract class ParserFieldsParameter<T> {
      * @return {@link List} of levels; each level contains set of {@link QName}
      */
     public static @NonNull List<Set<QName>> parseFieldsParameter(final @NonNull InstanceIdentifierContext<?> identifier,
-                                                                 final @NonNull String input) {
+                                                                 final @NonNull FieldsParam input) {
         return QNAME_PARSER.parseFields(identifier, input);
     }
 
@@ -66,7 +68,7 @@ public abstract class ParserFieldsParameter<T> {
      *     of provided {@code identifier}
      */
     public static @NonNull List<YangInstanceIdentifier> parseFieldsPaths(
-            final @NonNull InstanceIdentifierContext<?> identifier, final @NonNull String input) {
+            final @NonNull InstanceIdentifierContext<?> identifier, final @NonNull FieldsParam input) {
         final List<Set<LinkedPathElement>> levels = PATH_PARSER.parseFields(identifier, input);
         final List<Map<PathArgument, LinkedPathElement>> mappedLevels = mapLevelsContentByIdentifiers(levels);
         return buildPaths(mappedLevels);
@@ -125,10 +127,7 @@ public abstract class ParserFieldsParameter<T> {
      * @return {@link List} of levels; each level contains {@link Set} of identifiers of type {@link T}
      */
     private @NonNull List<Set<T>> parseFields(final @NonNull InstanceIdentifierContext<?> identifier,
-                                              final @NonNull String input) {
-        final List<Set<T>> parsed = new ArrayList<>();
-        final SchemaContext context = identifier.getSchemaContext();
-        final QNameModule startQNameModule = identifier.getSchemaNode().getQName().getModule();
+                                              final @NonNull FieldsParam input) {
         final DataSchemaContextNode<?> startNode = DataSchemaContextNode.fromDataSchemaNode(
                 (DataSchemaNode) identifier.getSchemaNode());
 
@@ -137,117 +136,54 @@ public abstract class ParserFieldsParameter<T> {
                     "Start node missing in " + input, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
         }
 
-        parseInput(input, startQNameModule, startNode, parsed, context);
+        final List<Set<T>> parsed = new ArrayList<>();
+        processSelectors(parsed, identifier.getSchemaContext(), identifier.getSchemaNode().getQName().getModule(),
+            startNode, input.nodeSelectors());
         return parsed;
     }
 
-    /**
-     * Parse input value of fields parameter and create list of sets. Each set represents one level of child nodes.
-     *
-     * @param input input value of fields parameter
-     * @param startQNameModule starting qname module
-     * @param startNode starting node
-     * @param parsed list of results
-     * @param context schema context
-     */
-    private void parseInput(final @NonNull String input, final @NonNull QNameModule startQNameModule,
-                            final @NonNull DataSchemaContextNode<?> startNode,
-                            final @NonNull List<Set<T>> parsed, final SchemaContext context) {
+    private void processSelectors(final List<Set<T>> parsed, final EffectiveModelContext context,
+            final QNameModule startNamespace, final DataSchemaContextNode<?> startNode,
+            final List<NodeSelector> selectors) {
         final Set<T> startLevel = new HashSet<>();
         parsed.add(startLevel);
 
-        int currentPosition = 0;
-        int startPosition = 0;
-        DataSchemaContextNode<?> currentNode = startNode;
-        QNameModule currentQNameModule = startQNameModule;
-        Set<T> currentLevel = startLevel;
-
-        while (currentPosition < input.length()) {
-            final char currentChar = input.charAt(currentPosition);
-
-            if (ParserConstants.YANG_IDENTIFIER_PART.matches(currentChar)) {
-                currentPosition++;
-                continue;
-            }
-
-            switch (currentChar) {
-                case '/':
-                    // add parsed identifier to results for current level
-                    currentNode = addChildToResult(currentNode, input.substring(startPosition, currentPosition),
-                            currentQNameModule, currentLevel);
-                    // go one level down
-                    currentLevel = prepareQNameLevel(parsed, currentLevel);
+        for (var selector : selectors) {
+            var node = startNode;
+            var namespace = startNamespace;
+            var level = startLevel;
+
+
+            // Note: path is guaranteed to have at least one step
+            final var it = selector.path().iterator();
+            while (true) {
+                // FIXME: The layout of this loop is rather weird, which is due to how prepareQNameLevel() operates. We
+                //        need to call it only when we know there is another identifier coming, otherwise we would end
+                //        up with empty levels sneaking into the mix.
+                //
+                //        Dealing with that weirdness requires understanding what the expected end results are and a
+                //        larger rewrite of the algorithms involved.
+                final var step = it.next();
+                final var module = step.module();
+                if (module != null) {
+                    // FIXME: this is not defensive enough, as we can fail to find the module
+                    namespace = context.findModules(module).iterator().next().getQNameModule();
+                }
 
-                    currentPosition++;
-                    break;
-                case ':':
-                    // new namespace and revision found
-                    currentQNameModule = context.findModules(
-                            input.substring(startPosition, currentPosition)).iterator().next().getQNameModule();
-                    currentPosition++;
+                // add parsed identifier to results for current level
+                node = addChildToResult(node, step.identifier().bindTo(namespace), level);
+                if (!it.hasNext()) {
                     break;
-                case '(':
-                    // add current child to parsed results for current level
-                    final DataSchemaContextNode<?> child = addChildToResult(
-                            currentNode,
-                            input.substring(startPosition, currentPosition), currentQNameModule, currentLevel);
-                    // call with child node as new start node for one level down
-                    final int closingParenthesis = currentPosition
-                            + findClosingParenthesis(input.substring(currentPosition + 1));
-                    parseInput(
-                            input.substring(currentPosition + 1, closingParenthesis),
-                            currentQNameModule,
-                            child,
-                            parsed,
-                            context);
-
-                    // closing parenthesis must be at the end of input or separator and one more character is expected
-                    currentPosition = closingParenthesis + 1;
-                    if (currentPosition != input.length()) {
-                        if (currentPosition + 1 < input.length()) {
-                            if (input.charAt(currentPosition) == ';') {
-                                currentPosition++;
-                            } else {
-                                throw new RestconfDocumentedException(
-                                        "Missing semicolon character after "
-                                                + child.getIdentifier().getNodeType().getLocalName()
-                                                + " child nodes",
-                                        ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
-                            }
-                        } else {
-                            throw new RestconfDocumentedException(
-                                    "Unexpected character '"
-                                            + input.charAt(currentPosition)
-                                            + "' found in fields parameter value",
-                                    ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
-                        }
-                    }
+                }
 
-                    break;
-                case ';':
-                    // complete identifier found
-                    addChildToResult(
-                            currentNode,
-                            input.substring(startPosition, currentPosition), currentQNameModule, currentLevel);
-                    currentPosition++;
-
-                    // next nodes can be placed on already utilized level-s
-                    currentNode = startNode;
-                    currentQNameModule = startQNameModule;
-                    currentLevel = startLevel;
-                    break;
-                default:
-                    throw new RestconfDocumentedException(
-                            "Unexpected character '" + currentChar + "' found in fields parameter value",
-                            ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+                // go one level down
+                level = prepareQNameLevel(parsed, level);
             }
 
-            startPosition = currentPosition;
-        }
-
-        // parse input to end
-        if (startPosition < input.length()) {
-            addChildToResult(currentNode, input.substring(startPosition), currentQNameModule, currentLevel);
+            final var subs = selector.subSelectors();
+            if (!subs.isEmpty()) {
+                processSelectors(parsed, context, namespace, node, subs);
+            }
         }
     }
 
@@ -280,43 +216,6 @@ public abstract class ParserFieldsParameter<T> {
         return nextLevel;
     }
 
-    /**
-     * Find position of matching parenthesis increased by one, but at most equals to input size.
-     *
-     * @param input input where to find for closing parenthesis
-     * @return int position of closing parenthesis increased by one
-     */
-    private static int findClosingParenthesis(final @NonNull String input) {
-        int position = 0;
-        int count = 1;
-
-        while (position < input.length()) {
-            final char currentChar = input.charAt(position);
-
-            if (currentChar == '(') {
-                count++;
-            }
-
-            if (currentChar == ')') {
-                count--;
-            }
-
-            if (count == 0) {
-                break;
-            }
-
-            position++;
-        }
-
-        // closing parenthesis was not found
-        if (position >= input.length()) {
-            throw new RestconfDocumentedException("Missing closing parenthesis in fields parameter",
-                    ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
-        }
-
-        return ++position;
-    }
-
     /**
      * Add parsed child of current node to result for current level.
      *
@@ -328,11 +227,6 @@ public abstract class ParserFieldsParameter<T> {
     abstract @NonNull DataSchemaContextNode<?> addChildToResult(@NonNull DataSchemaContextNode<?> currentNode,
             @NonNull QName childQName, @NonNull Set<T> level);
 
-    private @NonNull DataSchemaContextNode<?> addChildToResult(final @NonNull DataSchemaContextNode<?> currentNode,
-            final @NonNull String localName, final @NonNull QNameModule namespace, final @NonNull Set<T> level) {
-        return addChildToResult(currentNode, QName.create(namespace, localName), level);
-    }
-
     /**
      * Fields parser that stores set of {@link QName}s in each level. Because of this fact, from the output
      * it is is only possible to assume on what depth the selected element is placed. Identifiers of intermediary
index 857992176194a713c2a8078560f205d2b29f3016..8ab7d3dbe3d6d177962ea2549e5235dd99215c3e 100644 (file)
@@ -161,6 +161,7 @@ public class FieldsParameterTest {
         assertInvalidFields("a:.", "Expecting [a-ZA-Z_], not '.'", 2);
         assertInvalidFields("a:b+", "Expecting [a-zA-Z_.-/(:;], not '+'", 3);
         assertInvalidFields("a;)", "Expecting [a-ZA-Z_], not ')'", 2);
+        assertInvalidFields("*", "Expecting [a-ZA-Z_], not '*'", 0);
     }
 
     @Test
@@ -168,11 +169,14 @@ public class FieldsParameterTest {
         assertInvalidFields("a;", "Unexpected end of input", 2);
         assertInvalidFields("a(", "Unexpected end of input", 2);
         assertInvalidFields("a(a", "Unexpected end of input", 3);
+        assertInvalidFields("library(", "Unexpected end of input", 8);
+        assertInvalidFields("library(album);", "Unexpected end of input", 15);
     }
 
     @Test
     public void testUnexpectedRightParent() {
         assertInvalidFields("a)", "Expecting ';', not ')'", 1);
+        assertInvalidFields("library(album)player", "Expecting ';', not 'p'", 14);
     }
 
     private static void assertInvalidFields(final String str, final String message, final int errorOffset) {
index 375485ba577cee6fb79c65a8ddd5ea81219ee935..ba2447f9ac63b246e32565ffe67e6fcf208c1e02 100644 (file)
@@ -14,6 +14,7 @@ import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.when;
 
+import java.text.ParseException;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
@@ -24,6 +25,7 @@ import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.nb.rfc8040.FieldsParam;
 import org.opendaylight.restconf.nb.rfc8040.TestRestconfUtils;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
@@ -223,8 +225,7 @@ public class ParserFieldsParameterTest {
      */
     @Test
     public void parseFieldsParameterSimplePathTest() {
-        final String input = "library";
-        final List<Set<QName>> parsedFields = ParserFieldsParameter.parseFieldsParameter(identifierJukebox, input);
+        final List<Set<QName>> parsedFields = assertFieldsParameter(identifierJukebox, "library");
 
         assertNotNull(parsedFields);
         assertEquals(1, parsedFields.size());
@@ -237,8 +238,7 @@ public class ParserFieldsParameterTest {
      */
     @Test
     public void parseFieldsParameterDoublePathTest() {
-        final String input = "library;player";
-        final List<Set<QName>> parsedFields = ParserFieldsParameter.parseFieldsParameter(identifierJukebox, input);
+        final List<Set<QName>> parsedFields = assertFieldsParameter(identifierJukebox, "library;player");
 
         assertNotNull(parsedFields);
         assertEquals(1, parsedFields.size());
@@ -252,8 +252,7 @@ public class ParserFieldsParameterTest {
      */
     @Test
     public void parseFieldsParameterSubPathTest() {
-        final String input = "library/album/name";
-        final List<Set<QName>> parsedFields = ParserFieldsParameter.parseFieldsParameter(identifierJukebox, input);
+        final List<Set<QName>> parsedFields = assertFieldsParameter(identifierJukebox, "library/album/name");
 
         assertNotNull(parsedFields);
         assertEquals(3, parsedFields.size());
@@ -273,8 +272,7 @@ public class ParserFieldsParameterTest {
      */
     @Test
     public void parseFieldsParameterChildrenPathTest() {
-        final String input = "library(album(name))";
-        final List<Set<QName>> parsedFields = ParserFieldsParameter.parseFieldsParameter(identifierJukebox, input);
+        final List<Set<QName>> parsedFields = assertFieldsParameter(identifierJukebox, "library(album(name))");
 
         assertNotNull(parsedFields);
         assertEquals(3, parsedFields.size());
@@ -294,8 +292,8 @@ public class ParserFieldsParameterTest {
      */
     @Test
     public void parseFieldsParameterNamespaceTest() {
-        final String input = "augmented-jukebox:augmented-library";
-        final List<Set<QName>> parsedFields = ParserFieldsParameter.parseFieldsParameter(identifierJukebox, input);
+        final List<Set<QName>> parsedFields = assertFieldsParameter(identifierJukebox,
+            "augmented-jukebox:augmented-library");
 
         assertNotNull(parsedFields);
         assertEquals(1, parsedFields.size());
@@ -310,8 +308,8 @@ public class ParserFieldsParameterTest {
      */
     @Test
     public void parseFieldsParameterWithMultipleChildrenTest1() {
-        final String input = "services(type-of-service;instance/instance-name;instance/provider)";
-        final List<Set<QName>> parsedFields = ParserFieldsParameter.parseFieldsParameter(identifierTestServices, input);
+        final List<Set<QName>> parsedFields = assertFieldsParameter(identifierTestServices,
+            "services(type-of-service;instance/instance-name;instance/provider)");
 
         assertNotNull(parsedFields);
         assertEquals(parsedFields.size(), 3);
@@ -332,8 +330,8 @@ public class ParserFieldsParameterTest {
      */
     @Test
     public void parseFieldsParameterWithMultipleChildrenTest2() {
-        final String input = "services(type-of-service;instance(instance-name;provider))";
-        final List<Set<QName>> parsedFields = ParserFieldsParameter.parseFieldsParameter(identifierTestServices, input);
+        final List<Set<QName>> parsedFields = assertFieldsParameter(identifierTestServices,
+            "services(type-of-service;instance(instance-name;provider))");
 
         assertNotNull(parsedFields);
         assertEquals(parsedFields.size(), 3);
@@ -354,8 +352,8 @@ public class ParserFieldsParameterTest {
      */
     @Test
     public void parseFieldsParameterWithMultipleChildrenTest3() {
-        final String input = "services(instance/instance-name;type-of-service;next-data/next-service)";
-        final List<Set<QName>> parsedFields = ParserFieldsParameter.parseFieldsParameter(identifierTestServices, input);
+        final List<Set<QName>> parsedFields = assertFieldsParameter(identifierTestServices,
+            "services(instance/instance-name;type-of-service;next-data/next-service)");
 
         assertNotNull(parsedFields);
         assertEquals(parsedFields.size(), 3);
@@ -372,52 +370,12 @@ public class ParserFieldsParameterTest {
                 List.of(INSTANCE_NAME_Q_NAME, NEXT_SERVICE_Q_NAME)));
     }
 
-    /**
-     * Test parse fields parameter containing not expected character.
-     */
-    @Test
-    public void parseFieldsParameterNotExpectedCharacterNegativeTest() {
-        final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class,
-            () -> ParserFieldsParameter.parseFieldsParameter(identifierJukebox, "*"));
-        // Bad request
-        assertEquals("Error type is not correct", ErrorType.PROTOCOL, ex.getErrors().get(0).getErrorType());
-        assertEquals("Error tag is not correct", ErrorTag.INVALID_VALUE, ex.getErrors().get(0).getErrorTag());
-    }
-
-    /**
-     * Test parse fields parameter with missing closing parenthesis.
-     */
-    @Test
-    public void parseFieldsParameterMissingParenthesisNegativeTest() {
-        final String input = "library(";
-
-        final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class,
-            () -> ParserFieldsParameter.parseFieldsParameter(identifierJukebox, input));
-        // Bad request
-        assertEquals("Error type is not correct", ErrorType.PROTOCOL, ex.getErrors().get(0).getErrorType());
-        assertEquals("Error tag is not correct", ErrorTag.INVALID_VALUE, ex.getErrors().get(0).getErrorTag());
-    }
-
     /**
      * Test parse fields parameter when not existing child node selected.
      */
     @Test
-    public void parseFieldsParameterMissingChildNodeNegativeTest() {
-        final String input = "library(not-existing)";
-
-        final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class,
-            () -> ParserFieldsParameter.parseFieldsParameter(identifierJukebox, input));
-        // Bad request
-        assertEquals("Error type is not correct", ErrorType.PROTOCOL, ex.getErrors().get(0).getErrorType());
-        assertEquals("Error tag is not correct", ErrorTag.INVALID_VALUE, ex.getErrors().get(0).getErrorTag());
-    }
-
-    /**
-     * Test parse fields parameter with unexpected character after parenthesis.
-     */
-    @Test
-    public void parseFieldsParameterAfterParenthesisNegativeTest() {
-        final String input = "library(album);";
+    public void parseFieldsParameterMissingChildNodeNegativeTest() throws ParseException {
+        final FieldsParam input = FieldsParam.parse("library(not-existing)");
 
         final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class,
             () -> ParserFieldsParameter.parseFieldsParameter(identifierJukebox, input));
@@ -426,25 +384,9 @@ public class ParserFieldsParameterTest {
         assertEquals("Error tag is not correct", ErrorTag.INVALID_VALUE, ex.getErrors().get(0).getErrorTag());
     }
 
-    /**
-     * Test parse fields parameter with missing semicolon after parenthesis.
-     */
-    @Test
-    public void parseFieldsParameterMissingSemicolonNegativeTest() {
-        final String input = "library(album)player";
-
-        final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class,
-            () -> ParserFieldsParameter.parseFieldsParameter(this.identifierJukebox, input));
-        // Bad request
-        assertEquals("Error type is not correct", ErrorType.PROTOCOL, ex.getErrors().get(0).getErrorType());
-        assertEquals("Error tag is not correct", ErrorTag.INVALID_VALUE, ex.getErrors().get(0).getErrorTag());
-    }
-
     @Test
     public void parseTopLevelContainerToPathTest() {
-        final String input = "library";
-        final List<YangInstanceIdentifier> parsedFields = ParserFieldsParameter.parseFieldsPaths(
-                identifierJukebox, input);
+        final List<YangInstanceIdentifier> parsedFields = assertFieldsPaths(identifierJukebox, "library");
 
         assertNotNull(parsedFields);
         assertEquals(1, parsedFields.size());
@@ -456,8 +398,7 @@ public class ParserFieldsParameterTest {
     @Test
     public void parseTwoTopLevelContainersToPathsTest() {
         final String input = "library;player";
-        final List<YangInstanceIdentifier> parsedFields = ParserFieldsParameter.parseFieldsPaths(
-                identifierJukebox, input);
+        final List<YangInstanceIdentifier> parsedFields = assertFieldsPaths(identifierJukebox, input);
 
         assertNotNull(parsedFields);
         assertEquals(2, parsedFields.size());
@@ -473,9 +414,7 @@ public class ParserFieldsParameterTest {
 
     @Test
     public void parseNestedLeafToPathTest() {
-        final String input = "library/album/name";
-        final List<YangInstanceIdentifier> parsedFields = ParserFieldsParameter.parseFieldsPaths(
-                identifierJukebox, input);
+        final List<YangInstanceIdentifier> parsedFields = assertFieldsPaths(identifierJukebox, "library/album/name");
 
         assertEquals(1, parsedFields.size());
         final List<PathArgument> pathArguments = parsedFields.get(0).getPathArguments();
@@ -488,9 +427,8 @@ public class ParserFieldsParameterTest {
 
     @Test
     public void parseAugmentedLeafToPathTest() {
-        final String input = "player/augmented-jukebox:speed";
-        final List<YangInstanceIdentifier> parsedFields = ParserFieldsParameter.parseFieldsPaths(
-                identifierJukebox, input);
+        final List<YangInstanceIdentifier> parsedFields = assertFieldsPaths(identifierJukebox,
+            "player/augmented-jukebox:speed");
 
         assertEquals(1, parsedFields.size());
         final List<PathArgument> pathArguments = parsedFields.get(0).getPathArguments();
@@ -503,9 +441,8 @@ public class ParserFieldsParameterTest {
 
     @Test
     public void parseMultipleFieldsOnDifferentLevelsToPathsTest() {
-        final String input = "services(type-of-service;instance/instance-name;instance/provider)";
-        final List<YangInstanceIdentifier> parsedFields = ParserFieldsParameter.parseFieldsPaths(
-                identifierTestServices, input);
+        final List<YangInstanceIdentifier> parsedFields = assertFieldsPaths(identifierTestServices,
+            "services(type-of-service;instance/instance-name;instance/provider)");
 
         assertEquals(3, parsedFields.size());
 
@@ -524,9 +461,8 @@ public class ParserFieldsParameterTest {
 
     @Test
     public void parseListFieldUnderListToPathTest() {
-        final String input = "services/instance";
-        final List<YangInstanceIdentifier> parsedFields = ParserFieldsParameter.parseFieldsPaths(
-                identifierTestServices, input);
+        final List<YangInstanceIdentifier> parsedFields = assertFieldsPaths(identifierTestServices,
+            "services/instance");
 
         assertEquals(1, parsedFields.size());
         final List<PathArgument> pathArguments = parsedFields.get(0).getPathArguments();
@@ -540,9 +476,7 @@ public class ParserFieldsParameterTest {
 
     @Test
     public void parseLeafListFieldToPathTest() {
-        final String input = "protocols";
-        final List<YangInstanceIdentifier> parsedFields = ParserFieldsParameter.parseFieldsPaths(
-                identifierTestServices, input);
+        final List<YangInstanceIdentifier> parsedFields = assertFieldsPaths(identifierTestServices, "protocols");
 
         assertEquals(1, parsedFields.size());
         final List<PathArgument> pathArguments = parsedFields.get(0).getPathArguments();
@@ -557,4 +491,22 @@ public class ParserFieldsParameterTest {
                 .filter(path -> lastPathArg.equals(path.getLastPathArgument().getNodeType()))
                 .findAny();
     }
+
+    private static List<Set<QName>> assertFieldsParameter(final InstanceIdentifierContext<?> identifier,
+            final String input) {
+        return ParserFieldsParameter.parseFieldsParameter(identifier, assertFields(input));
+    }
+
+    private static List<YangInstanceIdentifier> assertFieldsPaths(final InstanceIdentifierContext<?> identifier,
+            final String input) {
+        return ParserFieldsParameter.parseFieldsPaths(identifier, assertFields(input));
+    }
+
+    private static FieldsParam assertFields(final String input) {
+        try {
+            return FieldsParam.parse(input);
+        } catch (ParseException e) {
+            throw new AssertionError(e);
+        }
+    }
 }
\ No newline at end of file