Refactor modulesGET() methods 63/109063/6
authorRobert Varga <robert.varga@pantheon.tech>
Sun, 26 Nov 2023 08:34:23 +0000 (09:34 +0100)
committerRobert Varga <nite@hq.sk>
Sun, 26 Nov 2023 11:34:22 +0000 (11:34 +0000)
Make sure mountPath is passed to RestconfServer as ApiPath and make
source revision passed as a query parameter. This ends up
eliminating one reference to ParserIdentifier.

JIRA: NETCONF-1157
Change-Id: I4de5293126d3dc0c68b8ee132e5bfa56a1264008
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsRestconf.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/URLConstants.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/rests/services/impl/RestconfSchemaSourceUrlProvider.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/ModulesGetResult.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/RestconfModulesGetTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfSchemaServiceTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfSchemaSourceUrlProviderTest.java

index 166b578be8203a92d3e88279a698b6c4a60c6762..7b3ae2b4ebe59b7fecc206a3103e2551debfd402 100644 (file)
@@ -31,6 +31,7 @@ import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
 import javax.ws.rs.container.AsyncResponse;
 import javax.ws.rs.container.Suspended;
 import javax.ws.rs.core.Context;
@@ -49,6 +50,7 @@ import org.opendaylight.restconf.common.errors.RestconfError;
 import org.opendaylight.restconf.common.errors.RestconfFuture;
 import org.opendaylight.restconf.common.patch.PatchStatusContext;
 import org.opendaylight.restconf.nb.rfc8040.ReadDataParams;
+import org.opendaylight.restconf.nb.rfc8040.URLConstants;
 import org.opendaylight.restconf.nb.rfc8040.databind.JsonChildBody;
 import org.opendaylight.restconf.nb.rfc8040.databind.JsonDataPostBody;
 import org.opendaylight.restconf.nb.rfc8040.databind.JsonOperationInputBody;
@@ -798,27 +800,65 @@ public final class JaxRsRestconf implements ParamConverterProvider {
     /**
      * Get schema of specific module.
      *
-     * @param identifier path parameter
+     * @param fileName source file name
+     * @param revision source revision
      * @param ar {@link AsyncResponse} which needs to be completed
      */
     @GET
     @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
-    @Path("/modules/{identifier:.+}")
-    public void modulesYangGET(@PathParam("identifier") final String identifier, @Suspended final AsyncResponse ar) {
-        completeModulesGET(server.modulesYangGET(identifier), ar);
+    @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
+    public void modulesYangGET(@PathParam("fileName") final String fileName,
+            @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
+        completeModulesGET(server.modulesYangGET(fileName, revision), ar);
     }
 
     /**
      * Get schema of specific module.
      *
-     * @param identifier path parameter
+     * @param mountPath mount point path
+     * @param fileName source file name
+     * @param revision source revision
+     * @param ar {@link AsyncResponse} which needs to be completed
+     */
+    @GET
+    @Produces(YangConstants.RFC6020_YANG_MEDIA_TYPE)
+    @Path("/" + URLConstants.MODULES_SUBPATH + "/{mountPath:.+}/{fileName : [^/]+}")
+    public void modulesYangGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
+            @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
+            @Suspended final AsyncResponse ar) {
+        completeModulesGET(server.modulesYangGET(mountPath, fileName, revision), ar);
+    }
+
+    /**
+     * Get schema of specific module.
+     *
+     * @param fileName source file name
+     * @param revision source revision
      * @param ar {@link AsyncResponse} which needs to be completed
      */
     @GET
     @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
-    @Path("/modules/{identifier:.+}")
-    public void modulesYinGET(@PathParam("identifier") final String identifier, @Suspended final AsyncResponse ar) {
-        completeModulesGET(server.modulesYinGET(identifier), ar);
+    @Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
+    public void modulesYinGET(@PathParam("fileName") final String fileName,
+            @QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
+        completeModulesGET(server.modulesYinGET(fileName, revision), ar);
+    }
+
+    /**
+     * Get schema of specific module.
+     *
+     * @param mountPath mount point path
+     * @param fileName source file name
+     * @param revision source revision
+     * @param ar {@link AsyncResponse} which needs to be completed
+     */
+    @GET
+    @Produces(YangConstants.RFC6020_YIN_MEDIA_TYPE)
+    @Path("/" + URLConstants.MODULES_SUBPATH + "/{mountPath:.+}/{fileName : [^/]+}")
+    public void modulesYinGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
+            @PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
+            @Suspended final AsyncResponse ar) {
+        completeModulesGET(server.modulesYinGET(mountPath, fileName, revision), ar);
     }
 
     private static void completeModulesGET(final RestconfFuture<ModulesGetResult> future, final AsyncResponse ar) {
index af728f1d25093d93255b19edd6cf85ac2a1d8a22..cfe46d8320aba3a8e2e9780c8fd366adae3cd1e2 100644 (file)
@@ -19,9 +19,18 @@ public final class URLConstants {
      */
     public static final String BASE_PATH = "rests";
     /**
-     * The second URL path element for streams supper, i.e. {@code https://localhost/BASE_PATH/STREAMS}.
+     * The second URL path element for streams support, i.e. {@code https://localhost/BASE_PATH/STREAMS}.
      */
     public static final String STREAMS_SUBPATH = "streams";
+    /**
+     * The second URL path element for YANG library module support, i.e. {@code https://localhost/BASE_PATH/MODULES}.
+     */
+    public static final String MODULES_SUBPATH = "modules";
+    /**
+     * The query parameter carrying the optional revision in YANG library module support, i.e.
+     * {@code https://localhost/BASE_PATH/MODULES?REVISION=2023-11-26}.
+     */
+    public static final String MODULES_REVISION_QUERY = "revision";
 
     private URLConstants() {
         // Hidden on purpose
index 07f5d798c5c993e44c8a64ad2e4ef9faeefadc10..ea61a33f74c4ec1d9ad9e3884a3cc391cfe5294b 100644 (file)
@@ -141,9 +141,13 @@ public abstract class InstanceIdentifierContext {
                     ErrorType.PROTOCOL, ErrorTags.RESOURCE_DENIED_TRANSPORT));
             final var nextModelContext = nextMountPoint.getService(DOMSchemaService.class)
                 .orElseThrow(() -> new RestconfDocumentedException(
-                    "Mount point " + userPath + " does not expose DOMSchemaService",
+                    "Mount point '" + userPath + "' does not expose DOMSchemaService",
                     ErrorType.PROTOCOL, ErrorTags.RESOURCE_DENIED_TRANSPORT))
                 .getGlobalContext();
+            if (nextModelContext == null) {
+                throw new RestconfDocumentedException("Mount point '" + userPath + "' does not have any models",
+                    ErrorType.PROTOCOL, ErrorTags.RESOURCE_DENIED_TRANSPORT);
+            }
 
             prefix = mount + 1;
             currentModelContext = nextModelContext;
index 2693891fde2e3be612c609f26feb9f5db8b15e9c..4ba88d0c921258a7243cd3229e1192fb8e0b7b4d 100644 (file)
@@ -24,7 +24,7 @@ import org.osgi.service.component.annotations.Component;
  * <p>The URL is expected to be requested by {@link org.opendaylight.netconf.yanglib.writer.YangLibraryWriter
  * YangLibraryWriter} when yang-library data is being constructed, only default module-set name ("ODL_modules")
  * is supported. The composed URL for resource download expected to be served by
- * {@link JaxRsRestconf#modulesYangGET(String, javax.ws.rs.container.AsyncResponse)} et al.
+ * {@link JaxRsRestconf#modulesYangGET(String, String, javax.ws.rs.container.AsyncResponse)} et al.
  */
 @Singleton
 @Component(immediate = true, service = YangLibrarySchemaSourceUrlProvider.class)
@@ -34,10 +34,10 @@ public final class RestconfSchemaSourceUrlProvider implements YangLibrarySchemaS
     public Optional<Uri> getSchemaSourceUrl(final @NonNull String moduleSetName,
             final @NonNull String moduleName, final @Nullable Revision revision) {
         if ("ODL_modules".equals(moduleSetName)) {
-            // "/modules/.*" path is mapped to RestconfSchemaService#getSchema(String) using JaxRS annotation
-            final var sb = new StringBuilder("/" + URLConstants.BASE_PATH + "/modules/").append(moduleName);
+            final var sb = new StringBuilder("/" + URLConstants.BASE_PATH + "/" + URLConstants.MODULES_SUBPATH + "/")
+                .append(moduleName);
             if (revision != null) {
-                sb.append("/").append(revision);
+                sb.append("?" + URLConstants.MODULES_REVISION_QUERY + "=").append(revision);
             }
             return Optional.of(new Uri(sb.toString()));
         }
index a20efbea3d86f6c5da9e91746d8395b7e7bf3bd5..941b442e5692998a460250e542cf33f3f384eca4 100644 (file)
@@ -13,7 +13,7 @@ import com.google.common.io.CharSource;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 
 /**
- * Result of an {@link RestconfServer#modulesYangGET(String)} invocation.
+ * Result of an {@link RestconfServer#modulesYangGET(String, String)} invocation.
  *
  * @param source A {@link CharSource} containing the body
  */
index b47c48480a64eb57eee5b108c110101aafe6ae25..827c3be02b1292d20775532ee31cb6f31155aafc 100644 (file)
@@ -10,6 +10,7 @@ package org.opendaylight.restconf.server.api;
 import java.net.URI;
 import java.util.Map;
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.restconf.common.errors.RestconfFuture;
 import org.opendaylight.restconf.common.patch.PatchStatusContext;
@@ -166,7 +167,11 @@ public interface RestconfServer {
     //        construct for both cases -- in this case it carries a yang.common.Revision
     RestconfFuture<NormalizedNodePayload> yangLibraryVersionGET();
 
-    RestconfFuture<ModulesGetResult> modulesYangGET(String identifier);
+    RestconfFuture<ModulesGetResult> modulesYangGET(String fileName, @Nullable String revision);
 
-    RestconfFuture<ModulesGetResult> modulesYinGET(String identifier);
+    RestconfFuture<ModulesGetResult> modulesYangGET(ApiPath mountPath, String fileName, @Nullable String revision);
+
+    RestconfFuture<ModulesGetResult> modulesYinGET(String fileName, @Nullable String revision);
+
+    RestconfFuture<ModulesGetResult> modulesYinGET(ApiPath mountPath, String fileName, @Nullable String revision);
 }
index 616ce5cba4110e0d95a24003fad3fee28f4b7e52..4c67254fc4cc6b969a520122185a207166c6edf8 100644 (file)
@@ -14,7 +14,6 @@ import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSetMultimap;
-import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
@@ -68,7 +67,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;
@@ -403,69 +401,64 @@ public final class MdsalRestconfServer implements RestconfServer {
     }
 
     @Override
-    public RestconfFuture<ModulesGetResult> modulesYangGET(final String identifier) {
-        return modulesGET(identifier, YangTextSchemaSource.class);
+    public RestconfFuture<ModulesGetResult> modulesYangGET(final String fileName, final String revision) {
+        return modulesGET(fileName, revision, YangTextSchemaSource.class);
     }
 
     @Override
-    public RestconfFuture<ModulesGetResult> modulesYinGET(final String identifier) {
-        return modulesGET(identifier, YinTextSchemaSource.class);
+    public RestconfFuture<ModulesGetResult> modulesYangGET(final ApiPath mountPath, final String fileName,
+            final String revision) {
+        return modulesGET(mountPath, fileName, revision, YangTextSchemaSource.class);
     }
 
-    private @NonNull RestconfFuture<ModulesGetResult> modulesGET(final String identifier,
-            final Class<? extends SchemaSourceRepresentation> representation) {
-        final var currentContext = databindProvider.currentContext();
-        final var pathComponents = SLASH_SPLITTER.split(identifier);
-        final var it =  pathComponents.iterator();
-        final DatabindContext databind;
-        final Object debugName;
-        if (Iterables.contains(pathComponents, MOUNT)) {
-            final var sb = new StringBuilder();
-            while (true) {
-                final var current = it.next();
-                sb.append(current);
-                if (MOUNT.equals(current) || !it.hasNext()) {
-                    break;
-                }
+    @Override
+    public RestconfFuture<ModulesGetResult> modulesYinGET(final String fileName, final String revision) {
+        return modulesGET(fileName, revision, YinTextSchemaSource.class);
+    }
 
-                sb.append('/');
-            }
+    @Override
+    public RestconfFuture<ModulesGetResult> modulesYinGET(final ApiPath mountPath, final String fileName,
+            final String revision) {
+        return modulesGET(mountPath, fileName, revision, YinTextSchemaSource.class);
+    }
 
-            final InstanceIdentifierContext point;
-            try {
-                point = ParserIdentifier.toInstanceIdentifier(sb.toString(), currentContext.modelContext(),
-                    mountPointService);
-            } catch (RestconfDocumentedException e) {
-                return RestconfFuture.failed(e);
-            }
+    private @NonNull RestconfFuture<ModulesGetResult> modulesGET(final String fileName, final String revision,
+            final Class<? extends SchemaSourceRepresentation> representation) {
+        return modulesGET(databindProvider.currentContext(), fileName, revision, representation);
+    }
 
-            final var mountPoint = point.getMountPoint();
-            debugName = mountPoint.getIdentifier();
-            final var optSchemaService = mountPoint.getService(DOMSchemaService.class);
-            if (optSchemaService.isEmpty()) {
-                return RestconfFuture.failed(new RestconfDocumentedException(
-                    "Mount point '" + debugName + "' does not expose models"));
-            }
-            final var schemaService = optSchemaService.orElseThrow();
-            final var modelContext = schemaService.getGlobalContext();
-            if (modelContext == null) {
-                return RestconfFuture.failed(new RestconfDocumentedException(
-                    "Mount point '" + debugName + "' does not have a model context"));
-            }
-            final var domProvider = schemaService.getExtensions().getInstance(DOMYangTextSourceProvider.class);
-            databind = DatabindContext.ofModel(modelContext,
-                domProvider == null ? null : new DOMSourceResolver(domProvider));
-        } else {
-            databind = currentContext;
-            debugName = "controller";
+    private @NonNull RestconfFuture<ModulesGetResult> modulesGET(final ApiPath mountPath, final String fileName,
+            final String revision, final Class<? extends SchemaSourceRepresentation> representation) {
+        final var mountOffset = mountPath.indexOf("yang-ext", "mount");
+        if (mountOffset != mountPath.steps().size() - 1) {
+            return RestconfFuture.failed(new RestconfDocumentedException("Mount path has to end with yang-ext:mount"));
+        }
+
+        final var currentContext = databindProvider.currentContext();
+        final InstanceIdentifierContext point;
+        try {
+            point = InstanceIdentifierContext.ofApiPath(mountPath, currentContext.modelContext(), mountPointService);
+        } catch (RestconfDocumentedException e) {
+            return RestconfFuture.failed(e);
         }
 
-        // module name has to be an identifier
-        if (!it.hasNext()) {
+        final var mountPoint = point.getMountPoint();
+        final var modelContext = point.getSchemaContext();
+        final var domProvider = mountPoint.getService(DOMSchemaService.class)
+            .flatMap(svc -> Optional.ofNullable(svc.getExtensions().getInstance(DOMYangTextSourceProvider.class)));
+
+        return modulesGET(DatabindContext.ofModel(modelContext,
+            domProvider.isEmpty() ? null : new DOMSourceResolver(domProvider.orElseThrow())), fileName, revision,
+            representation);
+    }
+
+    private static @NonNull RestconfFuture<ModulesGetResult> modulesGET(final DatabindContext databind,
+            final String moduleName, final String revisionStr,
+            final Class<? extends SchemaSourceRepresentation> representation) {
+        if (moduleName == null) {
             return RestconfFuture.failed(new RestconfDocumentedException("Module name must be supplied",
                 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE));
         }
-        final var moduleName = it.next();
         if (moduleName.isEmpty() || !YangNames.IDENTIFIER_START.matches(moduleName.charAt(0))) {
             return RestconfFuture.failed(new RestconfDocumentedException(
                 "Identifier must start with character from set 'a-zA-Z_", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE));
@@ -480,16 +473,13 @@ public final class MdsalRestconfServer implements RestconfServer {
         }
 
         // YANG Revision-compliant string is required
-        if (!it.hasNext()) {
-            return RestconfFuture.failed(new RestconfDocumentedException("Revision date must be supplied.",
-                ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE));
-        }
         final Revision revision;
         try {
-            revision = Revision.of(it.next());
+            revision = Revision.ofNullable(revisionStr).orElse(null);
         } catch (final DateTimeParseException e) {
             return RestconfFuture.failed(new RestconfDocumentedException(
-                "Supplied revision is not in expected date format YYYY-mm-dd", e));
+                "Supplied revision is not in expected date format YYYY-mm-dd",
+                ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e));
         }
 
         return databind.resolveSource(new SourceIdentifier(moduleName, revision), representation)
index 05f05c28d812d3d244c882ddb7cbe8b11617572b..fe9586050371c3a44c99dd89739fff579d983f84 100644 (file)
@@ -15,6 +15,8 @@ import com.google.common.io.CharStreams;
 import java.io.IOException;
 import java.io.Reader;
 import java.util.Optional;
+import java.util.function.Consumer;
+import javax.ws.rs.container.AsyncResponse;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Mock;
@@ -22,7 +24,9 @@ import org.mockito.junit.jupiter.MockitoExtension;
 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
 import org.opendaylight.mdsal.dom.api.DOMYangTextSourceProvider;
 import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
+import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -39,13 +43,15 @@ class RestconfModulesGetTest extends AbstractRestconfTest {
     private static final String TEST_MODULE_NAME = "test-module";
     private static final String TEST_MODULE_REVISION = "2016-06-02";
     private static final String TEST_MODULE_NAMESPACE = "test:module";
-    private static final String MOUNT_POINT_IDENT = "mount-point:mount-container/point-number/yang-ext:mount";
+    private static final ApiPath MOUNT_POINT_IDENT = apiPath("mount-point:mount-container/point-number/yang-ext:mount");
     private static final YangInstanceIdentifier MOUNT_IID = YangInstanceIdentifier.of(
         QName.create("mount:point", "2016-06-02", "mount-container"),
         QName.create("mount:point", "2016-06-02", "point-number"));
 
     @Mock
     private DOMYangTextSourceProvider sourceProvider;
+    @Mock
+    private DOMSchemaService schemaService;
 
     @Override
     EffectiveModelContext modelContext() {
@@ -68,7 +74,7 @@ class RestconfModulesGetTest extends AbstractRestconfTest {
                   "Initial revision.";
               }
             }
-            """, assertYang(TEST_MODULE_NAME + "/" + TEST_MODULE_REVISION));
+            """, assertYang(null, TEST_MODULE_NAME, TEST_MODULE_REVISION));
     }
 
     /**
@@ -77,7 +83,7 @@ class RestconfModulesGetTest extends AbstractRestconfTest {
      */
     @Test
     void toSchemaExportContextFromIdentifierNotFoundTest() {
-        final var error = assertError(ar -> restconf.modulesYinGET("not-existing-module/2016-01-01", ar));
+        final var error = assertError(ar -> restconf.modulesYinGET("not-existing-module", "2016-01-01", ar));
         assertEquals("Source not-existing-module@2016-01-01 not found", error.getErrorMessage());
         assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
         assertEquals(ErrorType.APPLICATION, error.getErrorType());
@@ -90,7 +96,7 @@ class RestconfModulesGetTest extends AbstractRestconfTest {
      */
     @Test
     void toSchemaExportContextFromIdentifierInvalidIdentifierNegativeTest() {
-        final var error = assertError(ar -> restconf.modulesYangGET(TEST_MODULE_REVISION + "/" + TEST_MODULE_NAME, ar));
+        final var error = assertError(ar -> restconf.modulesYangGET(TEST_MODULE_REVISION, TEST_MODULE_NAME, ar));
         assertEquals("Identifier must start with character from set 'a-zA-Z_", error.getErrorMessage());
         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
         assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
@@ -104,7 +110,7 @@ class RestconfModulesGetTest extends AbstractRestconfTest {
     void toSchemaExportContextFromIdentifierMountPointTest() {
         mockMountPoint();
 
-        final var content = assertYang(MOUNT_POINT_IDENT + "/" + TEST_MODULE_NAME + "/" + TEST_MODULE_REVISION);
+        final var content = assertYang(MOUNT_POINT_IDENT, TEST_MODULE_NAME, TEST_MODULE_REVISION);
         assertEquals("""
             module test-module {
               namespace test:module;
@@ -125,10 +131,9 @@ class RestconfModulesGetTest extends AbstractRestconfTest {
     @Test
     void toSchemaExportContextFromIdentifierMountPointNotFoundTest() {
         mockMountPoint();
-        doReturn(MOUNT_IID).when(mountPoint).getIdentifier();
 
         final var error = assertError(
-            ar -> restconf.modulesYangGET(MOUNT_POINT_IDENT + "/" + "not-existing-module" + "/" + "2016-01-01", ar));
+            ar -> restconf.modulesYangGET(MOUNT_POINT_IDENT, "not-existing-module", "2016-01-01", ar));
         assertEquals("Source not-existing-module@2016-01-01 not found", error.getErrorMessage());
         assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
         assertEquals(ErrorType.APPLICATION, error.getErrorType());
@@ -144,7 +149,7 @@ class RestconfModulesGetTest extends AbstractRestconfTest {
         mockMountPoint();
 
         final var error = assertError(
-            ar -> restconf.modulesYangGET(MOUNT_POINT_IDENT + "/" + TEST_MODULE_REVISION + "/" + TEST_MODULE_NAME, ar));
+            ar -> restconf.modulesYangGET(MOUNT_POINT_IDENT, TEST_MODULE_REVISION, TEST_MODULE_NAME, ar));
         assertEquals("Identifier must start with character from set 'a-zA-Z_", error.getErrorMessage());
         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
         assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
@@ -152,12 +157,15 @@ class RestconfModulesGetTest extends AbstractRestconfTest {
 
     @Test
     void toSchemaExportContextFromIdentifierNullSchemaContextBehindMountPointNegativeTest() {
+        doReturn(Optional.of(schemaService)).when(mountPoint).getService(DOMSchemaService.class);
+        doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(MOUNT_IID);
+
         final var error = assertError(
-            ar -> restconf.modulesYangGET("/yang-ext:mount/" + TEST_MODULE_NAME + "/" + TEST_MODULE_REVISION, ar));
-        // FIXME: this should be something different
-        assertEquals("Identifier may not be empty", error.getErrorMessage());
+            ar -> restconf.modulesYangGET(MOUNT_POINT_IDENT, TEST_MODULE_NAME, TEST_MODULE_REVISION, ar));
+        assertEquals("Mount point 'mount-point:mount-container/point-number' does not have any models",
+            error.getErrorMessage());
         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
-        assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
+        assertEquals(ErrorTags.RESOURCE_DENIED_TRANSPORT, error.getErrorTag());
     }
 
     private void mockMountPoint() {
@@ -172,8 +180,10 @@ class RestconfModulesGetTest extends AbstractRestconfTest {
      */
     @Test
     void validateAndGetRevisionNotSuppliedTest() {
-        final var error = assertInvalidValue("module");
-        assertEquals("Revision date must be supplied.", error.getErrorMessage());
+        final var error = assertError(ar -> restconf.modulesYangGET("module", null, ar));
+        assertEquals("Source module not found", error.getErrorMessage());
+        assertEquals(ErrorType.APPLICATION, error.getErrorType());
+        assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
     }
 
     /**
@@ -182,10 +192,8 @@ class RestconfModulesGetTest extends AbstractRestconfTest {
      */
     @Test
     void validateAndGetRevisionNotParsableTest() {
-        final var error = assertError(ar -> restconf.modulesYangGET("module/not-parsable-as-date", ar));
+        final var error = assertInvalidValue("module", "not-parsable-as-date");
         assertEquals("Supplied revision is not in expected date format YYYY-mm-dd", error.getErrorMessage());
-        assertEquals(ErrorType.APPLICATION, error.getErrorType());
-        assertEquals(ErrorTag.OPERATION_FAILED, error.getErrorTag());
     }
 
     /**
@@ -194,9 +202,7 @@ class RestconfModulesGetTest extends AbstractRestconfTest {
      */
     @Test
     void validateAndGetModulNameNotSuppliedTest() {
-        mockMountPoint();
-
-        final var error = assertInvalidValue(MOUNT_POINT_IDENT);
+        final var error = assertInvalidValue(null, null);
         assertEquals("Module name must be supplied", error.getErrorMessage());
     }
 
@@ -207,7 +213,7 @@ class RestconfModulesGetTest extends AbstractRestconfTest {
      */
     @Test
     void validateAndGetModuleNameNotParsableFirstTest() {
-        final var error = assertInvalidValue("01-not-parsable-as-name-on-first-char");
+        final var error = assertInvalidValue("01-not-parsable-as-name-on-first-char", null);
         assertEquals("Identifier must start with character from set 'a-zA-Z_", error.getErrorMessage());
     }
 
@@ -218,7 +224,7 @@ class RestconfModulesGetTest extends AbstractRestconfTest {
      */
     @Test
     public void validateAndGetModuleNameNotParsableNextTest() {
-        final var error = assertInvalidValue("not-parsable-as-name-after-first-char*");
+        final var error = assertInvalidValue("not-parsable-as-name-after-first-char*", null);
         assertEquals("Supplied name has not expected identifier format", error.getErrorMessage());
     }
 
@@ -228,20 +234,23 @@ class RestconfModulesGetTest extends AbstractRestconfTest {
      */
     @Test
     void validateAndGetModuleNameEmptyTest() {
-        final var error = assertInvalidValue("");
+        final var error = assertInvalidValue("", null);
         assertEquals("Identifier must start with character from set 'a-zA-Z_", error.getErrorMessage());
     }
 
-    private String assertYang(final String identifier) {
-        try (var reader = assertEntity(Reader.class, 200, ar -> restconf.modulesYangGET(identifier, ar))) {
+    private String assertYang(final ApiPath mountPath, final String fileName, final String revision) {
+        final Consumer<AsyncResponse> invocation = mountPath != null
+            ? ar -> restconf.modulesYangGET(mountPath, fileName, revision, ar)
+                : ar -> restconf.modulesYangGET(fileName, revision, ar);
+        try (var reader = assertEntity(Reader.class, 200, invocation)) {
             return CharStreams.toString(reader);
         } catch (IOException e) {
             throw new AssertionError(e);
         }
     }
 
-    private RestconfError assertInvalidValue(final String identifier) {
-        final var error = assertError(ar -> restconf.modulesYangGET(identifier, ar));
+    private RestconfError assertInvalidValue(final String fileName, final String revision) {
+        final var error = assertError(ar -> restconf.modulesYangGET(fileName, revision, ar));
         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
         assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
         return error;
index 22756b53ab901a245a239bdbcd74881eb62ba33b..3e9b9b45f3c38bc398bad120ec33f2405a0c4fe0 100644 (file)
@@ -28,6 +28,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.nb.rfc8040.databind.DatabindContext;
 import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags;
 import org.opendaylight.restconf.server.mdsal.DOMSourceResolver;
@@ -47,13 +48,10 @@ import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
  */
 @RunWith(MockitoJUnitRunner.StrictStubs.class)
 public class RestconfSchemaServiceTest {
-    private static final String MOUNT_POINT = "mount-point-1:cont/yang-ext:mount/";
-    private static final String NULL_MOUNT_POINT = "mount-point-2:cont/yang-ext:mount/";
-    private static final String NOT_EXISTING_MOUNT_POINT = "mount-point-3:cont/yang-ext:mount/";
-
-    private static final String TEST_MODULE = "module1/2014-01-01";
-    private static final String TEST_MODULE_BEHIND_MOUNT_POINT = "module1-behind-mount-point/2014-02-03";
-    private static final String NOT_EXISTING_MODULE = "not-existing/2016-01-01";
+    private static final ApiPath MOUNT_POINT = AbstractRestconfTest.apiPath("mount-point-1:cont/yang-ext:mount");
+    private static final ApiPath NULL_MOUNT_POINT = AbstractRestconfTest.apiPath("mount-point-2:cont/yang-ext:mount");
+    private static final ApiPath NOT_EXISTING_MOUNT_POINT =
+        AbstractRestconfTest.apiPath("mount-point-3:cont/yang-ext:mount");
 
     // schema context with modules
     private static final EffectiveModelContext SCHEMA_CONTEXT =
@@ -112,7 +110,8 @@ public class RestconfSchemaServiceTest {
             .getSource(new SourceIdentifier("module1", Revision.of("2014-01-01")));
         doReturn(yangReader).when(yangSource).openStream();
 
-        assertSame(yangReader, assertEntity(Reader.class, 200, ar -> restconf.modulesYangGET(TEST_MODULE, ar)));
+        assertSame(yangReader, assertEntity(Reader.class, 200,
+            ar -> restconf.modulesYangGET("module1", "2014-01-01", ar)));
     }
 
     /**
@@ -124,7 +123,7 @@ public class RestconfSchemaServiceTest {
         // prepare conditions - return not-mount point schema context
         doReturn(SCHEMA_CONTEXT).when(schemaService).getGlobalContext();
 
-        final var error = assertError(ar -> restconf.modulesYinGET(NOT_EXISTING_MODULE, ar));
+        final var error = assertError(ar -> restconf.modulesYinGET("not-existing", "2016-01-01", ar));
         assertEquals("Source not-existing@2016-01-01 not found", error.getErrorMessage());
         assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
         assertEquals(ErrorType.APPLICATION, error.getErrorType());
@@ -139,7 +138,7 @@ public class RestconfSchemaServiceTest {
         doReturn(SCHEMA_CONTEXT_WITH_MOUNT_POINTS).when(schemaService).getGlobalContext();
 
         final var reader = assertEntity(Reader.class, 200,
-            ar -> restconf.modulesYangGET(MOUNT_POINT + TEST_MODULE_BEHIND_MOUNT_POINT, ar));
+            ar -> restconf.modulesYangGET(MOUNT_POINT, "module1-behind-mount-point", "2014-02-03", ar));
         assertEquals("""
             module module1-behind-mount-point {
               namespace module:1:behind:mount:point;
@@ -159,7 +158,7 @@ public class RestconfSchemaServiceTest {
         // prepare conditions - return schema context with mount points
         doReturn(SCHEMA_CONTEXT_WITH_MOUNT_POINTS).when(schemaService).getGlobalContext();
 
-        final var error = assertError(ar -> restconf.modulesYangGET(MOUNT_POINT + NOT_EXISTING_MODULE, ar));
+        final var error = assertError(ar -> restconf.modulesYangGET(MOUNT_POINT, "not-existing", "2016-01-01", ar));
         assertEquals("Source not-existing@2016-01-01 not found", error.getErrorMessage());
         assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
         assertEquals(ErrorType.APPLICATION, error.getErrorType());
@@ -177,8 +176,8 @@ public class RestconfSchemaServiceTest {
         // make test - call service on mount point with null schema context
         // NULL_MOUNT_POINT contains null schema context
         final var error = assertError(
-            ar -> restconf.modulesYangGET(NULL_MOUNT_POINT + TEST_MODULE_BEHIND_MOUNT_POINT, ar));
-        assertEquals("Mount point mount-point-2:cont does not expose DOMSchemaService", error.getErrorMessage());
+            ar -> restconf.modulesYangGET(NULL_MOUNT_POINT, "module1-behind-mount-point", "2014-02-03", ar));
+        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());
     }
@@ -192,7 +191,7 @@ public class RestconfSchemaServiceTest {
         // prepare conditions - return correct schema context
         doReturn(SCHEMA_CONTEXT).when(schemaService).getGlobalContext();
 
-        final var error = assertError(ar -> restconf.modulesYangGET("", ar));
+        final var error = assertError(ar -> restconf.modulesYangGET("", null, ar));
         assertEquals("Identifier must start with character from set 'a-zA-Z_", error.getErrorMessage());
         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
         assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
@@ -209,7 +208,7 @@ public class RestconfSchemaServiceTest {
         doReturn(SCHEMA_CONTEXT_WITH_MOUNT_POINTS).when(schemaService).getGlobalContext();
 
         // make test and verify
-        final var error = assertError(ar -> restconf.modulesYangGET(MOUNT_POINT + "", ar));
+        final var error = assertError(ar -> restconf.modulesYangGET(MOUNT_POINT, "", null, ar));
         assertEquals("Identifier must start with character from set 'a-zA-Z_", error.getErrorMessage());
         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
         assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
@@ -225,7 +224,7 @@ public class RestconfSchemaServiceTest {
         doReturn(SCHEMA_CONTEXT).when(schemaService).getGlobalContext();
 
         // make test and verify
-        final var error = assertError(ar -> restconf.modulesYangGET("01_module/2016-01-01", ar));
+        final var error = assertError(ar -> restconf.modulesYangGET("01_module", "2016-01-01", ar));
         assertEquals("Identifier must start with character from set 'a-zA-Z_", error.getErrorMessage());
         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
         assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
@@ -242,7 +241,7 @@ public class RestconfSchemaServiceTest {
         doReturn(SCHEMA_CONTEXT_WITH_MOUNT_POINTS).when(schemaService).getGlobalContext();
 
         // make test and verify
-        final var error = assertError(ar -> restconf.modulesYangGET(MOUNT_POINT + "01_module/2016-01-01", ar));
+        final var error = assertError(ar -> restconf.modulesYangGET(MOUNT_POINT, "01_module", "2016-01-01", ar));
         assertEquals("Identifier must start with character from set 'a-zA-Z_", error.getErrorMessage());
         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
         assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
@@ -261,7 +260,7 @@ public class RestconfSchemaServiceTest {
         doReturn(SCHEMA_CONTEXT).when(schemaService).getGlobalContext();
 
         // make test and verify
-        final var error = assertError(ar -> restconf.modulesYangGET("2014-01-01", ar));
+        final var error = assertError(ar -> restconf.modulesYangGET("2014-01-01", null, ar));
         assertEquals("Identifier must start with character from set 'a-zA-Z_", error.getErrorMessage());
         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
         assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
@@ -281,7 +280,7 @@ public class RestconfSchemaServiceTest {
         doReturn(SCHEMA_CONTEXT_WITH_MOUNT_POINTS).when(schemaService).getGlobalContext();
 
         // make test and verify
-        final var error = assertError(ar -> restconf.modulesYangGET(MOUNT_POINT + "2014-01-01", ar));
+        final var error = assertError(ar -> restconf.modulesYangGET(MOUNT_POINT, "2014-01-01", null, ar));
         assertEquals("Identifier must start with character from set 'a-zA-Z_", error.getErrorMessage());
         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
         assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
@@ -298,10 +297,10 @@ public class RestconfSchemaServiceTest {
         doReturn(SCHEMA_CONTEXT).when(schemaService).getGlobalContext();
 
         // make test and verify
-        final var error = assertError(ar -> restconf.modulesYangGET("module", ar));
-        assertEquals("Revision date must be supplied.", error.getErrorMessage());
-        assertEquals(ErrorType.PROTOCOL, error.getErrorType());
-        assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
+        final var error = assertError(ar -> restconf.modulesYinGET("module", null, ar));
+        assertEquals("Source module not found", error.getErrorMessage());
+        assertEquals(ErrorType.APPLICATION, error.getErrorType());
+        assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
     }
 
     /**
@@ -315,10 +314,10 @@ public class RestconfSchemaServiceTest {
         doReturn(SCHEMA_CONTEXT_WITH_MOUNT_POINTS).when(schemaService).getGlobalContext();
 
         // make test and verify
-        final var error = assertError(ar -> restconf.modulesYangGET(MOUNT_POINT + "module", ar));
-        assertEquals("Revision date must be supplied.", error.getErrorMessage());
-        assertEquals(ErrorType.PROTOCOL, error.getErrorType());
-        assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
+        final var error = assertError(ar -> restconf.modulesYangGET(MOUNT_POINT, "module", null, ar));
+        assertEquals("Source module not found", error.getErrorMessage());
+        assertEquals(ErrorType.APPLICATION, error.getErrorType());
+        assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
     }
 
     /**
@@ -331,7 +330,7 @@ public class RestconfSchemaServiceTest {
         doReturn(SCHEMA_CONTEXT_WITH_MOUNT_POINTS).when(schemaService).getGlobalContext();
 
         final var error = assertError(
-            ar -> restconf.modulesYangGET(NOT_EXISTING_MOUNT_POINT + TEST_MODULE_BEHIND_MOUNT_POINT, ar));
+            ar -> restconf.modulesYangGET(NOT_EXISTING_MOUNT_POINT, "module1-behind-mount-point", "2014-02-03", ar));
         assertEquals("Failed to lookup for module with name 'mount-point-3'.", error.getErrorMessage());
         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
         assertEquals(ErrorTag.UNKNOWN_ELEMENT, error.getErrorTag());
index 4a1fd2c6e43a3c3ce3b1ff28321dfa4e501aa673..4d022c335fa2c0b8a7692837b00bb4096b616119 100644 (file)
@@ -10,20 +10,17 @@ package org.opendaylight.restconf.nb.rfc8040.rests.services.impl;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.util.List;
 import java.util.Optional;
-import java.util.stream.Stream;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
-import org.opendaylight.restconf.nb.rfc8040.URLConstants;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri;
 import org.opendaylight.yangtools.yang.common.Revision;
 
 class RestconfSchemaSourceUrlProviderTest {
-    private static final String URL_PREFIX = "/" + URLConstants.BASE_PATH + "/modules";
-
     @Test
     @DisplayName("Unsupported module-set name.")
     void unsupportedModuleSet() {
@@ -37,14 +34,14 @@ class RestconfSchemaSourceUrlProviderTest {
     void getSchemaSourceUrl(final String moduleName, final Revision revision, final Uri expected) {
         final var urlProvider = new RestconfSchemaSourceUrlProvider();
         final var result = urlProvider.getSchemaSourceUrl("ODL_modules", moduleName, revision);
-        assertTrue(result.isPresent());
         assertEquals(Optional.of(expected), result);
     }
 
-    private static Stream<Arguments> getSchemaSourceUrlArgs() {
-        return Stream.of(
-            Arguments.of("odl-module", Revision.of("2023-02-23"), new Uri(URL_PREFIX + "/odl-module/2023-02-23")),
-            Arguments.of("module-no-revision", null, new Uri(URL_PREFIX + "/module-no-revision"))
+    private static List<Arguments> getSchemaSourceUrlArgs() {
+        return List.of(
+            Arguments.of("odl-module", Revision.of("2023-02-23"),
+                new Uri("/rests/modules/odl-module?revision=2023-02-23")),
+            Arguments.of("module-no-revision", null, new Uri("/rests/modules/module-no-revision"))
         );
     }
 }