RestconfServer requires ApiPath identifiers 70/107370/18
authorRobert Varga <robert.varga@pantheon.tech>
Sun, 19 Nov 2023 20:37:21 +0000 (21:37 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Wed, 22 Nov 2023 15:12:09 +0000 (16:12 +0100)
Parsing the request URL is the responsibility of the caller, hence we
should accept an ApiPath, not a plain String.

JAX-RS implementation uses JaxRxApiPath's default constructor to bind
with @PathParam semantics and report BadRequestException if anything
goes wrong.

JIRA: NETCONF-1157
Change-Id: Ie9dc79d30cbb4ea52643403f2762090a5e3b3aeb
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
26 files changed:
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsApiPath.java [new file with mode: 0644]
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsRestconf.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/PatchBody.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/ResourceBody.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/legacy/InstanceIdentifierContext.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/utils/parser/IdentifierCodec.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/utils/parser/ParserIdentifier.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/RestconfServer.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/mdsal/MdsalRestconfServer.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/AbstractRestconfTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/Netconf799Test.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/Netconf822Test.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfDataDeleteTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfDataGetTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfDataPostTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfDataPutTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfOperationsGetTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfOperationsPostTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/AbstractPatchBodyTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/JsonPatchBodyMountPointTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/JsonPatchBodyTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/XmlPatchBodyMountPointTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/XmlPatchBodyTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfSchemaServiceTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/utils/parser/IdentifierCodecTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/utils/parser/ParserIdentifierTest.java

diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsApiPath.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsApiPath.java
new file mode 100644 (file)
index 0000000..1bac600
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2023 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.restconf.nb.jaxrs;
+
+import java.text.ParseException;
+import javax.ws.rs.BadRequestException;
+import javax.ws.rs.PathParam;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.restconf.api.ApiPath;
+
+/**
+ * A JAX-RS parsing bridge to {@link ApiPath}.
+ */
+@NonNullByDefault
+final class JaxRsApiPath {
+    final ApiPath apiPath;
+
+    /**
+     * Default constructor for {@link PathParam} integration.
+     *
+     * @param str Path parameter value
+     * @throws NullPointerException if {@code str} is {@code null}
+     * @throws BadRequestException if {@code str} cannmot be interpreted as an {@link ApiPath}
+     */
+    JaxRsApiPath(final String str) {
+        try {
+            apiPath = ApiPath.parseUrl(str);
+        } catch (ParseException e) {
+            throw new BadRequestException(e);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return apiPath.toString();
+    }
+}
index 315c4717abf0676674abb3deeff6d05923ec4108..58ee874343ab49f8425c445ea36ffcadd7ba10f1 100644 (file)
@@ -32,6 +32,7 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriInfo;
+import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.restconf.api.MediaTypes;
 import org.opendaylight.restconf.common.errors.RestconfError;
 import org.opendaylight.restconf.common.errors.RestconfFuture;
@@ -86,9 +87,9 @@ public final class JaxRsRestconf {
     @DELETE
     @Path("/data/{identifier:.+}")
     @SuppressWarnings("checkstyle:abbreviationAsWordInName")
-    public void dataDELETE(@Encoded @PathParam("identifier") final String identifier,
+    public void dataDELETE(@Encoded @PathParam("identifier") final JaxRsApiPath identifier,
             @Suspended final AsyncResponse ar) {
-        server.dataDELETE(identifier).addCallback(new JaxRsRestconfCallback<>(ar) {
+        server.dataDELETE(identifier.apiPath).addCallback(new JaxRsRestconfCallback<>(ar) {
             @Override
             Response transform(final Empty result) {
                 return Response.noContent().build();
@@ -132,10 +133,10 @@ public final class JaxRsRestconf {
         MediaType.APPLICATION_XML,
         MediaType.TEXT_XML
     })
-    public void dataGET(@Encoded @PathParam("identifier") final String identifier,
+    public void dataGET(@Encoded @PathParam("identifier") final JaxRsApiPath identifier,
             @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
         final var readParams = QueryParams.newReadDataParams(uriInfo);
-        completeDataGET(server.dataGET(identifier, readParams), readParams, ar);
+        completeDataGET(server.dataGET(identifier.apiPath, readParams), readParams, ar);
     }
 
     private static void completeDataGET(final RestconfFuture<NormalizedNodePayload> future,
@@ -195,10 +196,10 @@ public final class JaxRsRestconf {
         MediaType.APPLICATION_XML,
         MediaType.TEXT_XML
     })
-    public void dataXmlPATCH(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
+    public void dataXmlPATCH(@Encoded @PathParam("identifier") final JaxRsApiPath identifier, final InputStream body,
             @Suspended final AsyncResponse ar) {
         try (var xmlBody = new XmlResourceBody(body)) {
-            completeDataPATCH(server.dataPATCH(identifier, xmlBody), ar);
+            completeDataPATCH(server.dataPATCH(identifier.apiPath, xmlBody), ar);
         }
     }
 
@@ -235,10 +236,10 @@ public final class JaxRsRestconf {
         MediaTypes.APPLICATION_YANG_DATA_JSON,
         MediaType.APPLICATION_JSON,
     })
-    public void dataJsonPATCH(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
+    public void dataJsonPATCH(@Encoded @PathParam("identifier") final JaxRsApiPath identifier, final InputStream body,
             @Suspended final AsyncResponse ar) {
         try (var jsonBody = new JsonResourceBody(body)) {
-            completeDataPATCH(server.dataPATCH(identifier, jsonBody), ar);
+            completeDataPATCH(server.dataPATCH(identifier.apiPath, jsonBody), ar);
         }
     }
 
@@ -286,10 +287,10 @@ public final class JaxRsRestconf {
         MediaTypes.APPLICATION_YANG_DATA_JSON,
         MediaTypes.APPLICATION_YANG_DATA_XML
     })
-    public void dataYangJsonPATCH(@Encoded @PathParam("identifier") final String identifier,
+    public void dataYangJsonPATCH(@Encoded @PathParam("identifier") final JaxRsApiPath identifier,
             final InputStream body, @Suspended final AsyncResponse ar) {
         try (var jsonBody = new JsonPatchBody(body)) {
-            completeDataYangPATCH(server.dataPATCH(identifier, jsonBody), ar);
+            completeDataYangPATCH(server.dataPATCH(identifier.apiPath, jsonBody), ar);
         }
     }
 
@@ -328,10 +329,10 @@ public final class JaxRsRestconf {
         MediaTypes.APPLICATION_YANG_DATA_JSON,
         MediaTypes.APPLICATION_YANG_DATA_XML
     })
-    public void dataYangXmlPATCH(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
-            @Suspended final AsyncResponse ar) {
+    public void dataYangXmlPATCH(@Encoded @PathParam("identifier") final JaxRsApiPath identifier,
+            final InputStream body, @Suspended final AsyncResponse ar) {
         try (var xmlBody = new XmlPatchBody(body)) {
-            completeDataYangPATCH(server.dataPATCH(identifier, xmlBody), ar);
+            completeDataYangPATCH(server.dataPATCH(identifier.apiPath, xmlBody), ar);
         }
     }
 
@@ -401,10 +402,10 @@ public final class JaxRsRestconf {
         MediaTypes.APPLICATION_YANG_DATA_JSON,
         MediaType.APPLICATION_JSON,
     })
-    public void postDataJSON(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
+    public void postDataJSON(@Encoded @PathParam("identifier") final JaxRsApiPath identifier, final InputStream body,
             @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
-        completeDataPOST(server.dataPOST(identifier, new JsonDataPostBody(body), QueryParams.normalize(uriInfo)),
-            uriInfo, ar);
+        completeDataPOST(server.dataPOST(identifier.apiPath, new JsonDataPostBody(body),
+            QueryParams.normalize(uriInfo)), uriInfo, ar);
     }
 
     /**
@@ -442,9 +443,9 @@ public final class JaxRsRestconf {
         MediaType.APPLICATION_XML,
         MediaType.TEXT_XML
     })
-    public void postDataXML(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
+    public void postDataXML(@Encoded @PathParam("identifier") final JaxRsApiPath identifier, final InputStream body,
             @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
-        completeDataPOST(server.dataPOST(identifier, new XmlDataPostBody(body), QueryParams.normalize(uriInfo)),
+        completeDataPOST(server.dataPOST(identifier.apiPath, new XmlDataPostBody(body), QueryParams.normalize(uriInfo)),
             uriInfo, ar);
     }
 
@@ -504,10 +505,10 @@ public final class JaxRsRestconf {
         MediaTypes.APPLICATION_YANG_DATA_JSON,
         MediaType.APPLICATION_JSON,
     })
-    public void dataJsonPUT(@Encoded @PathParam("identifier") final String identifier,
+    public void dataJsonPUT(@Encoded @PathParam("identifier") final JaxRsApiPath identifier,
             @Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
         try (var jsonBody = new JsonResourceBody(body)) {
-            completeDataPUT(server.dataPUT(identifier, jsonBody, QueryParams.normalize(uriInfo)), ar);
+            completeDataPUT(server.dataPUT(identifier.apiPath, jsonBody, QueryParams.normalize(uriInfo)), ar);
         }
     }
 
@@ -546,10 +547,10 @@ public final class JaxRsRestconf {
         MediaType.APPLICATION_XML,
         MediaType.TEXT_XML
     })
-    public void dataXmlPUT(@Encoded @PathParam("identifier") final String identifier,
+    public void dataXmlPUT(@Encoded @PathParam("identifier") final JaxRsApiPath identifier,
             @Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
         try (var xmlBody = new XmlResourceBody(body)) {
-            completeDataPUT(server.dataPUT(identifier, xmlBody, QueryParams.normalize(uriInfo)), ar);
+            completeDataPUT(server.dataPUT(identifier.apiPath, xmlBody, QueryParams.normalize(uriInfo)), ar);
         }
     }
 
@@ -587,8 +588,8 @@ public final class JaxRsRestconf {
     @GET
     @Path("/operations/{operation:.+}")
     @Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
-    public void operationsJsonGET(@PathParam("operation") final String operation, final AsyncResponse ar) {
-        completeOperationsGet(server.operationsGET(operation), ar, OperationsGetResult::toJSON);
+    public void operationsJsonGET(@PathParam("operation") final JaxRsApiPath operation, final AsyncResponse ar) {
+        completeOperationsGet(server.operationsGET(operation.apiPath), ar, OperationsGetResult::toJSON);
     }
 
     private static void completeOperationsJsonGet(final RestconfFuture<OperationsGetResult> future,
@@ -617,8 +618,8 @@ public final class JaxRsRestconf {
     @GET
     @Path("/operations/{operation:.+}")
     @Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
-    public void operationsXmlGET(@PathParam("operation") final String operation, final AsyncResponse ar) {
-        completeOperationsXmlGet(server.operationsGET(operation), ar);
+    public void operationsXmlGET(@PathParam("operation") final JaxRsApiPath operation, final AsyncResponse ar) {
+        completeOperationsXmlGet(server.operationsGET(operation.apiPath), ar);
     }
 
     private static void completeOperationsXmlGet(final RestconfFuture<OperationsGetResult> future,
@@ -659,10 +660,10 @@ public final class JaxRsRestconf {
         MediaType.APPLICATION_XML,
         MediaType.TEXT_XML
     })
-    public void operationsXmlPOST(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
-            @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
+    public void operationsXmlPOST(@Encoded @PathParam("identifier") final JaxRsApiPath identifier,
+            final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
         try (var xmlBody = new XmlOperationInputBody(body)) {
-            operationsPOST(identifier, uriInfo, ar, xmlBody);
+            operationsPOST(identifier.apiPath, uriInfo, ar, xmlBody);
         }
     }
 
@@ -688,14 +689,14 @@ public final class JaxRsRestconf {
         MediaType.APPLICATION_XML,
         MediaType.TEXT_XML
     })
-    public void operationsJsonPOST(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
-            @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
+    public void operationsJsonPOST(@Encoded @PathParam("identifier") final JaxRsApiPath identifier,
+            final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
         try (var jsonBody = new JsonOperationInputBody(body)) {
-            operationsPOST(identifier, uriInfo, ar, jsonBody);
+            operationsPOST(identifier.apiPath, uriInfo, ar, jsonBody);
         }
     }
 
-    private void operationsPOST(final String identifier, final UriInfo uriInfo, final AsyncResponse ar,
+    private void operationsPOST(final ApiPath identifier, final UriInfo uriInfo, final AsyncResponse ar,
             final OperationInputBody body) {
         server.operationsPOST(uriInfo.getBaseUri(), identifier, body)
             .addCallback(new JaxRsRestconfCallback<OperationOutput>(ar) {
index 46598b7ef8f8a94088a1ba2ae050eb47d68f05cb..2b55f9ae905429aac6f94966b96dea3354554073 100644 (file)
@@ -12,10 +12,13 @@ import static com.google.common.base.Verify.verify;
 import java.io.IOException;
 import java.io.InputStream;
 import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.common.patch.PatchContext;
 import org.opendaylight.restconf.nb.rfc8040.utils.parser.IdentifierCodec;
 import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.patch.rev170222.yang.patch.yang.patch.Edit.Operation;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 
@@ -52,7 +55,12 @@ public abstract sealed class PatchBody extends AbstractBody permits JsonPatchBod
             targetUrl = IdentifierCodec.serialize(urlPath, context) + target;
         }
 
-        return ParserIdentifier.toInstanceIdentifier(targetUrl, context, null).getInstanceIdentifier();
+        try {
+            return ParserIdentifier.toInstanceIdentifier(targetUrl, context, null).getInstanceIdentifier();
+        } catch (RestconfDocumentedException e) {
+            throw new RestconfDocumentedException("Failed to parse target " + target,
+                ErrorType.RPC, ErrorTag.MALFORMED_MESSAGE, e);
+        }
     }
 
     /**
index a455511fee2afb9f4618fde759abe276eb34d725..8f2d3d2ec6914caa4f1272adb0cb87d0f210c3b1 100644 (file)
@@ -95,7 +95,7 @@ public abstract sealed class ResourceBody extends AbstractBody permits JsonResou
     /**
      * Valid top level node name.
      *
-     * @param path path of node
+     * @param apiPath path of node
      * @param data data
      */
     @VisibleForTesting
index e498204e2e88a5f40bc2302fe4bffab1a73b36a0..07f5d798c5c993e44c8a64ad2e4ef9faeefadc10 100644 (file)
@@ -11,9 +11,19 @@ import static com.google.common.base.Verify.verify;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
+import org.opendaylight.mdsal.dom.api.DOMSchemaService;
+import org.opendaylight.restconf.api.ApiPath;
+import org.opendaylight.restconf.api.ApiPath.Step;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.IdentifierCodec;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.YangInstanceIdentifierDeserializer;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
@@ -102,6 +112,58 @@ public abstract class InstanceIdentifierContext {
         this.mountPoint = mountPoint;
     }
 
+    // FIXME: NETCONF-773: this recursion should really live in MdsalRestconfServer
+    public static @NonNull InstanceIdentifierContext ofApiPath(final ApiPath path,
+            final EffectiveModelContext modelContext, final DOMMountPointService mountPointService) {
+        final var steps = path.steps();
+        final var limit = steps.size() - 1;
+
+        var prefix = 0;
+        DOMMountPoint currentMountPoint = null;
+        var currentModelContext = modelContext;
+        while (prefix <= limit) {
+            final var mount = indexOfMount(steps, prefix, limit);
+            if (mount == -1) {
+                break;
+            }
+
+            final var mountService = currentMountPoint == null ? mountPointService
+                : currentMountPoint.getService(DOMMountPointService.class).orElse(null);
+            if (mountService == null) {
+                throw new RestconfDocumentedException("Mount point service is not available",
+                    ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED);
+            }
+
+            final var mountPath = IdentifierCodec.deserialize(path.subPath(prefix, mount), modelContext);
+            final var userPath = path.subPath(0, mount);
+            final var nextMountPoint = mountService.getMountPoint(mountPath)
+                .orElseThrow(() -> new RestconfDocumentedException("Mount point '" + userPath + "' does not exist",
+                    ErrorType.PROTOCOL, ErrorTags.RESOURCE_DENIED_TRANSPORT));
+            final var nextModelContext = nextMountPoint.getService(DOMSchemaService.class)
+                .orElseThrow(() -> new RestconfDocumentedException(
+                    "Mount point " + userPath + " does not expose DOMSchemaService",
+                    ErrorType.PROTOCOL, ErrorTags.RESOURCE_DENIED_TRANSPORT))
+                .getGlobalContext();
+
+            prefix = mount + 1;
+            currentModelContext = nextModelContext;
+            currentMountPoint = nextMountPoint;
+        }
+
+        final var result = YangInstanceIdentifierDeserializer.create(currentModelContext, path.subPath(prefix));
+        return InstanceIdentifierContext.ofPath(result.stack, result.node, result.path, currentMountPoint);
+    }
+
+    private static int indexOfMount(final ImmutableList<Step> steps, final int fromIndex, final int limit) {
+        for (int i = fromIndex; i <= limit; ++ i) {
+            final var step = steps.get(i);
+            if ("yang-ext".equals(step.module()) && "mount".equals(step.identifier().getLocalName())) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
     public static @NonNull InstanceIdentifierContext ofLocalRoot(final EffectiveModelContext context) {
         return new Root(context, null);
     }
index b9999e979ef41b1f9320eafcc16247a0580b32c4..8c0e15e2976f8c30afd4da20c3b9401d4c490bfa 100644 (file)
@@ -7,6 +7,7 @@
  */
 package org.opendaylight.restconf.nb.rfc8040.utils.parser;
 
+import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 
@@ -25,7 +26,7 @@ public final class IdentifierCodec {
         return YangInstanceIdentifierSerializer.create(schemaContext, data);
     }
 
-    public static YangInstanceIdentifier deserialize(final String data, final EffectiveModelContext schemaContext) {
+    public static YangInstanceIdentifier deserialize(final ApiPath data, final EffectiveModelContext schemaContext) {
         return data == null ? YangInstanceIdentifier.of()
             : YangInstanceIdentifierDeserializer.create(schemaContext, data).path;
     }
index eb984e873e8537e16b84df45bdb802e20a6e8a69..e4aaa43345069478927a419653912ef7daa80f51 100644 (file)
@@ -13,6 +13,7 @@ import static java.util.Objects.requireNonNull;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Splitter;
 import com.google.common.collect.Iterables;
+import java.text.ParseException;
 import java.time.format.DateTimeParseException;
 import java.util.Date;
 import java.util.Iterator;
@@ -22,15 +23,14 @@ import org.opendaylight.mdsal.dom.api.DOMMountPoint;
 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
 import org.opendaylight.mdsal.dom.api.DOMYangTextSourceProvider;
+import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
-import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags;
 import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
 import org.opendaylight.restconf.nb.rfc8040.rests.services.api.SchemaExportContext;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.common.Revision;
 import org.opendaylight.yangtools.yang.common.YangNames;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
@@ -44,7 +44,6 @@ public final class ParserIdentifier {
     //        nested path split on yang-ext:mount. This splitting needs to be based on consulting the
     //        EffectiveModelContext and allowing it only where yang-ext:mount is actually used in models.
     private static final String MOUNT = "yang-ext:mount";
-    private static final Splitter MP_SPLITTER = Splitter.on("/" + MOUNT);
     private static final Splitter SLASH_SPLITTER = Splitter.on('/');
 
     private ParserIdentifier() {
@@ -52,7 +51,7 @@ public final class ParserIdentifier {
     }
 
     /**
-     * Make {@link InstanceIdentifierContext} from {@link String} identifier
+     * Make {@link InstanceIdentifierContext} from {@link String} identifier.
      * <br>
      * For identifiers of data NOT behind mount points returned
      * {@link InstanceIdentifierContext} is prepared with {@code null} reference of {@link DOMMountPoint} and with
@@ -62,59 +61,21 @@ public final class ParserIdentifier {
      * {@link InstanceIdentifierContext} is prepared with reference of {@link DOMMountPoint} and its
      * own {@link SchemaContext}.
      *
-     * @param identifier
-     *           - path identifier
-     * @param schemaContext
-     *           - controller schema context
-     * @param mountPointService
-     *           - mount point service
+     * @param identifier path identifier
+     * @param schemaContext controller schema context
+     * @param mountPointService mount point service
      * @return {@link InstanceIdentifierContext}
      */
-    // FIXME: NETCONF-631: this method should not be here, it should be a static factory in InstanceIdentifierContext:
-    //
-    //        @NonNull InstanceIdentifierContext forUrl(identifier, schemaContexxt, mountPointService)
-    //
     public static InstanceIdentifierContext toInstanceIdentifier(final String identifier,
             final EffectiveModelContext schemaContext, final @Nullable DOMMountPointService mountPointService) {
-        if (identifier == null || !identifier.contains(MOUNT)) {
-            return createIIdContext(schemaContext, identifier, null);
-        }
-        if (mountPointService == null) {
-            throw new RestconfDocumentedException("Mount point service is not available");
-        }
-
-        final Iterator<String> pathsIt = MP_SPLITTER.split(identifier).iterator();
-        final String mountPointId = pathsIt.next();
-        final YangInstanceIdentifier mountPath = IdentifierCodec.deserialize(mountPointId, schemaContext);
-        final DOMMountPoint mountPoint = mountPointService.getMountPoint(mountPath)
-                .orElseThrow(() -> new RestconfDocumentedException("Mount point does not exist.",
-                    ErrorType.PROTOCOL, ErrorTags.RESOURCE_DENIED_TRANSPORT));
-
-        final EffectiveModelContext mountSchemaContext = coerceModelContext(mountPoint);
-        final String pathId = pathsIt.next().replaceFirst("/", "");
-        return createIIdContext(mountSchemaContext, pathId, mountPoint);
-    }
-
-    /**
-     * Method to create {@link InstanceIdentifierContext} from {@link YangInstanceIdentifier}
-     * and {@link SchemaContext}, {@link DOMMountPoint}.
-     *
-     * @param url Invocation URL
-     * @param schemaContext SchemaContext in which the path is to be interpreted in
-     * @param mountPoint A mount point handle, if the URL is being interpreted relative to a mount point
-     * @return {@link InstanceIdentifierContext}
-     * @throws RestconfDocumentedException if the path cannot be resolved
-     */
-    private static InstanceIdentifierContext createIIdContext(final EffectiveModelContext schemaContext,
-            final String url, final @Nullable DOMMountPoint mountPoint) {
-        // First things first: an empty path means data invocation on SchemaContext
-        if (url == null) {
-            return mountPoint != null ? InstanceIdentifierContext.ofMountPointRoot(mountPoint, schemaContext)
-                : InstanceIdentifierContext.ofLocalRoot(schemaContext);
+        final ApiPath apiPath;
+        try {
+            apiPath = ApiPath.parseUrl(identifier);
+        } catch (ParseException e) {
+            throw new RestconfDocumentedException(e.getMessage(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e);
         }
 
-        final var result = YangInstanceIdentifierDeserializer.create(schemaContext, url);
-        return InstanceIdentifierContext.ofPath(result.stack, result.node, result.path, mountPoint);
+        return InstanceIdentifierContext.ofApiPath(apiPath, schemaContext, mountPointService);
     }
 
     /**
index b5c6b7ad29b70a3b30e35db99d207c8d30b3dbac..dbef7817e8fbf25e806ae4b56b22792d6949e5ca 100644 (file)
@@ -37,7 +37,7 @@ public interface RestconfServer {
      * @return A {@link RestconfFuture} of the operation
      */
     @SuppressWarnings("checkstyle:abbreviationAsWordInName")
-    RestconfFuture<Empty> dataDELETE(String identifier);
+    RestconfFuture<Empty> dataDELETE(ApiPath identifier);
 
     /**
      * Return the content of the datastore.
@@ -54,7 +54,7 @@ public interface RestconfServer {
      * @param readParams {@link ReadDataParams} for this request
      * @return A {@link RestconfFuture} of the {@link NormalizedNodePayload} content
      */
-    RestconfFuture<NormalizedNodePayload> dataGET(String identifier, ReadDataParams readParams);
+    RestconfFuture<NormalizedNodePayload> dataGET(ApiPath identifier, ReadDataParams readParams);
 
     /**
      * Partially modify the target data resource, as defined in
@@ -73,7 +73,7 @@ public interface RestconfServer {
      * @param body data node for put to config DS
      * @return A {@link RestconfFuture} of the operation
      */
-    RestconfFuture<Empty> dataPATCH(String identifier, ResourceBody body);
+    RestconfFuture<Empty> dataPATCH(ApiPath identifier, ResourceBody body);
 
     /**
      * Ordered list of edits that are applied to the datastore by the server, as defined in
@@ -92,11 +92,11 @@ public interface RestconfServer {
      * @param body YANG Patch body
      * @return A {@link RestconfFuture} of the {@link PatchStatusContext} content
      */
-    RestconfFuture<PatchStatusContext> dataPATCH(String identifier, PatchBody body);
+    RestconfFuture<PatchStatusContext> dataPATCH(ApiPath identifier, PatchBody body);
 
     RestconfFuture<CreateResource> dataPOST(ChildBody body, Map<String, String> queryParameters);
 
-    RestconfFuture<? extends DataPostResult> dataPOST(String identifier, DataPostBody body,
+    RestconfFuture<? extends DataPostResult> dataPOST(ApiPath identifier, DataPostBody body,
         Map<String, String> queryParameters);
 
     /**
@@ -116,10 +116,10 @@ public interface RestconfServer {
      * @param queryParameters Query parameters
      * @return A {@link RestconfFuture} completing with {@link DataPutResult}
      */
-    RestconfFuture<DataPutResult> dataPUT(String identifier, ResourceBody body, Map<String, String> queryParameters);
+    RestconfFuture<DataPutResult> dataPUT(ApiPath identifier, ResourceBody body, Map<String, String> queryParameters);
 
     /**
-     * Return the set of supported RPCs supported by {@link #operationsPOST(URI, String, OperationInputBody)},
+     * Return the set of supported RPCs supported by {@link #operationsPOST(URI, ApiPath, OperationInputBody)},
      * as expressed in the <a href="https://www.rfc-editor.org/rfc/rfc8040#page-84">ietf-restconf.yang</a>
      * {@code container operations} statement.
      *
@@ -136,7 +136,7 @@ public interface RestconfServer {
      * @param operation An operation
      * @return A {@link RestconfFuture} completing with an {@link OperationsGetResult}
      */
-    RestconfFuture<OperationsGetResult> operationsGET(String operation);
+    RestconfFuture<OperationsGetResult> operationsGET(ApiPath operation);
 
     /**
      * Invoke an RPC operation, as defined in
@@ -150,7 +150,7 @@ public interface RestconfServer {
     // FIXME: 'operation' should really be an ApiIdentifier with non-null module, but we also support ang-ext:mount,
     //        and hence it is a path right now
     // FIXME: use ApiPath instead of String
-    RestconfFuture<OperationOutput> operationsPOST(URI restconfURI, String operation, OperationInputBody body);
+    RestconfFuture<OperationOutput> operationsPOST(URI restconfURI, ApiPath operation, OperationInputBody body);
 
     /**
      * Return the revision of {@code ietf-yang-library} module implemented by this server, as defined in
index 75956a797f28ceb33ca9242bbc4139cb4c4aa585..5329bfa61320c3a23bd8f13ae68d8c82714a146a 100644 (file)
@@ -7,7 +7,6 @@
  */
 package org.opendaylight.restconf.server.mdsal;
 
-import static com.google.common.base.Verify.verifyNotNull;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.VisibleForTesting;
@@ -43,6 +42,7 @@ import org.opendaylight.mdsal.dom.api.DOMMountPoint;
 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
 import org.opendaylight.mdsal.dom.api.DOMRpcService;
 import org.opendaylight.mdsal.dom.spi.SimpleDOMActionResult;
+import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.common.errors.RestconfFuture;
 import org.opendaylight.restconf.common.errors.SettableRestconfFuture;
@@ -62,7 +62,6 @@ import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy;
 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
-import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
 import org.opendaylight.restconf.server.api.DataPostResult;
 import org.opendaylight.restconf.server.api.DataPostResult.CreateResource;
 import org.opendaylight.restconf.server.api.DataPostResult.InvokeOperation;
@@ -149,7 +148,7 @@ public final class MdsalRestconfServer implements RestconfServer {
     }
 
     @Override
-    public RestconfFuture<Empty> dataDELETE(final String identifier) {
+    public RestconfFuture<Empty> dataDELETE(final ApiPath identifier) {
         final var reqPath = bindRequestPath(identifier);
         final var strategy = getRestconfStrategy(reqPath.getSchemaContext(), reqPath.getMountPoint());
         return strategy.delete(reqPath.getInstanceIdentifier());
@@ -161,7 +160,7 @@ public final class MdsalRestconfServer implements RestconfServer {
     }
 
     @Override
-    public RestconfFuture<NormalizedNodePayload> dataGET(final String identifier, final ReadDataParams readParams) {
+    public RestconfFuture<NormalizedNodePayload> dataGET(final ApiPath identifier, final ReadDataParams readParams) {
         return readData(bindRequestPath(identifier), readParams);
     }
 
@@ -192,7 +191,7 @@ public final class MdsalRestconfServer implements RestconfServer {
     }
 
     @Override
-    public RestconfFuture<Empty> dataPATCH(final String identifier, final ResourceBody body) {
+    public RestconfFuture<Empty> dataPATCH(final ApiPath identifier, final ResourceBody body) {
         return dataPATCH(bindRequestPath(identifier), body);
     }
 
@@ -207,7 +206,7 @@ public final class MdsalRestconfServer implements RestconfServer {
     }
 
     @Override
-    public RestconfFuture<PatchStatusContext> dataPATCH(final String identifier, final PatchBody body) {
+    public RestconfFuture<PatchStatusContext> dataPATCH(final ApiPath identifier, final PatchBody body) {
         return dataPATCH(bindRequestPath(identifier), body);
     }
 
@@ -231,7 +230,7 @@ public final class MdsalRestconfServer implements RestconfServer {
     }
 
     @Override
-    public RestconfFuture<? extends DataPostResult> dataPOST(final String identifier, final DataPostBody body,
+    public RestconfFuture<? extends DataPostResult> dataPOST(final ApiPath identifier, final DataPostBody body,
             final Map<String, String> queryParameters) {
         final var reqPath = bindRequestPath(identifier);
         if (reqPath.getSchemaNode() instanceof ActionDefinition) {
@@ -363,7 +362,7 @@ public final class MdsalRestconfServer implements RestconfServer {
     }
 
     @Override
-    public RestconfFuture<DataPutResult> dataPUT(final String identifier, final ResourceBody body,
+    public RestconfFuture<DataPutResult> dataPUT(final ApiPath identifier, final ResourceBody body,
              final Map<String, String> queryParameters) {
         return dataPUT(bindRequestPath(identifier), body, queryParameters);
     }
@@ -387,7 +386,7 @@ public final class MdsalRestconfServer implements RestconfServer {
     }
 
     @Override
-    public RestconfFuture<OperationsGetResult> operationsGET(final String operation) {
+    public RestconfFuture<OperationsGetResult> operationsGET(final ApiPath operation) {
         // get current module RPCs/actions by RPC/action name
         final var inference = bindRequestPath(operation).inference();
         if (inference.isEmpty()) {
@@ -436,7 +435,7 @@ public final class MdsalRestconfServer implements RestconfServer {
     }
 
     @Override
-    public RestconfFuture<OperationOutput> operationsPOST(final URI restconfURI, final String apiPath,
+    public RestconfFuture<OperationOutput> operationsPOST(final URI restconfURI, final ApiPath apiPath,
             final OperationInputBody body) {
         final var currentContext = databindProvider.currentContext();
         final var reqPath = bindRequestPath(currentContext, apiPath);
@@ -471,16 +470,14 @@ public final class MdsalRestconfServer implements RestconfServer {
                 .map(Revision::toString).orElse(""))));
     }
 
-    private @NonNull InstanceIdentifierContext bindRequestPath(final String identifier) {
+    private @NonNull InstanceIdentifierContext bindRequestPath(final @NonNull ApiPath identifier) {
         return bindRequestPath(databindProvider.currentContext(), identifier);
     }
 
-    private @NonNull InstanceIdentifierContext bindRequestPath(final DatabindContext databind,
-            final String identifier) {
-        // FIXME: go through ApiPath first. That part should eventually live in callers
+    private @NonNull InstanceIdentifierContext bindRequestPath(final @NonNull DatabindContext databind,
+            final @NonNull ApiPath identifier) {
         // FIXME: DatabindContext looks like it should be internal
-        return verifyNotNull(ParserIdentifier.toInstanceIdentifier(requireNonNull(identifier), databind.modelContext(),
-            mountPointService));
+        return InstanceIdentifierContext.ofApiPath(identifier, databind.modelContext(), mountPointService);
     }
 
     private @NonNull InstanceIdentifierContext bindRequestRoot() {
index f666f0d4448de7d55db813b77b2878e26ec0fafb..464042388780f6cdf27e8b3a0903ae930f9f7173 100644 (file)
@@ -41,6 +41,8 @@ import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 
 @ExtendWith(MockitoExtension.class)
 abstract class AbstractRestconfTest extends AbstractJukeboxTest {
+    static final JaxRsApiPath JUKEBOX_API_PATH = new JaxRsApiPath("example-jukebox:jukebox");
+
     @Mock
     UriInfo uriInfo;
     @Mock
index bee242b097bd42edb0b2632cbb3f2dd1aa01d95d..f337df24733f78ff893a288061b632b2583dc1ca 100644 (file)
@@ -66,7 +66,7 @@ class Netconf799Test extends AbstractInstanceIdentifierTest {
             () -> DatabindContext.ofModel(IID_SCHEMA), dataBroker, rpcService, actionService, mountPointService));
         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
         doReturn(true).when(asyncResponse).resume(captor.capture());
-        restconf.postDataJSON("instance-identifier-module:cont/cont1/reset",
+        restconf.postDataJSON(new JaxRsApiPath("instance-identifier-module:cont/cont1/reset"),
             stringInputStream("""
             {
               "instance-identifier-module:input": {
index 3c41d6b89dacf4612f2ca2422b2b4df88572d3b3..b32a1cd67ff60d95850d8c6f960142dd5a676e20 100644 (file)
@@ -43,9 +43,10 @@ class Netconf822Test extends AbstractRestconfTest {
 
     @Test
     void testOperationsContentByIdentifier() {
+        final var apiPath = new JaxRsApiPath("foo:new1");
         assertEquals("""
-            { "foo:new1" : [null] }""", assertEntity(200, ar -> restconf.operationsJsonGET("foo:new1", ar)));
+            { "foo:new1" : [null] }""", assertEntity(200, ar -> restconf.operationsJsonGET(apiPath, ar)));
         assertEquals("""
-            <new1 xmlns="foo"/>""", assertEntity(200, ar -> restconf.operationsXmlGET("foo:new1", ar)));
+            <new1 xmlns="foo"/>""", assertEntity(200, ar -> restconf.operationsXmlGET(apiPath, ar)));
     }
 }
index 510a6bdeb0d9a212e7ad6e185aa1aeaa27e738f9..b046fd4d09a6db24d08ba9cb18fb26871df12a6e 100644 (file)
@@ -50,7 +50,7 @@ class RestconfDataDeleteTest extends AbstractRestconfTest {
                 .when(tx).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
         doReturn(CommitInfo.emptyFluentFuture()).when(tx).commit();
 
-        assertNull(assertEntity(204, ar -> restconf.dataDELETE("example-jukebox:jukebox", ar)));
+        assertNull(assertEntity(204, ar -> restconf.dataDELETE(JUKEBOX_API_PATH, ar)));
     }
 
     @Test
@@ -59,7 +59,7 @@ class RestconfDataDeleteTest extends AbstractRestconfTest {
                 .when(tx).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
         doReturn(true).when(tx).cancel();
 
-        final var error = assertError(ar -> restconf.dataDELETE("example-jukebox:jukebox", ar));
+        final var error = assertError(ar -> restconf.dataDELETE(JUKEBOX_API_PATH, ar));
         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
         assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
     }
@@ -81,7 +81,7 @@ class RestconfDataDeleteTest extends AbstractRestconfTest {
                 .when(tx).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
         doReturn(CommitInfo.emptyFluentFuture()).when(tx).commit();
 
-        assertNull(assertEntity(204,
-            ar -> restconf.dataDELETE("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox", ar)));
+        assertNull(assertEntity(204, ar -> restconf.dataDELETE(
+            new JaxRsApiPath("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox"), ar)));
     }
 }
index c60f8dfb6034297127d98606325392f07ef5e038..7c89688d6ba9d3533fef427e7f992a3b57ad3f30 100644 (file)
@@ -72,8 +72,7 @@ class RestconfDataGetTest extends AbstractRestconfTest {
         doReturn(immediateFluentFuture(Optional.empty()))
                 .when(tx).read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
 
-        assertEquals(EMPTY_JUKEBOX,
-            assertNormalizedNode(200, ar -> restconf.dataGET("example-jukebox:jukebox", uriInfo, ar)));
+        assertEquals(EMPTY_JUKEBOX, assertNormalizedNode(200, ar -> restconf.dataGET(JUKEBOX_API_PATH, uriInfo, ar)));
     }
 
     @Test
@@ -123,8 +122,8 @@ class RestconfDataGetTest extends AbstractRestconfTest {
 
         // response must contain all child nodes from config and operational containers merged in one container
         final var data = assertInstanceOf(ContainerNode.class,
-            assertNormalizedNode(200, ar ->
-                restconf.dataGET("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox", uriInfo, ar)));
+            assertNormalizedNode(200, ar -> restconf.dataGET(
+                new JaxRsApiPath("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox"), uriInfo, ar)));
         assertEquals(3, data.size());
         assertNotNull(data.childByArg(CONT_PLAYER.name()));
         assertNotNull(data.childByArg(LIBRARY_NID));
@@ -139,7 +138,7 @@ class RestconfDataGetTest extends AbstractRestconfTest {
         doReturn(immediateFluentFuture(Optional.empty()))
                 .when(tx).read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
 
-        final var error = assertError(ar -> restconf.dataGET("example-jukebox:jukebox", uriInfo, ar));
+        final var error = assertError(ar -> restconf.dataGET(JUKEBOX_API_PATH, uriInfo, ar));
         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
         assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
         assertEquals("Request could not be completed because the relevant data model content does not exist",
@@ -160,7 +159,7 @@ class RestconfDataGetTest extends AbstractRestconfTest {
 
         // response must contain only config data
         final var data = assertInstanceOf(ContainerNode.class,
-            assertNormalizedNode(200, ar -> restconf.dataGET("example-jukebox:jukebox", uriInfo, ar)));
+            assertNormalizedNode(200, ar -> restconf.dataGET(JUKEBOX_API_PATH, uriInfo, ar)));
         // config data present
         assertNotNull(data.childByArg(CONT_PLAYER.name()));
         assertNotNull(data.childByArg(LIBRARY_NID));
@@ -181,8 +180,8 @@ class RestconfDataGetTest extends AbstractRestconfTest {
                 .read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
 
         // response must contain only operational data
-        final var data = assertInstanceOf(ContainerNode.class,
-            assertNormalizedNode(200, ar -> restconf.dataGET("example-jukebox:jukebox", uriInfo, ar)));
+        final var data = assertInstanceOf(ContainerNode.class, assertNormalizedNode(200,
+            ar -> restconf.dataGET(JUKEBOX_API_PATH, uriInfo, ar)));
         // state data present
         assertNotNull(data.childByArg(CONT_PLAYER.name()));
         assertNotNull(data.childByArg(PLAYLIST_NID));
index 9fcbc667137dd52501fb9398692e42dea3b82102..d4f592bd00c42558d8ea150b1f754b5eaa205905 100644 (file)
@@ -62,7 +62,7 @@ class RestconfDataPostTest extends AbstractRestconfTest {
         doReturn(UriBuilder.fromUri("http://localhost:8181/rests/")).when(uriInfo).getBaseUriBuilder();
 
         assertEquals(URI.create("http://localhost:8181/rests/data/example-jukebox:jukebox/playlist=name%20of%20band"),
-            assertResponse(201, ar -> restconf.postDataJSON("example-jukebox:jukebox", stringInputStream("""
+            assertResponse(201, ar -> restconf.postDataJSON(JUKEBOX_API_PATH, stringInputStream("""
                 {
                   "example-jukebox:playlist" : {
                     "name" : "name of band",
index ecdc0314de5359158a2efa59d4425ade35bad4df..d03427ceef10f57fed59446ecb4c409db483a252 100644 (file)
@@ -58,15 +58,14 @@ class RestconfDataPutTest extends AbstractRestconfTest {
         doReturn(immediateTrueFluentFuture()).when(readTx).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
         doNothing().when(rwTx).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX);
 
-        assertNull(assertEntity(204, ar -> restconf.dataJsonPUT("example-jukebox:jukebox", uriInfo,
-            stringInputStream("""
-                {
-                  "example-jukebox:jukebox" : {
-                    "player": {
-                      "gap": "0.2"
-                    }
-                  }
-                }"""), ar)));
+        assertNull(assertEntity(204, ar -> restconf.dataJsonPUT(JUKEBOX_API_PATH, uriInfo, stringInputStream("""
+            {
+              "example-jukebox:jukebox" : {
+                "player": {
+                  "gap": "0.2"
+                }
+              }
+            }"""), ar)));
     }
 
     @Test
@@ -82,12 +81,13 @@ class RestconfDataPutTest extends AbstractRestconfTest {
         doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
 
         assertNull(assertEntity(204, ar -> restconf.dataXmlPUT(
-            "example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox", uriInfo, stringInputStream("""
-            <jukebox xmlns="http://example.com/ns/example-jukebox">
-              <player>
-                <gap>0.2</gap>
-              </player>
-            </jukebox>"""), ar)));
+            new JaxRsApiPath("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox"), uriInfo,
+            stringInputStream("""
+                <jukebox xmlns="http://example.com/ns/example-jukebox">
+                  <player>
+                    <gap>0.2</gap>
+                  </player>
+                </jukebox>"""), ar)));
     }
 
     @Test
@@ -101,7 +101,7 @@ class RestconfDataPutTest extends AbstractRestconfTest {
             .exists(eq(LogicalDatastoreType.CONFIGURATION), any(YangInstanceIdentifier.class));
 
         assertNull(assertEntity(201, ar -> restconf.dataJsonPUT(
-            "example-jukebox:jukebox/playlist=0/song=3", uriInfo, stringInputStream("""
+            new JaxRsApiPath("example-jukebox:jukebox/playlist=0/song=3"), uriInfo, stringInputStream("""
             {
               "example-jukebox:song" : [
                 {
@@ -125,7 +125,7 @@ class RestconfDataPutTest extends AbstractRestconfTest {
             .read(eq(LogicalDatastoreType.CONFIGURATION), any(YangInstanceIdentifier.class));
 
         assertNull(assertEntity(201, ar -> restconf.dataJsonPUT(
-            "example-jukebox:jukebox/playlist=0/song=3", uriInfo, stringInputStream("""
+            new JaxRsApiPath("example-jukebox:jukebox/playlist=0/song=3"), uriInfo, stringInputStream("""
             {
               "example-jukebox:song" : [
                 {
@@ -150,7 +150,7 @@ class RestconfDataPutTest extends AbstractRestconfTest {
             .read(eq(LogicalDatastoreType.CONFIGURATION), any(YangInstanceIdentifier.class));
 
         assertNull(assertEntity(201, ar -> restconf.dataJsonPUT(
-            "example-jukebox:jukebox/playlist=0/song=3", uriInfo, stringInputStream("""
+            new JaxRsApiPath("example-jukebox:jukebox/playlist=0/song=3"), uriInfo, stringInputStream("""
             {
               "example-jukebox:song" : [
                 {
@@ -175,7 +175,7 @@ class RestconfDataPutTest extends AbstractRestconfTest {
             .read(eq(LogicalDatastoreType.CONFIGURATION), any(YangInstanceIdentifier.class));
 
         assertNull(assertEntity(201, ar -> restconf.dataJsonPUT(
-            "example-jukebox:jukebox/playlist=0/song=3", uriInfo, stringInputStream("""
+            new JaxRsApiPath("example-jukebox:jukebox/playlist=0/song=3"), uriInfo, stringInputStream("""
             {
               "example-jukebox:song" : [
                 {
index f2141fb3d3590b2514c0c7106ff41e230a6cdf48..eec29af9ec8c5f115b1638f95cdb4c790e10a8c5 100644 (file)
@@ -25,9 +25,10 @@ import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 
 @ExtendWith(MockitoExtension.class)
 class RestconfOperationsGetTest extends AbstractRestconfTest {
-    private static final String DEVICE_ID = "network-topology:network-topology/topology=topology-netconf/"
-        + "node=device/yang-ext:mount";
-    private static final String DEVICE_RPC1_MODULE1_ID = DEVICE_ID + "module1:dummy-rpc1-module1";
+    private static final JaxRsApiPath DEVICE_ID =
+        new JaxRsApiPath("network-topology:network-topology/topology=topology-netconf/node=device/yang-ext:mount");
+    private static final JaxRsApiPath DEVICE_RPC1_MODULE1_ID = new JaxRsApiPath("network-topology:network-topology/"
+        + "topology=topology-netconf/node=device/yang-ext:mount/module1:dummy-rpc1-module1");
     private static final String EXPECTED_JSON = """
         {
           "ietf-restconf:operations" : {
index 3df7cd37c6d01583125cb7ea8d9c8cb7f4d8bc24..17c15a25dbd4fc496aa73e4ace7645cf2c2ac9be 100644 (file)
@@ -73,8 +73,8 @@ class RestconfOperationsPostTest extends AbstractRestconfTest {
         doReturn(false).when(result).isEmpty();
 
         prepNNC(result);
-        assertSame(result, assertNormalizedNode(200, ar -> restconf.operationsXmlPOST("invoke-rpc-module:rpc-test",
-            stringInputStream("""
+        assertSame(result, assertNormalizedNode(200, ar -> restconf.operationsXmlPOST(
+            new JaxRsApiPath("invoke-rpc-module:rpc-test"), stringInputStream("""
                 <input xmlns="invoke:rpc:module"/>"""), uriInfo, ar)));
     }
 
@@ -84,7 +84,7 @@ class RestconfOperationsPostTest extends AbstractRestconfTest {
         doReturn(true).when(result).isEmpty();
 
         prepNNC(result);
-        assertNull(assertEntity(204, ar -> restconf.operationsJsonPOST("invoke-rpc-module:rpc-test",
+        assertNull(assertEntity(204, ar -> restconf.operationsJsonPOST(new JaxRsApiPath("invoke-rpc-module:rpc-test"),
             stringInputStream("""
                 {
                   "invoke-rpc-module:input" : {
@@ -97,8 +97,8 @@ class RestconfOperationsPostTest extends AbstractRestconfTest {
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult(OUTPUT, List.of()))).when(rpcService)
             .invokeRpc(RPC, INPUT);
 
-        assertEquals(OUTPUT, assertNormalizedNode(200, ar -> restconf.operationsXmlPOST("invoke-rpc-module:rpc-test",
-            stringInputStream("""
+        assertEquals(OUTPUT, assertNormalizedNode(200, ar -> restconf.operationsXmlPOST(
+            new JaxRsApiPath("invoke-rpc-module:rpc-test"), stringInputStream("""
                 <input xmlns="invoke:rpc:module">
                   <cont>
                     <lf>test</lf>
@@ -112,7 +112,7 @@ class RestconfOperationsPostTest extends AbstractRestconfTest {
                 "No implementation of RPC " + RPC + " available.");
         doReturn(Futures.immediateFailedFuture(exception)).when(rpcService).invokeRpc(RPC, INPUT);
 
-        final var error = assertError(ar -> restconf.operationsJsonPOST("invoke-rpc-module:rpc-test",
+        final var error = assertError(ar -> restconf.operationsJsonPOST(new JaxRsApiPath("invoke-rpc-module:rpc-test"),
             stringInputStream("""
                 {
                   "invoke-rpc-module:input" : {
@@ -141,7 +141,7 @@ class RestconfOperationsPostTest extends AbstractRestconfTest {
 
         assertEquals(OUTPUT, assertNormalizedNode(200,
             ar -> restconf.operationsJsonPOST(
-                "ietf-yang-library:modules-state/yang-ext:mount/invoke-rpc-module:rpc-test",
+                new JaxRsApiPath("ietf-yang-library:modules-state/yang-ext:mount/invoke-rpc-module:rpc-test"),
             stringInputStream("""
                 {
                   "invoke-rpc-module:input" : {
@@ -164,7 +164,7 @@ class RestconfOperationsPostTest extends AbstractRestconfTest {
 
         final var error = assertError(
             ar -> restconf.operationsJsonPOST(
-                "ietf-yang-library:modules-state/yang-ext:mount/invoke-rpc-module:rpc-test",
+                new JaxRsApiPath("ietf-yang-library:modules-state/yang-ext:mount/invoke-rpc-module:rpc-test"),
                 stringInputStream("""
                     {
                       "invoke-rpc-module:input" : {
@@ -180,7 +180,8 @@ class RestconfOperationsPostTest extends AbstractRestconfTest {
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult(OUTPUT, List.of())))
             .when(rpcService).invokeRpc(RPC, INPUT);
 
-        assertEquals(OUTPUT, assertNormalizedNode(200, ar -> restconf.operationsJsonPOST("invoke-rpc-module:rpc-test",
+        assertEquals(OUTPUT, assertNormalizedNode(200, ar -> restconf.operationsJsonPOST(
+            new JaxRsApiPath("invoke-rpc-module:rpc-test"),
             stringInputStream("""
                 {
                   "invoke-rpc-module:input" : {
index 3bbd096f201b428407ed026512151537f386b712..54f1167a46c9e2a8af6b885001cc343dc7b0f9a4 100644 (file)
@@ -63,7 +63,17 @@ abstract class AbstractPatchBodyTest extends AbstractInstanceIdentifierTest {
         assertNotNull(patchContext.entities());
     }
 
-    final @NonNull PatchContext parse(final String uriPath, final String patchBody) throws IOException {
+    final @NonNull PatchContext parse(final String prefix, final String suffix, final String patchBody)
+            throws IOException {
+        final String uriPath;
+        if (prefix.isEmpty()) {
+            uriPath = suffix;
+        } else if (suffix.isEmpty()) {
+            uriPath = prefix;
+        } else {
+            uriPath = prefix + '/' + suffix;
+        }
+
         final var iid = ParserIdentifier.toInstanceIdentifier(uriPath, IID_SCHEMA, mountPointService);
 
         try (var body = bodyConstructor.apply(stringInputStream(patchBody))) {
index 12b96972b03f7a3e88baac36bf5a031816df96dc..0a3c4d468e389bf14f55e06484086c3357918669 100644 (file)
@@ -12,7 +12,7 @@ import org.opendaylight.mdsal.dom.api.DOMMountPoint;
 public class JsonPatchBodyMountPointTest extends JsonPatchBodyTest {
     @Override
     String mountPrefix() {
-        return "instance-identifier-module:cont/yang-ext:mount/";
+        return "instance-identifier-module:cont/yang-ext:mount";
     }
 
     @Override
index 192790793f769973c40b1aaf6fd58a832f98df89..a475a294a8db71728ebb96b03a11fe4bbfea5a3a 100644 (file)
@@ -26,7 +26,7 @@ public class JsonPatchBodyTest extends AbstractPatchBodyTest {
 
     @Test
     public final void modulePatchDataTest() throws Exception {
-        checkPatchContext(parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
+        checkPatchContext(parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
             {
               "ietf-yang-patch:yang-patch" : {
                 "patch-id" : "test-patch",
@@ -67,7 +67,7 @@ public class JsonPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void modulePatchCreateAndDeleteTest() throws Exception {
-        checkPatchContext(parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
+        checkPatchContext(parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
             {
               "ietf-yang-patch:yang-patch" : {
                 "patch-id" : "test-patch",
@@ -108,7 +108,7 @@ public class JsonPatchBodyTest extends AbstractPatchBodyTest {
     @Test
     public final void modulePatchValueMissingNegativeTest() throws Exception {
         final var ex = assertThrows(RestconfDocumentedException.class,
-            () -> parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
+            () -> parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
                 {
                   "ietf-yang-patch:yang-patch" : {
                     "patch-id" : "test-patch",
@@ -133,7 +133,7 @@ public class JsonPatchBodyTest extends AbstractPatchBodyTest {
     @Test
     public final void modulePatchValueNotSupportedNegativeTest() throws Exception {
         final var ex = assertThrows(RestconfDocumentedException.class,
-            () -> parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
+            () -> parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
                 {
                   "ietf-yang-patch:yang-patch" : {
                     "patch-id" : "test-patch",
@@ -163,7 +163,7 @@ public class JsonPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void modulePatchCompleteTargetInURITest() throws Exception {
-        checkPatchContext(parse(mountPrefix() + "instance-identifier-patch-module:patch-cont", """
+        checkPatchContext(parse(mountPrefix(), "instance-identifier-patch-module:patch-cont", """
             {
               "ietf-yang-patch:yang-patch" : {
                 "patch-id" : "test-patch",
@@ -214,7 +214,7 @@ public class JsonPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void modulePatchMergeOperationOnListTest() throws Exception {
-        checkPatchContext(parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
+        checkPatchContext(parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
             {
               "ietf-yang-patch:yang-patch" : {
                 "patch-id" : "Test merge operation",
@@ -254,7 +254,7 @@ public class JsonPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void modulePatchMergeOperationOnContainerTest() throws Exception {
-        checkPatchContext(parse(mountPrefix() + "instance-identifier-patch-module:patch-cont", """
+        checkPatchContext(parse(mountPrefix(), "instance-identifier-patch-module:patch-cont", """
             {
               "ietf-yang-patch:yang-patch" : {
                 "patch-id" : "Test merge operation",
@@ -305,7 +305,7 @@ public class JsonPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void modulePatchSimpleLeafValueTest() throws Exception {
-        final var returnValue = parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
+        final var returnValue = parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
             {
               "ietf-yang-patch:yang-patch" : {
                 "patch-id" : "test-patch",
@@ -331,7 +331,7 @@ public class JsonPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void modulePatchTargetTopLevelContainerWithEmptyURITest() throws Exception {
-        checkPatchContext(parse(mountPrefix(), """
+        checkPatchContext(parse(mountPrefix(), "", """
             {
               "ietf-yang-patch:yang-patch" : {
                 "patch-id" : "test-patch",
@@ -361,7 +361,7 @@ public class JsonPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void modulePatchTargetTopLevelContainerWithFullPathURITest() throws Exception {
-        final var returnValue = parse(mountPrefix() + "instance-identifier-patch-module:patch-cont", """
+        final var returnValue = parse(mountPrefix(), "instance-identifier-patch-module:patch-cont", """
             {
               "ietf-yang-patch:yang-patch": {
                 "patch-id": "test-patch",
@@ -406,8 +406,8 @@ public class JsonPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void modulePatchTargetSecondLevelListWithFullPathURITest() throws Exception {
-        final var returnValue = parse(
-            mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=my-leaf-set", """
+        final var returnValue = parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=my-leaf-set",
+            """
             {
               "ietf-yang-patch:yang-patch": {
                 "patch-id": "test-patch",
@@ -448,7 +448,7 @@ public class JsonPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void modulePatchTargetTopLevelAugmentedContainerTest() throws Exception {
-        final var returnValue = parse(mountPrefix(), """
+        final var returnValue = parse(mountPrefix(), "", """
             {
                 "ietf-yang-patch:yang-patch": {
                     "patch-id": "test-patch",
@@ -479,7 +479,7 @@ public class JsonPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void modulePatchTargetMapNodeTest() throws Exception {
-        final var returnValue = parse(mountPrefix(), """
+        final var returnValue = parse(mountPrefix(), "", """
             {
                 "ietf-yang-patch:yang-patch": {
                     "patch-id": "map-patch",
@@ -515,7 +515,7 @@ public class JsonPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void modulePatchTargetLeafSetNodeTest() throws Exception {
-        final var returnValue = parse(mountPrefix(), """
+        final var returnValue = parse(mountPrefix(), "", """
             {
                 "ietf-yang-patch:yang-patch": {
                     "patch-id": "set-patch",
@@ -547,7 +547,7 @@ public class JsonPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void modulePatchTargetUnkeyedListNodeTest() throws Exception {
-        final var returnValue = parse(mountPrefix(), """
+        final var returnValue = parse(mountPrefix(), "", """
             {
                 "ietf-yang-patch:yang-patch": {
                     "patch-id": "list-patch",
@@ -583,7 +583,7 @@ public class JsonPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void modulePatchTargetCaseNodeTest() throws Exception {
-        final var returnValue = parse(mountPrefix(), """
+        final var returnValue = parse(mountPrefix(), "", """
             {
                 "ietf-yang-patch:yang-patch": {
                     "patch-id": "choice-patch",
index e4ae381b4f0c620160e6c1130604c9dc796838b0..a0aa7f1e13dbaa03703a62f1720cbc2e7cf6b074 100644 (file)
@@ -12,7 +12,7 @@ import org.opendaylight.mdsal.dom.api.DOMMountPoint;
 public class XmlPatchBodyMountPointTest extends XmlPatchBodyTest {
     @Override
     String mountPrefix() {
-        return "instance-identifier-module:cont/yang-ext:mount/";
+        return "instance-identifier-module:cont/yang-ext:mount";
     }
 
     @Override
index ab96d76440a29cda40fa9eb03f22837358947d49..d0737c807c80df54a09147d8b1d8278b7c0fa143 100644 (file)
@@ -26,7 +26,7 @@ public class XmlPatchBodyTest extends AbstractPatchBodyTest {
 
     @Test
     public final void moduleDataTest() throws Exception {
-        checkPatchContext(parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
+        checkPatchContext(parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
                 <patch-id>test-patch</patch-id>
                 <comment>this is test patch</comment>
@@ -63,7 +63,7 @@ public class XmlPatchBodyTest extends AbstractPatchBodyTest {
     @Test
     public final void moduleDataValueMissingNegativeTest() throws Exception {
         final var ex = assertThrows(RestconfDocumentedException.class,
-            () -> parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
+            () -> parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
                 <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
                     <patch-id>test-patch</patch-id>
                     <comment>Test patch with missing value node for create operation</comment>
@@ -83,7 +83,7 @@ public class XmlPatchBodyTest extends AbstractPatchBodyTest {
     @Test
     public final void moduleDataNotValueNotSupportedNegativeTest() throws Exception {
         final var ex = assertThrows(RestconfDocumentedException.class,
-            () -> parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
+            () -> parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
                 <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
                     <patch-id>test-patch</patch-id>
                     <comment>Test patch with not allowed value node for delete operation</comment>
@@ -108,7 +108,7 @@ public class XmlPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void moduleDataAbsoluteTargetPathTest() throws Exception {
-        checkPatchContext(parse(mountPrefix(), """
+        checkPatchContext(parse(mountPrefix(), "", """
             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
                 <patch-id>test-patch</patch-id>
                 <comment>Test patch with absolute target path</comment>
@@ -144,7 +144,7 @@ public class XmlPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void modulePatchCompleteTargetInURITest() throws Exception {
-        checkPatchContext(parse(mountPrefix() + "instance-identifier-patch-module:patch-cont", """
+        checkPatchContext(parse(mountPrefix(), "instance-identifier-patch-module:patch-cont", """
             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
                 <patch-id>test-patch</patch-id>
                 <comment>Test to create and replace data in container directly using / sign as a target</comment>
@@ -189,7 +189,7 @@ public class XmlPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void moduleDataMergeOperationOnListTest() throws Exception {
-        checkPatchContext(parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
+        checkPatchContext(parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
                 <patch-id>Test merge operation</patch-id>
                 <comment>This is test patch for merge operation on list</comment>
@@ -225,7 +225,7 @@ public class XmlPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void moduleDataMergeOperationOnContainerTest() throws Exception {
-        checkPatchContext(parse(mountPrefix() + "instance-identifier-patch-module:patch-cont", """
+        checkPatchContext(parse(mountPrefix(), "instance-identifier-patch-module:patch-cont", """
             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
                 <patch-id>Test merge operation</patch-id>
                 <comment>This is test patch for merge operation on container</comment>
@@ -270,7 +270,7 @@ public class XmlPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void modulePatchTargetTopLevelContainerWithEmptyURITest() throws Exception {
-        checkPatchContext(parse(mountPrefix(), """
+        checkPatchContext(parse(mountPrefix(), "", """
             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
                 <patch-id>test-patch</patch-id>
                 <comment>Test patch applied to the top-level container with empty URI</comment>
@@ -294,7 +294,7 @@ public class XmlPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void modulePatchTargetTopLevelContainerWithFullPathURITest() throws Exception {
-        final var returnValue = parse(mountPrefix() + "instance-identifier-patch-module:patch-cont", """
+        final var returnValue = parse(mountPrefix(), "instance-identifier-patch-module:patch-cont", """
             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
                 <patch-id>test-patch</patch-id>
                 <comment>Test patch applied to the top-level container with '/' in target</comment>
@@ -333,8 +333,7 @@ public class XmlPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void modulePatchTargetSecondLevelListWithFullPathURITest() throws Exception {
-        final var returnValue = parse(
-            mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=my-leaf-set",
+        final var returnValue = parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=my-leaf-set",
             """
                 <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
                     <patch-id>test-patch</patch-id>
@@ -369,7 +368,7 @@ public class XmlPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void moduleTargetTopLevelAugmentedContainerTest() throws Exception {
-        final var returnValue = parse(mountPrefix(), """
+        final var returnValue = parse(mountPrefix(), "", """
             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
                 <patch-id>test-patch</patch-id>
                 <comment>This test patch for augmented element</comment>
@@ -396,7 +395,7 @@ public class XmlPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void moduleTargetMapNodeTest() throws Exception {
-        final var returnValue = parse(mountPrefix(), """
+        final var returnValue = parse(mountPrefix(), "", """
             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
                 <patch-id>map-patch</patch-id>
                 <comment>YANG patch comment</comment>
@@ -428,7 +427,7 @@ public class XmlPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void modulePatchTargetLeafSetNodeTest() throws Exception {
-        final var returnValue = parse(mountPrefix(), """
+        final var returnValue = parse(mountPrefix(), "", """
             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
                 <patch-id>set-patch</patch-id>
                 <comment>YANG patch comment</comment>
@@ -456,7 +455,7 @@ public class XmlPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void moduleTargetUnkeyedListNodeTest() throws Exception {
-        final var returnValue = parse(mountPrefix(), """
+        final var returnValue = parse(mountPrefix(), "", """
             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
                 <patch-id>list-patch</patch-id>
                 <comment>YANG patch comment</comment>
@@ -488,7 +487,7 @@ public class XmlPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void moduleTargetCaseNodeTest() throws Exception {
-        final var returnValue = parse(mountPrefix(), """
+        final var returnValue = parse(mountPrefix(), "", """
             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
                 <patch-id>choice-patch</patch-id>
                 <comment>YANG patch comment</comment>
@@ -515,7 +514,7 @@ public class XmlPatchBodyTest extends AbstractPatchBodyTest {
      */
     @Test
     public final void modulePatchSimpleLeafValueTest() throws Exception {
-        final var returnValue = parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
+        final var returnValue = parse(mountPrefix(), "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
             <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
                 <patch-id>test-patch</patch-id>
                 <comment>this is test patch</comment>
index 6a6b8ef20f06d3a82d5cdd6c4fa59ebe0d32b60c..103350152eacfa04c894b1160db21f7f4ead48a3 100644 (file)
@@ -23,6 +23,7 @@ import org.opendaylight.mdsal.dom.api.DOMYangTextSourceProvider;
 import org.opendaylight.mdsal.dom.broker.DOMMountPointServiceImpl;
 import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags;
 import org.opendaylight.restconf.nb.rfc8040.rests.services.api.RestconfSchemaService;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
@@ -201,9 +202,15 @@ public class RestconfSchemaServiceTest {
         when(mockSchemaService.getGlobalContext()).thenReturn(SCHEMA_CONTEXT_WITH_MOUNT_POINTS);
 
         // make test - call service on mount point with null schema context
-        assertThrows(IllegalStateException.class,
+        final var errors = assertThrows(RestconfDocumentedException.class,
             // NULL_MOUNT_POINT contains null schema context
-            () -> schemaService.getSchema(NULL_MOUNT_POINT + TEST_MODULE_BEHIND_MOUNT_POINT));
+            () -> schemaService.getSchema(NULL_MOUNT_POINT + TEST_MODULE_BEHIND_MOUNT_POINT))
+            .getErrors();
+        assertEquals(1, errors.size());
+        final var error = errors.get(0);
+        assertEquals("Mount point mount-point-2:cont does not expose DOMSchemaService", error.getErrorMessage());
+        assertEquals(ErrorType.PROTOCOL, error.getErrorType());
+        assertEquals(ErrorTags.RESOURCE_DENIED_TRANSPORT, error.getErrorTag());
     }
 
     /**
index 242c46bdfcb3b2d8ad869c6eea9b9d95d76d5855..22e87bc914ab6486117e3cbb6837fdc4874fb0e2 100644 (file)
@@ -10,6 +10,7 @@ package org.opendaylight.restconf.nb.rfc8040.utils.parser;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import org.junit.jupiter.api.Test;
+import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
@@ -27,9 +28,9 @@ class IdentifierCodecTest {
      * URI contains list identifier and leaf identifier.
      */
     @Test
-    void codecListAndLeafTest() {
-        final var dataYangII = IdentifierCodec.deserialize(
-            "list-test:top/list1=%2C%27\"%3A\"%20%2F,,foo/list2=a,b/result", SCHEMA_CONTEXT);
+    void codecListAndLeafTest() throws Exception {
+        final var dataYangII = IdentifierCodec.deserialize(ApiPath.parse(
+            "list-test:top/list1=%2C%27\"%3A\"%20%2F,,foo/list2=a,b/result"), SCHEMA_CONTEXT);
         assertEquals("list-test:top/list1=%2C%27\"%3A\" %2F,,foo/list2=a,b/result",
             IdentifierCodec.serialize(dataYangII, SCHEMA_CONTEXT));
     }
@@ -40,9 +41,9 @@ class IdentifierCodecTest {
      * URI contains leaf list identifier.
      */
     @Test
-    void codecLeafListTest() {
+    void codecLeafListTest() throws Exception {
         final var str = "list-test:top/Y=4";
-        final var dataYangII = IdentifierCodec.deserialize(str, SCHEMA_CONTEXT);
+        final var dataYangII = IdentifierCodec.deserialize(ApiPath.parse(str), SCHEMA_CONTEXT);
         assertEquals(str, IdentifierCodec.serialize(dataYangII, SCHEMA_CONTEXT));
     }
 
@@ -67,12 +68,13 @@ class IdentifierCodecTest {
     }
 
     /**
-     * Positive test of serialization <code>YangInstanceIdentifier.EMPTY</code> and deserialization of result back to
-     * <code>YangInstanceIdentifier.EMPTY</code>.
+     * Positive test of serialization {@link YangInstanceIdentifier#EMPTY} and deserialization of result back to
+     * {@link YangInstanceIdentifier#EMPTY}.
      */
     @Test
-    void codecDeserializeAndSerializeEmptyTest() {
+    void codecDeserializeAndSerializeEmptyTest() throws Exception {
         final var serialized = IdentifierCodec.serialize(YangInstanceIdentifier.of(), SCHEMA_CONTEXT);
-        assertEquals(YangInstanceIdentifier.of(), IdentifierCodec.deserialize(serialized, SCHEMA_CONTEXT));
+        assertEquals(YangInstanceIdentifier.of(), IdentifierCodec.deserialize(ApiPath.parse(serialized),
+            SCHEMA_CONTEXT));
     }
 }
index 88640fb50e940a712fa731f284af7ba9f93529cb..15cc983d18c21f7f6580fe81a85cf105ed3e4c52 100644 (file)
@@ -12,9 +12,7 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertThrows;
-import static org.mockito.Mockito.when;
 
-import java.util.Optional;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -26,6 +24,7 @@ import org.opendaylight.mdsal.dom.api.DOMSchemaService;
 import org.opendaylight.mdsal.dom.api.DOMYangTextSourceProvider;
 import org.opendaylight.mdsal.dom.broker.DOMMountPointServiceImpl;
 import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
+import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
@@ -85,8 +84,8 @@ public class ParserIdentifierTest {
         YangParserTestUtils.parseYangResourceDirectory("/parser-identifier");
 
     // mount point and mount point service
+    private final DOMMountPointService mountPointService = new DOMMountPointServiceImpl();
     private DOMMountPoint mountPoint;
-    private DOMMountPointService mountPointService;
 
     // mock mount point and mount point service
     @Mock
@@ -100,8 +99,6 @@ public class ParserIdentifierTest {
 
     @Before
     public void setup() throws Exception {
-        mountPointService = new DOMMountPointServiceImpl();
-
         // create and register mount point
         final var mountPointId = YangInstanceIdentifier.builder()
                 .node(QName.create("mount:point", "2016-06-02", "mount-container"))
@@ -112,10 +109,6 @@ public class ParserIdentifierTest {
                 .addService(DOMSchemaService.class, FixedDOMSchemaService.of(MODEL_CONTEXT_ON_MOUNT_POINT))
                 .register()
                 .getInstance();
-
-        // register mount point with null schema context
-        when(mockMountPointService.getMountPoint(YangInstanceIdentifier.of()))
-                .thenReturn(Optional.of(mockMountPoint));
     }
 
     /**
@@ -154,16 +147,6 @@ public class ParserIdentifierTest {
         assertEquals(MODEL_CONTEXT_ON_MOUNT_POINT, context.getSchemaContext());
     }
 
-    /**
-     * Test of creating <code>InstanceIdentifierContext</code> when identifier is <code>null</code>.
-     * <code>{@link YangInstanceIdentifier#empty()}</code> should be returned.
-     */
-    @Test
-    public void toInstanceIdentifierNullIdentifierTest() {
-        final var context = ParserIdentifier.toInstanceIdentifier(null, MODEL_CONTEXT, null);
-        assertEquals(YangInstanceIdentifier.of(), context.getInstanceIdentifier());
-    }
-
     /**
      * Negative test of creating <code>InstanceIdentifierContext</code> when <code>SchemaContext</code> is
      * <code>null</code>. Test fails expecting <code>NullPointerException</code>.
@@ -221,11 +204,11 @@ public class ParserIdentifierTest {
     @Test
     public void toInstanceIdentifierMissingMountPointNegativeTest() {
         final var ex = assertThrows(RestconfDocumentedException.class,
-            () -> ParserIdentifier.toInstanceIdentifier("/yang-ext:mount", MODEL_CONTEXT, mountPointService));
+            () -> ParserIdentifier.toInstanceIdentifier("yang-ext:mount", MODEL_CONTEXT, mountPointService));
         final var errors = ex.getErrors();
         assertEquals(1, errors.size());
         final var error = errors.get(0);
-        assertEquals("Mount point does not exist.", error.getErrorMessage());
+        assertEquals("Mount point '' does not exist", error.getErrorMessage());
         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
         assertEquals(ErrorTags.RESOURCE_DENIED_TRANSPORT, error.getErrorTag());
     }
@@ -406,19 +389,24 @@ public class ParserIdentifierTest {
 
     @Test
     public void toSchemaExportContextFromIdentifierNullSchemaContextBehindMountPointNegativeTest() {
-        assertThrows(IllegalStateException.class, () -> ParserIdentifier.toSchemaExportContextFromIdentifier(
-                MODEL_CONTEXT, "/yang-ext:mount/" + TEST_MODULE_NAME + "/" + TEST_MODULE_REVISION,
-                mockMountPointService, sourceProvider));
+        final var ex = assertThrows(RestconfDocumentedException.class,
+            () -> ParserIdentifier.toSchemaExportContextFromIdentifier(MODEL_CONTEXT,
+                "/yang-ext:mount/" + TEST_MODULE_NAME + "/" + TEST_MODULE_REVISION, mockMountPointService,
+                sourceProvider));
+        final var errors = ex.getErrors();
+        assertEquals(1, errors.size());
+        final var error = errors.get(0);
+        // FIXME: this should be something different
+        assertEquals("Identifier may not be empty", error.getErrorMessage());
+        assertEquals(ErrorType.PROTOCOL, error.getErrorType());
+        assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
     }
 
     /**
-     * Test invoke RPC.
-     *
-     * <p>
-     * Verify if RPC schema node was found.
+     * Test invoke RPC. Verify if RPC schema node was found.
      */
     @Test
-    public void invokeRpcTest() {
+    public void invokeRpcTest() throws Exception {
         final var result = ParserIdentifier.toInstanceIdentifier(INVOKE_RPC, MODEL_CONTEXT, null);
 
         // RPC schema node
@@ -427,58 +415,56 @@ public class ParserIdentifierTest {
         assertEquals("rpc-test", rpcQName.getLocalName());
 
         // other fields
-        assertEquals(IdentifierCodec.deserialize(INVOKE_RPC, MODEL_CONTEXT), result.getInstanceIdentifier());
+        assertEquals(IdentifierCodec.deserialize(ApiPath.parse(INVOKE_RPC), MODEL_CONTEXT),
+            result.getInstanceIdentifier());
         assertEquals(null, result.getMountPoint());
         assertEquals(MODEL_CONTEXT, result.getSchemaContext());
     }
 
     /**
-     * Test invoke RPC on mount point.
-     *
-     * <p>
-     * Verify if RPC schema node was found.
+     * Test invoke RPC on mount point. Verify if RPC schema node was found.
      */
     @Test
-    public void invokeRpcOnMountPointTest() {
+    public void invokeRpcOnMountPointTest() throws Exception {
         final var result = ParserIdentifier.toInstanceIdentifier(MOUNT_POINT_IDENT + "/" + INVOKE_RPC, MODEL_CONTEXT,
             mountPointService);
 
         // RPC schema node
-        final QName rpcQName = result.getSchemaNode().getQName();
+        final var rpcQName = result.getSchemaNode().getQName();
         assertEquals("invoke:rpc:module", rpcQName.getModule().getNamespace().toString());
         assertEquals("rpc-test", rpcQName.getLocalName());
 
         // other fields
-        assertEquals(IdentifierCodec.deserialize(INVOKE_RPC, MODEL_CONTEXT), result.getInstanceIdentifier());
+        assertEquals(IdentifierCodec.deserialize(ApiPath.parse(INVOKE_RPC), MODEL_CONTEXT),
+            result.getInstanceIdentifier());
         assertEquals(mountPoint, result.getMountPoint());
         assertEquals(MODEL_CONTEXT_ON_MOUNT_POINT, result.getSchemaContext());
     }
 
     /**
-     * Test Action.
-     * Verify if Action schema node was found.
+     * Test Action. Verify if Action schema node was found.
      */
     @Test
-    public void invokeActionTest() {
+    public void invokeActionTest() throws Exception {
         final var result = ParserIdentifier.toInstanceIdentifier(INVOKE_ACTION, MODEL_CONTEXT, null);
 
         // Action schema node
-        final QName actionQName = result.getSchemaNode().getQName();
+        final var actionQName = result.getSchemaNode().getQName();
         assertEquals("https://example.com/ns/example-actions", actionQName.getModule().getNamespace().toString());
         assertEquals("reset", actionQName.getLocalName());
 
         // other fields
-        assertEquals(IdentifierCodec.deserialize(INVOKE_ACTION, MODEL_CONTEXT), result.getInstanceIdentifier());
+        assertEquals(IdentifierCodec.deserialize(ApiPath.parse(INVOKE_ACTION), MODEL_CONTEXT),
+            result.getInstanceIdentifier());
         assertNull(result.getMountPoint());
         assertSame(MODEL_CONTEXT, result.getSchemaContext());
     }
 
     /**
-     * Test invoke Action on mount point.
-     * Verify if Action schema node was found.
+     * Test invoke Action on mount point. Verify if Action schema node was found.
      */
     @Test
-    public void invokeActionOnMountPointTest() {
+    public void invokeActionOnMountPointTest() throws Exception {
         final var result = ParserIdentifier.toInstanceIdentifier(MOUNT_POINT_IDENT + "/" + INVOKE_ACTION,
             MODEL_CONTEXT, mountPointService);
 
@@ -488,7 +474,8 @@ public class ParserIdentifierTest {
         assertEquals("reset", actionQName.getLocalName());
 
         // other fields
-        assertEquals(IdentifierCodec.deserialize(INVOKE_ACTION, MODEL_CONTEXT), result.getInstanceIdentifier());
+        assertEquals(IdentifierCodec.deserialize(ApiPath.parse(INVOKE_ACTION), MODEL_CONTEXT),
+            result.getInstanceIdentifier());
         assertEquals(mountPoint, result.getMountPoint());
         assertEquals(MODEL_CONTEXT_ON_MOUNT_POINT, result.getSchemaContext());
     }