Report ServerException from ApiPathNormalizer 24/111724/4
authorRobert Varga <robert.varga@pantheon.tech>
Sat, 18 May 2024 22:54:15 +0000 (00:54 +0200)
committerRobert Varga <nite@hq.sk>
Sun, 19 May 2024 15:45:55 +0000 (15:45 +0000)
This patch takes a step away from RestconfDocumentedException by making
APiPath normalization requests report ServerException instead.

We also update ServerException to match RestconfDocumentedException's
behaviour of reporting cause message in error-info.

JIRA: NETCONF-1188
Change-Id: I51aa91d4b3c914a421fe28a9ed8a517be1e47571
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
15 files changed:
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/Insert.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/RestconfStrategy.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/PatchBody.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/ServerException.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/mdsal/MdsalRestconfServer.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/ApiPathNormalizer.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/DefaultResourceContext.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/OperationsResource.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/AbstractResourceBodyTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/transactions/MdsalRestconfStrategyTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/server/api/ServerExceptionTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/server/spi/ApiPathCanonizerTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/server/spi/ApiPathNormalizerTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/server/spi/NC1265Test.java

index bd550434b7b4f7c10091d8ce1e19cc2fb3da9cb3..c0f98ce7dfe1a3e352b862622b7eb9be81596b9a 100644 (file)
@@ -21,6 +21,7 @@ import org.opendaylight.restconf.api.QueryParameters;
 import org.opendaylight.restconf.api.query.InsertParam;
 import org.opendaylight.restconf.api.query.PointParam;
 import org.opendaylight.restconf.server.api.DatabindContext;
+import org.opendaylight.restconf.server.api.ServerException;
 import org.opendaylight.restconf.server.spi.ApiPathNormalizer;
 import org.opendaylight.yangtools.concepts.Immutable;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
@@ -39,7 +40,7 @@ public final class Insert implements Immutable {
     @FunctionalInterface
     public interface PointNormalizer {
 
-        PathArgument normalizePoint(ApiPath value);
+        PathArgument normalizePoint(ApiPath value) throws ServerException;
     }
 
     private final @NonNull InsertParam insert;
@@ -117,13 +118,11 @@ public final class Insert implements Immutable {
     }
 
     private static PathArgument parsePoint(final PointNormalizer pointParser, final String value) {
-        final ApiPath pointPath;
         try {
-            pointPath = ApiPath.parse(value);
-        } catch (ParseException e) {
+            return pointParser.normalizePoint(ApiPath.parse(value));
+        } catch (ParseException | ServerException e) {
             throw new IllegalArgumentException("Malformed point parameter '" + value + "': " + e.getMessage(), e);
         }
-        return pointParser.normalizePoint(pointPath);
     }
 
     private static IllegalArgumentException invalidPointIAE() {
index 472e666c690fc17298203dfe3c11ab4e0a54c1bf..884e9ac38b40311dcb3fbe5baa5d704b27ce5295 100644 (file)
@@ -87,6 +87,7 @@ import org.opendaylight.restconf.server.api.ResourceBody;
 import org.opendaylight.restconf.server.api.ServerError;
 import org.opendaylight.restconf.server.api.ServerErrorInfo;
 import org.opendaylight.restconf.server.api.ServerErrorPath;
+import org.opendaylight.restconf.server.api.ServerException;
 import org.opendaylight.restconf.server.api.ServerRequest;
 import org.opendaylight.restconf.server.spi.ApiPathCanonizer;
 import org.opendaylight.restconf.server.spi.ApiPathNormalizer;
@@ -203,7 +204,7 @@ public abstract class RestconfStrategy implements DatabindAware {
         operations = new OperationsResource(pathNormalizer);
     }
 
-    public final @NonNull StrategyAndPath resolveStrategyPath(final ApiPath path) {
+    public final @NonNull StrategyAndPath resolveStrategyPath(final ApiPath path) throws ServerException {
         final var andTail = resolveStrategy(path);
         final var strategy = andTail.strategy();
         return new StrategyAndPath(strategy, strategy.pathNormalizer.normalizeDataPath(andTail.tail()));
@@ -215,34 +216,35 @@ public abstract class RestconfStrategy implements DatabindAware {
      * @param path {@link ApiPath} to resolve
      * @return A strategy and the remaining path
      * @throws NullPointerException if {@code path} is {@code null}
+     * @throws ServerException if an error occurs
      */
-    public final @NonNull StrategyAndTail resolveStrategy(final ApiPath path) {
+    public final @NonNull StrategyAndTail resolveStrategy(final ApiPath path) throws ServerException {
         var mount = path.indexOf("yang-ext", "mount");
         if (mount == -1) {
             return new StrategyAndTail(this, path);
         }
         if (mountPointService == null) {
-            throw new RestconfDocumentedException("Mount point service is not available",
-                ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED);
+            throw new ServerException(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED,
+                "Mount point service is not available");
         }
         final var mountPath = path.subPath(0, mount);
         final var dataPath = pathNormalizer.normalizeDataPath(path.subPath(0, mount));
         final var mountPoint = mountPointService.getMountPoint(dataPath.instance())
-            .orElseThrow(() -> new RestconfDocumentedException("Mount point '" + mountPath + "' does not exist",
-                ErrorType.PROTOCOL, ErrorTags.RESOURCE_DENIED_TRANSPORT));
+            .orElseThrow(() -> new ServerException(ErrorType.PROTOCOL, ErrorTags.RESOURCE_DENIED_TRANSPORT,
+                "Mount point '%s' does not exist", mountPath));
 
-        return createStrategy(mountPath, mountPoint).resolveStrategy(path.subPath(mount + 1));
+        return createStrategy(databind, mountPath, mountPoint).resolveStrategy(path.subPath(mount + 1));
     }
 
-    private static @NonNull RestconfStrategy createStrategy(final ApiPath mountPath, final DOMMountPoint mountPoint) {
+    private static @NonNull RestconfStrategy createStrategy(final DatabindContext databind, final ApiPath mountPath,
+            final DOMMountPoint mountPoint) throws ServerException {
         final var mountSchemaService = mountPoint.getService(DOMSchemaService.class)
-            .orElseThrow(() -> new RestconfDocumentedException(
-                "Mount point '" + mountPath + "' does not expose DOMSchemaService",
-                ErrorType.PROTOCOL, ErrorTags.RESOURCE_DENIED_TRANSPORT));
+            .orElseThrow(() -> new ServerException(ErrorType.PROTOCOL, ErrorTags.RESOURCE_DENIED_TRANSPORT,
+                "Mount point '%s' does not expose DOMSchemaService", mountPath));
         final var mountModelContext = mountSchemaService.getGlobalContext();
         if (mountModelContext == null) {
-            throw new RestconfDocumentedException("Mount point '" + mountPath + "' does not have any models",
-                ErrorType.PROTOCOL, ErrorTags.RESOURCE_DENIED_TRANSPORT);
+            throw new ServerException(ErrorType.PROTOCOL, ErrorTags.RESOURCE_DENIED_TRANSPORT,
+                "Mount point '%s' does not have any models", mountPath);
         }
         final var mountDatabind = DatabindContext.ofModel(mountModelContext);
         final var mountPointService = mountPoint.getService(DOMMountPointService.class).orElse(null);
@@ -263,8 +265,9 @@ public abstract class RestconfStrategy implements DatabindAware {
                 actionService, sourceProvider, mountPointService);
         }
         LOG.warn("Mount point {} does not expose a suitable access interface", mountPath);
-        throw new RestconfDocumentedException("Could not find a supported access interface in mount point",
-            ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, mountPoint.getIdentifier());
+        throw new ServerException(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED,
+            new ServerErrorPath(databind, mountPoint.getIdentifier()),
+            "Could not find a supported access interface in mount point");
     }
 
     @Override
@@ -335,8 +338,8 @@ public abstract class RestconfStrategy implements DatabindAware {
         final Data path;
         try {
             path = pathNormalizer.normalizeDataPath(apiPath);
-        } catch (RestconfDocumentedException e) {
-            return RestconfFuture.failed(e);
+        } catch (ServerException e) {
+            return RestconfFuture.failed(e.toLegacy());
         }
 
         final Insert insert;
@@ -581,8 +584,8 @@ public abstract class RestconfStrategy implements DatabindAware {
         final Data path;
         try {
             path = pathNormalizer.normalizeDataPath(apiPath);
-        } catch (RestconfDocumentedException e) {
-            return RestconfFuture.failed(e);
+        } catch (ServerException e) {
+            return RestconfFuture.failed(e.toLegacy());
         }
 
         final NormalizedNode data;
@@ -599,8 +602,8 @@ public abstract class RestconfStrategy implements DatabindAware {
         final Data path;
         try {
             path = pathNormalizer.normalizeDataPath(apiPath);
-        } catch (RestconfDocumentedException e) {
-            return RestconfFuture.failed(e);
+        } catch (ServerException e) {
+            return RestconfFuture.failed(e.toLegacy());
         }
 
         final PatchContext patch;
@@ -828,8 +831,8 @@ public abstract class RestconfStrategy implements DatabindAware {
         final Data path;
         try {
             path = pathNormalizer.normalizeDataPath(apiPath);
-        } catch (RestconfDocumentedException e) {
-            return RestconfFuture.failed(e);
+        } catch (ServerException e) {
+            return RestconfFuture.failed(e.toLegacy());
         }
 
         // FIXME: reject empty YangInstanceIdentifier, as datastores may not be deleted
@@ -845,8 +848,8 @@ public abstract class RestconfStrategy implements DatabindAware {
         final Data path;
         try {
             path = pathNormalizer.normalizeDataPath(apiPath);
-        } catch (RestconfDocumentedException e) {
-            return RestconfFuture.failed(e);
+        } catch (ServerException e) {
+            return RestconfFuture.failed(e.toLegacy());
         }
 
         final DataGetParams getParams;
@@ -1282,8 +1285,8 @@ public abstract class RestconfStrategy implements DatabindAware {
         final Rpc path;
         try {
             path = pathNormalizer.normalizeRpcPath(apiPath);
-        } catch (RestconfDocumentedException e) {
-            return RestconfFuture.failed(e);
+        } catch (ServerException e) {
+            return RestconfFuture.failed(e.toLegacy());
         }
 
         final ContainerNode data;
@@ -1408,8 +1411,8 @@ public abstract class RestconfStrategy implements DatabindAware {
         final InstanceReference path;
         try {
             path = pathNormalizer.normalizeDataOrActionPath(apiPath);
-        } catch (RestconfDocumentedException e) {
-            return RestconfFuture.failed(e);
+        } catch (ServerException e) {
+            return RestconfFuture.failed(e.toLegacy());
         }
         if (path instanceof Data dataPath) {
             try (var resourceBody = body.toResource()) {
index fefdf7cfb5bafd8100b51752180552f2e8d8155c..bdeaf2fc43b03790ad4a7dbec80f6bcb82bc11c1 100644 (file)
@@ -43,9 +43,9 @@ public abstract sealed class PatchBody extends RequestBody permits JsonPatchBody
          *
          * @param apiPath sub-resource
          * @return A {@link ResourceContext}
-         * @throws RestconfDocumentedException if the sub-resource cannot be resolved
+         * @throws ServerException if the sub-resource cannot be resolved
          */
-        protected abstract ResourceContext resolveRelative(ApiPath apiPath);
+        protected abstract ResourceContext resolveRelative(ApiPath apiPath) throws ServerException;
     }
 
     PatchBody(final InputStream inputStream) {
@@ -81,7 +81,7 @@ public abstract sealed class PatchBody extends RequestBody permits JsonPatchBody
         final Data result;
         try {
             result = resource.resolveRelative(targetPath).path;
-        } catch (RestconfDocumentedException e) {
+        } catch (ServerException e) {
             throw new RestconfDocumentedException("Invalid edit target '" + targetPath + "'",
                 ErrorType.RPC, ErrorTag.MALFORMED_MESSAGE, e);
         }
index 7e434128c12d9559d4188a63715f7a59d5717ed3..f6d0751eb2efbd99c36b609d4b8ed3fed82ea770 100644 (file)
@@ -17,6 +17,8 @@ import java.io.ObjectStreamException;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.restconf.api.ErrorMessage;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 
@@ -63,7 +65,8 @@ public final class ServerException extends Exception {
 
     public ServerException(final ErrorType type, final ErrorTag tag, final String message,
             final @Nullable Throwable cause) {
-        this(requireNonNull(message), new ServerError(type, tag, message), cause);
+        this(requireNonNull(message),
+            new ServerError(type, tag, new ErrorMessage(message), null, null, errorInfoOf(cause)), cause);
     }
 
     public ServerException(final ErrorType type, final ErrorTag tag, final String format,
@@ -71,6 +74,10 @@ public final class ServerException extends Exception {
         this(type, tag, format.formatted(args));
     }
 
+    public ServerException(final ErrorType type, final ErrorTag tag, final ServerErrorPath path, final String message) {
+        this(message, new ServerError(type, tag, new ErrorMessage(message), null, requireNonNull(path), null), null);
+    }
+
     /**
      * Return the reported {@link ServerError}.
      *
@@ -80,6 +87,23 @@ public final class ServerException extends Exception {
         return error;
     }
 
+    @Deprecated
+    public RestconfDocumentedException toLegacy() {
+        final var message = error.message();
+        final var info = error.info();
+        final var path = error.path();
+        if (path != null) {
+            return new RestconfDocumentedException(this,
+                new RestconfError(error.type(), error.tag(), message != null ? message.elementBody() : null,
+                    error.appTag(), info != null ? info.elementBody() : null, path.path()),
+                path.databind().modelContext());
+        } else {
+            return new RestconfDocumentedException(this,
+                new RestconfError(error.type(), error.tag(), message != null ? message.elementBody() : null,
+                    error.appTag(), info != null ? info.elementBody() : null, null));
+        }
+    }
+
     @java.io.Serial
     private void readObjectNoData() throws ObjectStreamException {
         throw new NotSerializableException();
@@ -95,6 +119,16 @@ public final class ServerException extends Exception {
         throw new NotSerializableException();
     }
 
+    private static @Nullable ServerErrorInfo errorInfoOf(final @Nullable Throwable cause) {
+        if (cause != null) {
+            final var message = cause.getMessage();
+            if (message != null) {
+                return new ServerErrorInfo(message);
+            }
+        }
+        return null;
+    }
+
     private static ErrorTag errorTagOf(final @Nullable Throwable cause) {
         if (cause instanceof UnsupportedOperationException) {
             return ErrorTag.OPERATION_NOT_SUPPORTED;
index 3fc09ce4b82dd6693fd612f5bcf55dcee7f44568..26bf2e2649071b2854b61cd279dc76c146da1f6f 100644 (file)
@@ -50,6 +50,7 @@ import org.opendaylight.restconf.server.api.OperationInputBody;
 import org.opendaylight.restconf.server.api.PatchBody;
 import org.opendaylight.restconf.server.api.ResourceBody;
 import org.opendaylight.restconf.server.api.RestconfServer;
+import org.opendaylight.restconf.server.api.ServerException;
 import org.opendaylight.restconf.server.api.ServerRequest;
 import org.opendaylight.restconf.server.spi.RpcImplementation;
 import org.opendaylight.yangtools.yang.common.Empty;
@@ -147,8 +148,8 @@ public final class MdsalRestconfServer implements RestconfServer, AutoCloseable
         final StrategyAndTail stratAndTail;
         try {
             stratAndTail = localStrategy().resolveStrategy(identifier);
-        } catch (RestconfDocumentedException e) {
-            return RestconfFuture.failed(e);
+        } catch (ServerException e) {
+            return RestconfFuture.failed(e.toLegacy());
         }
         return stratAndTail.strategy().dataDELETE(request, stratAndTail.tail());
     }
@@ -163,8 +164,8 @@ public final class MdsalRestconfServer implements RestconfServer, AutoCloseable
         final StrategyAndTail stratAndTail;
         try {
             stratAndTail = localStrategy().resolveStrategy(identifier);
-        } catch (RestconfDocumentedException e) {
-            return RestconfFuture.failed(e);
+        } catch (ServerException e) {
+            return RestconfFuture.failed(e.toLegacy());
         }
         return stratAndTail.strategy().dataGET(request, stratAndTail.tail());
     }
@@ -180,8 +181,8 @@ public final class MdsalRestconfServer implements RestconfServer, AutoCloseable
         final StrategyAndTail strategyAndTail;
         try {
             strategyAndTail = localStrategy().resolveStrategy(identifier);
-        } catch (RestconfDocumentedException e) {
-            return RestconfFuture.failed(e);
+        } catch (ServerException e) {
+            return RestconfFuture.failed(e.toLegacy());
         }
         return strategyAndTail.strategy().dataPATCH(strategyAndTail.tail(), body);
     }
@@ -197,8 +198,8 @@ public final class MdsalRestconfServer implements RestconfServer, AutoCloseable
         final StrategyAndTail strategyAndTail;
         try {
             strategyAndTail = localStrategy().resolveStrategy(identifier);
-        } catch (RestconfDocumentedException e) {
-            return RestconfFuture.failed(e);
+        } catch (ServerException e) {
+            return RestconfFuture.failed(e.toLegacy());
         }
         return strategyAndTail.strategy().dataPATCH(strategyAndTail.tail(), body);
     }
@@ -214,8 +215,8 @@ public final class MdsalRestconfServer implements RestconfServer, AutoCloseable
         final StrategyAndTail strategyAndTail;
         try {
             strategyAndTail = localStrategy().resolveStrategy(identifier);
-        } catch (RestconfDocumentedException e) {
-            return RestconfFuture.failed(e);
+        } catch (ServerException e) {
+            return RestconfFuture.failed(e.toLegacy());
         }
         return strategyAndTail.strategy().dataPOST(request, strategyAndTail.tail(), body);
     }
@@ -231,8 +232,8 @@ public final class MdsalRestconfServer implements RestconfServer, AutoCloseable
         final StrategyAndTail strategyAndTail;
         try {
             strategyAndTail = localStrategy().resolveStrategy(identifier);
-        } catch (RestconfDocumentedException e) {
-            return RestconfFuture.failed(e);
+        } catch (ServerException e) {
+            return RestconfFuture.failed(e.toLegacy());
         }
         return strategyAndTail.strategy().dataPUT(request, strategyAndTail.tail(), body);
     }
@@ -276,8 +277,8 @@ public final class MdsalRestconfServer implements RestconfServer, AutoCloseable
         final StrategyAndTail stratAndTail;
         try {
             stratAndTail = localStrategy().resolveStrategy(mountPath);
-        } catch (RestconfDocumentedException e) {
-            return RestconfFuture.failed(e);
+        } catch (ServerException e) {
+            return RestconfFuture.failed(e.toLegacy());
         }
         // FIXME: require remnant to be empty
         return modulesGET(stratAndTail.strategy(), fileName, revision, representation);
@@ -327,8 +328,8 @@ public final class MdsalRestconfServer implements RestconfServer, AutoCloseable
         final StrategyAndTail strategyAndTail;
         try {
             strategyAndTail = localStrategy().resolveStrategy(operation);
-        } catch (RestconfDocumentedException e) {
-            return RestconfFuture.failed(e);
+        } catch (ServerException e) {
+            return RestconfFuture.failed(e.toLegacy());
         }
 
         final var strategy = strategyAndTail.strategy();
@@ -342,8 +343,8 @@ public final class MdsalRestconfServer implements RestconfServer, AutoCloseable
         final StrategyAndTail strategyAndTail;
         try {
             strategyAndTail = localStrategy().resolveStrategy(apiPath);
-        } catch (RestconfDocumentedException e) {
-            return RestconfFuture.failed(e);
+        } catch (ServerException e) {
+            return RestconfFuture.failed(e.toLegacy());
         }
         final var strategy = strategyAndTail.strategy();
         return strategy.operationsPOST(request, restconfURI, strategyAndTail.tail(), body);
index 83e3cdce6d013340cff9bdfd5dc15c344ad2b214..e36a6c4a1e0f9babf399840977df5352fa030069 100644 (file)
@@ -20,7 +20,6 @@ import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.restconf.api.ApiPath.ListInstance;
 import org.opendaylight.restconf.api.ApiPath.Step;
-import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.nb.rfc8040.Insert.PointNormalizer;
 import org.opendaylight.restconf.server.api.DatabindAware;
 import org.opendaylight.restconf.server.api.DatabindContext;
@@ -29,6 +28,7 @@ import org.opendaylight.restconf.server.api.DatabindPath.Action;
 import org.opendaylight.restconf.server.api.DatabindPath.Data;
 import org.opendaylight.restconf.server.api.DatabindPath.InstanceReference;
 import org.opendaylight.restconf.server.api.DatabindPath.Rpc;
+import org.opendaylight.restconf.server.api.ServerException;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -69,7 +69,7 @@ public final class ApiPathNormalizer implements DatabindAware, PointNormalizer {
         return databind;
     }
 
-    public @NonNull DatabindPath normalizePath(final ApiPath apiPath) {
+    public @NonNull DatabindPath normalizePath(final ApiPath apiPath) throws ServerException {
         final var it = apiPath.steps().iterator();
         if (!it.hasNext()) {
             return new Data(databind);
@@ -83,9 +83,8 @@ public final class ApiPathNormalizer implements DatabindAware, PointNormalizer {
         final var firstStep = it.next();
         final var firstModule = firstStep.module();
         if (firstModule == null) {
-            throw new RestconfDocumentedException(
-                "First member must use namespace-qualified form, '" + firstStep.identifier() + "' does not",
-                ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+            throw new ServerException(ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE,
+                "First member must use namespace-qualified form, '%s' does not", firstStep.identifier());
         }
 
         var namespace = resolveNamespace(firstModule);
@@ -101,12 +100,13 @@ public final class ApiPathNormalizer implements DatabindAware, PointNormalizer {
 
             // We have found an RPC match,
             if (it.hasNext()) {
-                throw new RestconfDocumentedException("First step in the path resolves to RPC '" + qname + "' and "
-                    + "therefore it must be the only step present", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+                throw new ServerException(ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE,
+                    "First step in the path resolves to RPC '%s' and therefore it must be the only step present",
+                    qname);
             }
             if (step instanceof ListInstance) {
-                throw new RestconfDocumentedException("First step in the path resolves to RPC '" + qname + "' and "
-                    + "therefore it must not contain key values", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+                throw new ServerException(ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE,
+                    "First step in the path resolves to RPC '%s' and therefore it must not contain key values", qname);
             }
 
             final var stack = SchemaInferenceStack.of(modelContext);
@@ -121,7 +121,7 @@ public final class ApiPathNormalizer implements DatabindAware, PointNormalizer {
 
     @NonNull DatabindPath normalizeSteps(final SchemaInferenceStack stack, final @NonNull DataSchemaContext rootNode,
             final @NonNull List<PathArgument> pathPrefix, final @NonNull QNameModule firstNamespace,
-            final @NonNull Step firstStep, final Iterator<@NonNull Step> it) {
+            final @NonNull Step firstStep, final Iterator<@NonNull Step> it) throws ServerException {
         var parentNode = rootNode;
         var namespace = firstNamespace;
         var step = firstStep;
@@ -136,13 +136,12 @@ public final class ApiPathNormalizer implements DatabindAware, PointNormalizer {
                     final var action = optAction.orElseThrow();
 
                     if (it.hasNext()) {
-                        throw new RestconfDocumentedException("Request path resolves to action '" + qname + "' and "
-                            + "therefore it must not continue past it", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+                        throw new ServerException(ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE,
+                            "Request path resolves to action '%s' and therefore it must not continue past it", qname);
                     }
                     if (step instanceof ListInstance) {
-                        throw new RestconfDocumentedException("Request path resolves to action '" + qname + "' and "
-                            + "therefore it must not contain key values",
-                            ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+                        throw new ServerException(ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE,
+                            "Request path resolves to action '%s' and therefore it must not contain key values", qname);
                     }
 
                     final var stmt = stack.enterSchemaTree(qname);
@@ -157,8 +156,8 @@ public final class ApiPathNormalizer implements DatabindAware, PointNormalizer {
             final var found = parentNode instanceof DataSchemaContext.Composite composite
                 ? composite.enterChild(stack, qname) : null;
             if (found == null) {
-                throw new RestconfDocumentedException("Schema for '" + qname + "' not found",
-                    ErrorType.PROTOCOL, ErrorTag.DATA_MISSING);
+                throw new ServerException(ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, "Schema for '%s' not found",
+                    qname);
             }
 
             // Now add all mixins encountered to the path
@@ -177,20 +176,18 @@ public final class ApiPathNormalizer implements DatabindAware, PointNormalizer {
                     pathArg = prepareNodeWithPredicates(stack, qname, listSchema, values);
                 } else if (schema instanceof LeafListSchemaNode leafListSchema) {
                     if (values.size() != 1) {
-                        throw new RestconfDocumentedException("Entry '" + qname + "' requires one value predicate.",
-                            ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE);
+                        throw new ServerException(ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE,
+                            "Entry '%s' requires one value predicate.", qname);
                     }
                     pathArg = new NodeWithValue<>(qname, parserJsonValue(stack, leafListSchema, values.get(0)));
                 } else {
-                    throw new RestconfDocumentedException(
-                        "Entry '" + qname + "' does not take a key or value predicate.",
-                        ErrorType.PROTOCOL, ErrorTag.MISSING_ATTRIBUTE);
+                    throw new ServerException(ErrorType.PROTOCOL, ErrorTag.MISSING_ATTRIBUTE,
+                        "Entry '%s' does not take a key or value predicate.", qname);
                 }
             } else {
                 if (childNode.dataSchemaNode() instanceof ListSchemaNode list && !list.getKeyDefinition().isEmpty()) {
-                    throw new RestconfDocumentedException(
-                        "Entry '" + qname + "' requires key or value predicate to be present.",
-                        ErrorType.PROTOCOL, ErrorTag.MISSING_ATTRIBUTE);
+                    throw new ServerException(ErrorType.PROTOCOL, ErrorTag.MISSING_ATTRIBUTE,
+                        "Entry '%s' requires key or value predicate to be present.", qname);
                 }
                 pathArg = childNode.getPathStep();
             }
@@ -212,45 +209,45 @@ public final class ApiPathNormalizer implements DatabindAware, PointNormalizer {
         }
     }
 
-    public @NonNull Data normalizeDataPath(final ApiPath apiPath) {
+    public @NonNull Data normalizeDataPath(final ApiPath apiPath) throws ServerException {
         final var path = normalizePath(apiPath);
         if (path instanceof Data dataPath) {
             return dataPath;
         }
-        throw new RestconfDocumentedException("Point '" + apiPath + "' resolves to non-data " + path,
-            ErrorType.PROTOCOL, ErrorTag.DATA_MISSING);
+        throw new ServerException(ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, "Point '%s' resolves to non-data %s",
+            apiPath, path);
     }
 
     @Override
-    public PathArgument normalizePoint(final ApiPath value) {
+    public PathArgument normalizePoint(final ApiPath value) throws ServerException {
         final var path = normalizePath(value);
         if (path instanceof Data dataPath) {
             final var lastArg = dataPath.instance().getLastPathArgument();
             if (lastArg != null) {
                 return lastArg;
             }
-            throw new IllegalArgumentException("Point '" + value + "' resolves to an empty path");
+            throw new ServerException(ErrorType.PROTOCOL,  ErrorTag.DATA_MISSING,
+                "Point '%s' resolves to an empty path", value);
         }
-        throw new IllegalArgumentException("Point '" + value + "' resolves to non-data " + path);
+        throw new ServerException(ErrorType.PROTOCOL,  ErrorTag.DATA_MISSING, "Point '%s' resolves to non-data %s",
+            value, path);
     }
 
-    public @NonNull Rpc normalizeRpcPath(final ApiPath apiPath) {
+    public @NonNull Rpc normalizeRpcPath(final ApiPath apiPath) throws ServerException {
         final var steps = apiPath.steps();
         return switch (steps.size()) {
-            case 0 -> throw new RestconfDocumentedException("RPC name must be present", ErrorType.PROTOCOL,
-                ErrorTag.DATA_MISSING);
+            case 0 -> throw new ServerException(ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, "RPC name must be present");
             case 1 -> normalizeRpcPath(steps.get(0));
-            default -> throw new RestconfDocumentedException(apiPath + " does not refer to an RPC", ErrorType.PROTOCOL,
-                ErrorTag.DATA_MISSING);
+            default -> throw new ServerException(ErrorType.PROTOCOL, ErrorTag.DATA_MISSING,
+                "%s does not refer to an RPC", apiPath);
         };
     }
 
-    public @NonNull Rpc normalizeRpcPath(final ApiPath.Step step) {
+    public @NonNull Rpc normalizeRpcPath(final ApiPath.Step step) throws ServerException {
         final var firstModule = step.module();
         if (firstModule == null) {
-            throw new RestconfDocumentedException(
-                "First member must use namespace-qualified form, '" + step.identifier() + "' does not",
-                ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+            throw new ServerException(ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE,
+                "First member must use namespace-qualified form, '%s' does not", step.identifier());
         }
 
         final var namespace = resolveNamespace(firstModule);
@@ -260,17 +257,16 @@ public final class ApiPathNormalizer implements DatabindAware, PointNormalizer {
         try {
             stmt = stack.enterSchemaTree(qname);
         } catch (IllegalArgumentException e) {
-            throw new RestconfDocumentedException(qname + " does not refer to an RPC", ErrorType.PROTOCOL,
-                ErrorTag.DATA_MISSING, e);
+            throw new ServerException(ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, qname + " does not refer to an RPC",
+                e);
         }
         if (stmt instanceof RpcEffectiveStatement rpc) {
             return new Rpc(databind, stack.toInference(), rpc);
         }
-        throw new RestconfDocumentedException(qname + " does not refer to an RPC", ErrorType.PROTOCOL,
-            ErrorTag.DATA_MISSING);
+        throw new ServerException(ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, "%s does not refer to an RPC", qname);
     }
 
-    public @NonNull InstanceReference normalizeDataOrActionPath(final ApiPath apiPath) {
+    public @NonNull InstanceReference normalizeDataOrActionPath(final ApiPath apiPath) throws ServerException {
         // FIXME: optimize this
         final var path = normalizePath(apiPath);
         if (path instanceof Data dataPath) {
@@ -279,18 +275,18 @@ public final class ApiPathNormalizer implements DatabindAware, PointNormalizer {
         if (path instanceof Action actionPath) {
             return actionPath;
         }
-        throw new RestconfDocumentedException("Unexpected path " + path, ErrorType.PROTOCOL, ErrorTag.DATA_MISSING);
+        throw new ServerException(ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, "Unexpected path %s", path);
     }
 
     private NodeIdentifierWithPredicates prepareNodeWithPredicates(final SchemaInferenceStack stack, final QName qname,
-            final @NonNull ListSchemaNode schema, final List<@NonNull String> keyValues) {
+            final @NonNull ListSchemaNode schema, final List<@NonNull String> keyValues) throws ServerException {
         final var keyDef = schema.getKeyDefinition();
         final var keySize = keyDef.size();
         final var varSize = keyValues.size();
         if (keySize != varSize) {
-            throw new RestconfDocumentedException(
-                "Schema for " + qname + " requires " + keySize + " key values, " + varSize + " supplied",
-                ErrorType.PROTOCOL, keySize > varSize ? ErrorTag.MISSING_ATTRIBUTE : ErrorTag.UNKNOWN_ATTRIBUTE);
+            throw new ServerException(ErrorType.PROTOCOL,
+                keySize > varSize ? ErrorTag.MISSING_ATTRIBUTE : ErrorTag.UNKNOWN_ATTRIBUTE,
+                "Schema for %s requires %s key values, %s supplied", qname, keySize, varSize);
         }
 
         final var values = ImmutableMap.<QName, Object>builderWithExpectedSize(keySize);
@@ -307,7 +303,7 @@ public final class ApiPathNormalizer implements DatabindAware, PointNormalizer {
     }
 
     private Object prepareValueByType(final SchemaInferenceStack stack, final DataSchemaNode schemaNode,
-            final @NonNull String value) {
+            final @NonNull String value) throws ServerException {
         if (schemaNode instanceof TypedDataSchemaNode typedSchema) {
             return parserJsonValue(stack, typedSchema, value);
         }
@@ -315,7 +311,7 @@ public final class ApiPathNormalizer implements DatabindAware, PointNormalizer {
     }
 
     private Object parserJsonValue(final SchemaInferenceStack stack, final TypedDataSchemaNode schemaNode,
-            final String value) {
+            final String value) throws ServerException {
         // As per https://www.rfc-editor.org/rfc/rfc8040#page-29:
         //            The syntax for
         //            "api-identifier" and "key-value" MUST conform to the JSON identifier
@@ -326,17 +322,17 @@ public final class ApiPathNormalizer implements DatabindAware, PointNormalizer {
         try {
             return databind.jsonCodecs().codecFor(schemaNode, stack).parseValue(value);
         } catch (IllegalArgumentException e) {
-            throw new RestconfDocumentedException("Invalid value '" + value + "' for " + schemaNode.getQName(),
-                ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e);
+            throw new ServerException(ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
+                "Invalid value '" + value + "' for " + schemaNode.getQName(), e);
         }
     }
 
-    private @NonNull QNameModule resolveNamespace(final String moduleName) {
+    private @NonNull QNameModule resolveNamespace(final String moduleName) throws ServerException {
         final var it = databind.modelContext().findModuleStatements(moduleName).iterator();
         if (it.hasNext()) {
             return it.next().localQNameModule();
         }
-        throw new RestconfDocumentedException("Failed to lookup for module with name '" + moduleName + "'.",
-            ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
+        throw new ServerException(ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT,
+            "Failed to lookup for module with name '%s'.", moduleName);
     }
 }
index 86a46006c56e62a426cccafcaa7bd55b9af6240f..ddf09f6baa991c50e1f3431beeb790cfcac4ba08 100644 (file)
@@ -9,9 +9,9 @@ package org.opendaylight.restconf.server.spi;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.opendaylight.restconf.api.ApiPath;
-import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.server.api.DatabindPath.Data;
 import org.opendaylight.restconf.server.api.PatchBody.ResourceContext;
+import org.opendaylight.restconf.server.api.ServerException;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 
@@ -25,7 +25,7 @@ public final class DefaultResourceContext extends ResourceContext {
     }
 
     @Override
-    protected ResourceContext resolveRelative(final ApiPath apiPath) {
+    protected ResourceContext resolveRelative(final ApiPath apiPath) throws ServerException {
         // If subResource is empty just return this resource
         if (apiPath.isEmpty()) {
             return this;
@@ -45,7 +45,7 @@ public final class DefaultResourceContext extends ResourceContext {
         if (resolved instanceof Data dataPath) {
             return new DefaultResourceContext(dataPath);
         }
-        throw new RestconfDocumentedException("Sub-resource '" + apiPath + "' resolves to non-data " + resolved,
-            ErrorType.PROTOCOL, ErrorTag.DATA_MISSING);
+        throw new ServerException(ErrorType.PROTOCOL, ErrorTag.DATA_MISSING,
+            "Sub-resource '%s' resolves to non-data %s", apiPath, resolved);
     }
 }
index 723280eddadcf07b210200fc843e617c506209cb..9f44a7dc0e41e36424c2832774ecd14141eaa9bb 100644 (file)
@@ -18,9 +18,9 @@ import java.util.Map.Entry;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.restconf.api.FormattableBody;
-import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.common.errors.RestconfFuture;
 import org.opendaylight.restconf.server.api.DatabindPath.Rpc;
+import org.opendaylight.restconf.server.api.ServerException;
 import org.opendaylight.restconf.server.api.ServerRequest;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
@@ -73,8 +73,8 @@ public final class OperationsResource implements HttpGetResource {
         final Rpc path;
         try {
             path = pathNormalizer.normalizeRpcPath(apiPath);
-        } catch (RestconfDocumentedException e) {
-            return RestconfFuture.failed(e);
+        } catch (ServerException e) {
+            return RestconfFuture.failed(e.toLegacy());
         }
         return RestconfFuture.of(new OneOperation(path.inference().modelContext(), path.rpc().argument()));
     }
index f482f50b42af94685963fd6facc0fed0be3db7ed..d9eea3e190b2826f3dd54bca055fb959f7ea1000 100644 (file)
@@ -36,7 +36,9 @@ import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.restconf.common.patch.PatchContext;
 import org.opendaylight.restconf.nb.rfc8040.AbstractInstanceIdentifierTest;
 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy;
+import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy.StrategyAndPath;
 import org.opendaylight.restconf.server.api.PatchBody;
+import org.opendaylight.restconf.server.api.ServerException;
 import org.opendaylight.restconf.server.spi.DefaultResourceContext;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 
@@ -99,7 +101,12 @@ abstract class AbstractPatchBodyTest extends AbstractInstanceIdentifierTest {
 
         final var strategy = new MdsalRestconfStrategy(IID_DATABIND, dataBroker, ImmutableMap.of(), null, null, null,
             mountPointService);
-        final var stratAndPath = strategy.resolveStrategyPath(apiPath);
+        final StrategyAndPath stratAndPath;
+        try {
+            stratAndPath = strategy.resolveStrategyPath(apiPath);
+        } catch (ServerException e) {
+            throw new AssertionError(e);
+        }
 
         try (var body = bodyConstructor.apply(stringInputStream(patchBody))) {
             return body.toPatchContext(new DefaultResourceContext(stratAndPath.path()));
index 1825ebae1d8906b43c3ea9146c611389eadba62f..65f0ede627919ef81de7811306e3ee6eac010492 100644 (file)
@@ -29,8 +29,10 @@ import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.common.errors.RestconfError;
 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy;
+import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy.StrategyAndPath;
 import org.opendaylight.restconf.server.api.DatabindContext;
 import org.opendaylight.restconf.server.api.ResourceBody;
+import org.opendaylight.restconf.server.api.ServerException;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -87,7 +89,12 @@ abstract class AbstractResourceBodyTest extends AbstractBodyTest {
 
         final var strategy = new MdsalRestconfStrategy(DATABIND, dataBroker, ImmutableMap.of(), null, null, null,
             mountPointService);
-        final var stratAndPath = strategy.resolveStrategyPath(apiPath);
+        final StrategyAndPath stratAndPath;
+        try {
+            stratAndPath = strategy.resolveStrategyPath(apiPath);
+        } catch (ServerException e) {
+            throw new AssertionError(e);
+        }
 
         try (var body = bodyConstructor.apply(stringInputStream(patchBody))) {
             return body.toNormalizedNode(stratAndPath.path());
index 5fc1b353abe1ac68b2b9aace59468bdded69e1e3..41ea0e1a13ee553a6dfb115c06983c5710b0013f 100644 (file)
@@ -9,6 +9,7 @@ package org.opendaylight.restconf.nb.rfc8040.rests.transactions;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNotSame;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -45,11 +46,11 @@ import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.restconf.api.ErrorMessage;
 import org.opendaylight.restconf.api.query.ContentParam;
 import org.opendaylight.restconf.api.query.WithDefaultsParam;
-import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy.StrategyAndTail;
 import org.opendaylight.restconf.server.api.DatabindContext;
 import org.opendaylight.restconf.server.api.PatchStatusContext;
 import org.opendaylight.restconf.server.api.PatchStatusEntity;
+import org.opendaylight.restconf.server.api.ServerException;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -419,7 +420,7 @@ public final class MdsalRestconfStrategyTest extends AbstractRestconfStrategyTes
     }
 
     @Test
-    public void testGetRestconfStrategyLocal() {
+    public void testGetRestconfStrategyLocal() throws Exception {
         final var strategy = jukeboxStrategy();
         assertEquals(new StrategyAndTail(strategy, ApiPath.empty()), strategy.resolveStrategy(ApiPath.empty()));
     }
@@ -472,13 +473,12 @@ public final class MdsalRestconfStrategyTest extends AbstractRestconfStrategyTes
         final var strategy = jukeboxStrategy();
         final var mountPath = ApiPath.parse("yang-ext:mount");
 
-        final var ex = assertThrows(RestconfDocumentedException.class, () -> strategy.resolveStrategy(mountPath));
-        final var errors = ex.getErrors();
-        assertEquals(1, errors.size());
-        final var error = errors.get(0);
-        assertEquals(ErrorType.APPLICATION, error.getErrorType());
-        assertEquals(ErrorTag.OPERATION_FAILED, error.getErrorTag());
-        assertEquals("Could not find a supported access interface in mount point", error.getErrorMessage());
-        assertEquals(JUKEBOX_IID, error.getErrorPath());
+        final var error = assertThrows(ServerException.class, () -> strategy.resolveStrategy(mountPath)).error();
+        assertEquals(ErrorType.APPLICATION, error.type());
+        assertEquals(ErrorTag.OPERATION_FAILED, error.tag());
+        assertEquals(new ErrorMessage("Could not find a supported access interface in mount point"), error.message());
+        final var path = error.path();
+        assertNotNull(path);
+        assertEquals(JUKEBOX_IID, path.path());
     }
 }
index 7229afa2bd1498a012c2bf9cbeebc35914229e8a..fe3b6eedf96accbd31ea28e1d6a7f7082a484b83 100644 (file)
@@ -12,6 +12,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
 
 import org.junit.jupiter.api.Test;
+import org.opendaylight.restconf.api.ErrorMessage;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 
@@ -58,7 +59,10 @@ class ServerExceptionTest {
         final var ex = new ServerException("some message", cause);
         assertEquals("some message", ex.getMessage());
         assertSame(cause, ex.getCause());
-        assertEquals(new ServerError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, "some message"), ex.error());
+        assertEquals(
+            new ServerError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, new ErrorMessage("some message"), null,
+                null, new ServerErrorInfo("cause message")),
+            ex.error());
     }
 
     @Test
@@ -67,7 +71,8 @@ class ServerExceptionTest {
         final var ex = new ServerException("some message", cause);
         assertEquals("some message", ex.getMessage());
         assertSame(cause, ex.getCause());
-        assertEquals(new ServerError(ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, "some message"), ex.error());
+        assertEquals(new ServerError(ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, new ErrorMessage("some message"),
+            null, null, new ServerErrorInfo("cause message")), ex.error());
     }
 
     @Test
@@ -76,7 +81,9 @@ class ServerExceptionTest {
         final var ex = new ServerException("some message", cause);
         assertEquals("some message", ex.getMessage());
         assertSame(cause, ex.getCause());
-        assertEquals(new ServerError(ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED, "some message"),
+        assertEquals(
+            new ServerError(ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED, new ErrorMessage("some message"),
+                null, null, new ServerErrorInfo("cause message")),
             ex.error());
     }
 
index ae84e0450d0ef20777afc52bdff6d839ddfcab69..bab88e8b8dd547040dad8c1cba61c2026ac2f3f8 100644 (file)
@@ -20,6 +20,7 @@ import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.common.errors.RestconfError;
 import org.opendaylight.restconf.server.api.DatabindContext;
 import org.opendaylight.restconf.server.api.DatabindPath.Data;
+import org.opendaylight.restconf.server.api.ServerException;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -353,7 +354,11 @@ class ApiPathCanonizerTest {
     }
 
     private static YangInstanceIdentifier assertNormalized(final String str) {
-        return assertInstanceOf(Data.class, NORMALIZER.normalizePath(newApiPath(str))).instance();
+        try {
+            return assertInstanceOf(Data.class, NORMALIZER.normalizePath(newApiPath(str))).instance();
+        } catch (ServerException e) {
+            throw new AssertionError(e);
+        }
     }
 
     private static RestconfError assertError(final YangInstanceIdentifier path) {
index f28bc016645d36ca7677d2e4688bbebe69e9cfc9..134f409728bbe74925f2ee50328a4d45f3c601c1 100644 (file)
@@ -16,11 +16,13 @@ import com.google.common.collect.ImmutableMap;
 import java.text.ParseException;
 import org.junit.jupiter.api.Test;
 import org.opendaylight.restconf.api.ApiPath;
-import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
-import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.restconf.api.ErrorMessage;
 import org.opendaylight.restconf.server.api.DatabindContext;
 import org.opendaylight.restconf.server.api.DatabindPath.Action;
 import org.opendaylight.restconf.server.api.DatabindPath.Data;
+import org.opendaylight.restconf.server.api.ServerError;
+import org.opendaylight.restconf.server.api.ServerErrorInfo;
+import org.opendaylight.restconf.server.api.ServerException;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -228,9 +230,9 @@ class ApiPathNormalizerTest {
     @Test
     void prepareQnameNotExistingPrefixNegativeTest() {
         final var error = assertErrorPath("not-existing:contA");
-        assertEquals("Failed to lookup for module with name 'not-existing'.", error.getErrorMessage());
-        assertEquals(ErrorType.PROTOCOL, error.getErrorType());
-        assertEquals(ErrorTag.UNKNOWN_ELEMENT, error.getErrorTag());
+        assertEquals(new ErrorMessage("Failed to lookup for module with name 'not-existing'."), error.message());
+        assertEquals(ErrorType.PROTOCOL, error.type());
+        assertEquals(ErrorTag.UNKNOWN_ELEMENT, error.tag());
     }
 
     /**
@@ -241,9 +243,10 @@ class ApiPathNormalizerTest {
     @Test
     public void prepareQnameNotValidContainerNameNegativeTest() {
         final var error = assertErrorPath("deserializer-test:contA/leafB");
-        assertEquals("Schema for '(deserializer:test?revision=2016-06-06)leafB' not found", error.getErrorMessage());
-        assertEquals(ErrorType.PROTOCOL, error.getErrorType());
-        assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
+        assertEquals(new ErrorMessage("Schema for '(deserializer:test?revision=2016-06-06)leafB' not found"),
+            error.message());
+        assertEquals(ErrorType.PROTOCOL, error.type());
+        assertEquals(ErrorTag.DATA_MISSING, error.tag());
     }
 
     /**
@@ -254,9 +257,10 @@ class ApiPathNormalizerTest {
     @Test
     void prepareQnameNotValidListNameNegativeTest() {
         final var error = assertErrorPath("deserializer-test:list-no-key/disabled=false");
-        assertEquals("Schema for '(deserializer:test?revision=2016-06-06)disabled' not found", error.getErrorMessage());
-        assertEquals(ErrorType.PROTOCOL, error.getErrorType());
-        assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
+        assertEquals(new ErrorMessage("Schema for '(deserializer:test?revision=2016-06-06)disabled' not found"),
+            error.message());
+        assertEquals(ErrorType.PROTOCOL, error.type());
+        assertEquals(ErrorTag.DATA_MISSING, error.tag());
     }
 
     /**
@@ -266,11 +270,11 @@ class ApiPathNormalizerTest {
     @Test
     void prepareIdentifierNotKeyedEntryNegativeTest() {
         final var error = assertErrorPath("deserializer-test:list-one-key");
-        assertEquals("""
+        assertEquals(new ErrorMessage("""
             Entry '(deserializer:test?revision=2016-06-06)list-one-key' requires key or value predicate to be \
-            present.""", error.getErrorMessage());
-        assertEquals(ErrorType.PROTOCOL, error.getErrorType());
-        assertEquals(ErrorTag.MISSING_ATTRIBUTE, error.getErrorTag());
+            present."""), error.message());
+        assertEquals(ErrorType.PROTOCOL, error.type());
+        assertEquals(ErrorTag.MISSING_ATTRIBUTE, error.tag());
     }
 
     /**
@@ -280,11 +284,11 @@ class ApiPathNormalizerTest {
     @Test
     void deserializeKeysEndsWithCommaTooManyNegativeTest() {
         final var error = assertErrorPath("deserializer-test:list-multiple-keys=value,100,false,");
-        assertEquals("""
-            Schema for (deserializer:test?revision=2016-06-06)list-multiple-keys requires 3 key values, 4 supplied""",
-            error.getErrorMessage());
-        assertEquals(ErrorType.PROTOCOL, error.getErrorType());
-        assertEquals(ErrorTag.UNKNOWN_ATTRIBUTE, error.getErrorTag());
+        assertEquals(new ErrorMessage("""
+            Schema for (deserializer:test?revision=2016-06-06)list-multiple-keys requires 3 key values, 4 supplied"""),
+            error.message());
+        assertEquals(ErrorType.PROTOCOL, error.type());
+        assertEquals(ErrorTag.UNKNOWN_ATTRIBUTE, error.tag());
     }
 
     /**
@@ -294,10 +298,12 @@ class ApiPathNormalizerTest {
     @Test
     void deserializeKeysEndsWithCommaIllegalNegativeTest() {
         final var error = assertErrorPath("deserializer-test:list-multiple-keys=value,100,");
-        assertEquals("Invalid value '' for (deserializer:test?revision=2016-06-06)enabled", error.getErrorMessage());
-        assertEquals(ErrorType.PROTOCOL, error.getErrorType());
-        assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
-        assertEquals("Invalid value '' for boolean type. Allowed values are 'true' and 'false'", error.getErrorInfo());
+        assertEquals(new ErrorMessage("Invalid value '' for (deserializer:test?revision=2016-06-06)enabled"),
+            error.message());
+        assertEquals(ErrorType.PROTOCOL, error.type());
+        assertEquals(ErrorTag.INVALID_VALUE, error.tag());
+        assertEquals(new ServerErrorInfo("Invalid value '' for boolean type. Allowed values are 'true' and 'false'"),
+            error.info());
     }
 
     /**
@@ -331,11 +337,11 @@ class ApiPathNormalizerTest {
     @Test
     void notAllListKeysEncodedNegativeTest() {
         final var error = assertErrorPath("deserializer-test:list-multiple-keys=%3Afoo/string-value");
-        assertEquals("""
-            Schema for (deserializer:test?revision=2016-06-06)list-multiple-keys requires 3 key values, 1 supplied""",
-            error.getErrorMessage());
-        assertEquals(ErrorType.PROTOCOL, error.getErrorType());
-        assertEquals(ErrorTag.MISSING_ATTRIBUTE, error.getErrorTag());
+        assertEquals(new ErrorMessage("""
+            Schema for (deserializer:test?revision=2016-06-06)list-multiple-keys requires 3 key values, 1 supplied"""),
+            error.message());
+        assertEquals(ErrorType.PROTOCOL, error.type());
+        assertEquals(ErrorTag.MISSING_ATTRIBUTE, error.tag());
     }
 
     /**
@@ -379,10 +385,10 @@ class ApiPathNormalizerTest {
     @Test
     void leafListMissingKeyNegativeTest() {
         final var error = assertErrorPath("deserializer-test:leaf-list-0=");
-        assertEquals("Invalid value '' for (deserializer:test?revision=2016-06-06)leaf-list-0",
-            error.getErrorMessage());
-        assertEquals(ErrorType.PROTOCOL, error.getErrorType());
-        assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
+        assertEquals(new ErrorMessage("Invalid value '' for (deserializer:test?revision=2016-06-06)leaf-list-0"),
+            error.message());
+        assertEquals(ErrorType.PROTOCOL, error.type());
+        assertEquals(ErrorTag.INVALID_VALUE, error.tag());
     }
 
     /**
@@ -441,20 +447,25 @@ class ApiPathNormalizerTest {
             "deserializer-test-included:refs/list-with-identityref=deserializer-test:derived-identity/foo");
     }
 
-    private static RestconfError assertErrorPath(final String path) {
+    private static ServerError assertErrorPath(final String path) {
         final var apiPath = assertApiPath(path);
-        final var ex = assertThrows(RestconfDocumentedException.class, () -> NORMALIZER.normalizePath(apiPath));
-        final var errors = ex.getErrors();
-        assertEquals(1, errors.size());
-        return errors.get(0);
+        return assertThrows(ServerException.class, () -> NORMALIZER.normalizePath(apiPath)).error();
     }
 
     private static Action assertNormalizedAction(final String path) {
-        return assertInstanceOf(Action.class, NORMALIZER.normalizePath(assertApiPath(path)));
+        try {
+            return assertInstanceOf(Action.class, NORMALIZER.normalizePath(assertApiPath(path)));
+        } catch (ServerException e) {
+            throw new AssertionError(e);
+        }
     }
 
     private static YangInstanceIdentifier assertNormalizedPath(final String path) {
-        return assertInstanceOf(Data.class, NORMALIZER.normalizePath(assertApiPath(path))).instance();
+        try {
+            return assertInstanceOf(Data.class, NORMALIZER.normalizePath(assertApiPath(path))).instance();
+        } catch (ServerException e) {
+            throw new AssertionError(e);
+        }
     }
 
     private static ApiPath assertApiPath(final String path) {
index af3ad22c70d9a1abb7a9e5a71880d80ab95f75bc..495f196efcd59ceb0ef4637868f0f93979b6b76e 100644 (file)
@@ -13,9 +13,11 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import java.text.ParseException;
 import org.junit.jupiter.api.Test;
 import org.opendaylight.restconf.api.ApiPath;
-import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
-import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.restconf.api.ErrorMessage;
 import org.opendaylight.restconf.server.api.DatabindContext;
+import org.opendaylight.restconf.server.api.ServerError;
+import org.opendaylight.restconf.server.api.ServerErrorInfo;
+import org.opendaylight.restconf.server.api.ServerException;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -97,39 +99,39 @@ class NC1265Test {
     @Test
     void noslashInstanceIdentifierKey() {
         final var error = assertRestconfError("nc1265:bar=nc1265:baz=123");
-        assertEquals(ErrorType.PROTOCOL, error.getErrorType());
-        assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
-        assertEquals("Invalid value 'nc1265:baz=123' for (nc1265)key", error.getErrorMessage());
-        assertEquals("""
+        assertEquals(ErrorType.PROTOCOL, error.type());
+        assertEquals(ErrorTag.INVALID_VALUE, error.tag());
+        assertEquals(new ErrorMessage("Invalid value 'nc1265:baz=123' for (nc1265)key"), error.message());
+        assertEquals(new ServerErrorInfo("""
             Could not parse Instance Identifier 'nc1265:baz=123'. Offset: 0 : Reason: Identifier must start with '/'.\
-            """, error.getErrorInfo());
+            """), error.info());
     }
 
     @Test
     void malformedInstanceIdentifierKey() {
         final var error = assertRestconfError("nc1265:bar=%2Fnc1265:baz[key='abc']");
-        assertEquals(ErrorType.PROTOCOL, error.getErrorType());
-        assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
-        assertEquals("Invalid value '/nc1265:baz[key='abc']' for (nc1265)key", error.getErrorMessage());
-        assertEquals("""
+        assertEquals(ErrorType.PROTOCOL, error.type());
+        assertEquals(ErrorTag.INVALID_VALUE, error.tag());
+        assertEquals(new ErrorMessage("Invalid value '/nc1265:baz[key='abc']' for (nc1265)key"), error.message());
+        assertEquals(new ServerErrorInfo("""
             Incorrect lexical representation of integer value: abc.
             An integer value can be defined as:
               - a decimal number,
               - a hexadecimal number (prefix 0x),%n  - an octal number (prefix 0).
-            Signed values are allowed. Spaces between digits are NOT allowed.""", error.getErrorInfo());
+            Signed values are allowed. Spaces between digits are NOT allowed."""), error.info());
     }
 
     private static void assertNormalized(final YangInstanceIdentifier expected, final String apiPath) {
-        assertEquals(expected, NORMALIZER.normalizeDataPath(assertApiPath(apiPath)).instance());
+        try {
+            assertEquals(expected, NORMALIZER.normalizeDataPath(assertApiPath(apiPath)).instance());
+        } catch (ServerException e) {
+            throw new AssertionError(e);
+        }
     }
 
-    private static RestconfError assertRestconfError(final String apiPath) {
+    private static ServerError assertRestconfError(final String apiPath) {
         final var parsed = assertApiPath(apiPath);
-
-        final var ex = assertThrows(RestconfDocumentedException.class, () -> NORMALIZER.normalizeDataPath(parsed));
-        final var errors = ex.getErrors();
-        assertEquals(1, errors.size());
-        return errors.get(0);
+        return assertThrows(ServerException.class, () -> NORMALIZER.normalizeDataPath(parsed)).error();
     }
 
     private static ApiPath assertApiPath(final String apiPath) {