Remove RestconfDocumentedException.status
[netconf.git] / restconf / restconf-common / src / main / java / org / opendaylight / restconf / common / errors / RestconfDocumentedException.java
index ac010cd659f60cd671e01e4a60eb6503bcf58895..c04d9d98e8b5ef4a3df9c7d53351622f1c98ff21 100644 (file)
@@ -9,12 +9,9 @@ package org.opendaylight.restconf.common.errors;
 
 import static java.util.Objects.requireNonNull;
 
-import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.core.Response.Status;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.restconf.common.ErrorTags;
@@ -22,24 +19,26 @@ import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.common.OperationFailedException;
 import org.opendaylight.yangtools.yang.common.RpcError;
-import org.opendaylight.yangtools.yang.common.YangError;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangNetconfError;
+import org.opendaylight.yangtools.yang.data.api.YangNetconfErrorAware;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 
 /**
- * Unchecked exception to communicate error information, as defined in the ietf restcong draft, to be sent to the
- * client.
- *
- * <p>
- * See also <a href="https://tools.ietf.org/html/draft-bierman-netconf-restconf-02">RESTCONF</a>
+ * Unchecked exception to communicate error information, as defined
+ * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-3.9">"errors" YANG Data Template</a>.
  *
  * @author Devin Avery
  * @author Thomas Pantelis
  */
-public class RestconfDocumentedException extends WebApplicationException {
-    private static final long serialVersionUID = 1L;
+public class RestconfDocumentedException extends RuntimeException {
+    @java.io.Serial
+    private static final long serialVersionUID = 3L;
+
+    private final List<RestconfError> errors;
 
-    private final ImmutableList<RestconfError> errors;
-    private final Status status;
+    // FIXME: this field should be non-null
+    private final transient @Nullable EffectiveModelContext modelContext;
 
     /**
      * Constructs an instance with an error message. The error type defaults to APPLICATION and the error tag defaults
@@ -128,14 +127,13 @@ public class RestconfDocumentedException extends WebApplicationException {
         // FIXME: We override getMessage so supplied message is lost for any public access
         // this was lost also in original code.
         super(cause);
-        if (!errors.isEmpty()) {
-            this.errors = ImmutableList.copyOf(errors);
+        if (errors.isEmpty()) {
+            this.errors = List.of(new RestconfError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, message));
         } else {
-            this.errors = ImmutableList.of(
-                new RestconfError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, message));
+            this.errors = List.copyOf(errors);
         }
 
-        status = null;
+        modelContext = null;
     }
 
     /**
@@ -146,28 +144,32 @@ public class RestconfDocumentedException extends WebApplicationException {
         this(message, cause, convertToRestconfErrors(rpcErrors));
     }
 
-    /**
-     * Constructs an instance with an HTTP status and no error information.
-     *
-     * @param status
-     *            the HTTP status.
-     */
-    public RestconfDocumentedException(final Status status) {
-        errors = ImmutableList.of();
-        this.status = requireNonNull(status, "Status can't be null");
+    public RestconfDocumentedException(final Throwable cause, final RestconfError error) {
+        super(cause);
+        errors = List.of(error);
+        modelContext = null;
     }
 
-    public RestconfDocumentedException(final Throwable cause, final RestconfError error) {
-        super(cause, ErrorTags.statusOf(error.getErrorTag()));
-        errors = ImmutableList.of(error);
-        status = null;
+    public RestconfDocumentedException(final Throwable cause, final RestconfError error,
+            final EffectiveModelContext modelContext) {
+        super(cause);
+        errors = List.of(error);
+        this.modelContext = requireNonNull(modelContext);
+    }
+
+    public RestconfDocumentedException(final Throwable cause, final List<RestconfError> errors) {
+        super(cause);
+        if (errors.isEmpty()) {
+            throw new IllegalArgumentException("At least one error is required");
+        }
+        this.errors = List.copyOf(errors);
+        modelContext = null;
     }
 
     public static RestconfDocumentedException decodeAndThrow(final String message,
             final OperationFailedException cause) {
         for (final RpcError error : cause.getErrorList()) {
-            if (error.getErrorType() == RpcError.ErrorType.TRANSPORT
-                    && error.getTag().equals(ErrorTag.RESOURCE_DENIED.elementBody())) {
+            if (error.getErrorType() == ErrorType.TRANSPORT && error.getTag().equals(ErrorTag.RESOURCE_DENIED)) {
                 throw new RestconfDocumentedException(error.getMessage(), ErrorType.TRANSPORT,
                     ErrorTags.RESOURCE_DENIED_TRANSPORT, cause);
             }
@@ -230,40 +232,48 @@ public class RestconfDocumentedException extends WebApplicationException {
     }
 
     /**
-     * Throw an instance of this exception if the specified exception has a {@link YangError} attachment.
+     * Throw an instance of this exception if the specified exception has a {@link YangNetconfError} attachment.
      *
      * @param cause Proposed cause of a RestconfDocumented exception
      */
     public static void throwIfYangError(final Throwable cause) {
-        if (cause instanceof YangError) {
-            final YangError error = (YangError) cause;
-            throw new RestconfDocumentedException(cause, new RestconfError(error.getErrorType().toNetconf(),
-                new ErrorTag(error.getErrorTag()), error.getErrorMessage().orElse(null),
-                error.getErrorAppTag().orElse(null)));
+        if (cause instanceof YangNetconfErrorAware infoAware) {
+            throw new RestconfDocumentedException(cause, infoAware.getNetconfErrors().stream()
+                .map(error -> new RestconfError(error.type(), error.tag(), error.message(), error.appTag(),
+                    // FIXME: pass down error info
+                    null, error.path()))
+                .toList());
         }
     }
 
     private static List<RestconfError> convertToRestconfErrors(final Collection<? extends RpcError> rpcErrors) {
-        final List<RestconfError> errorList = new ArrayList<>();
-        if (rpcErrors != null) {
-            for (RpcError rpcError : rpcErrors) {
-                errorList.add(new RestconfError(rpcError));
-            }
+        if (rpcErrors == null || rpcErrors.isEmpty()) {
+            return List.of();
         }
 
+        final var errorList = new ArrayList<RestconfError>();
+        for (var rpcError : rpcErrors) {
+            errorList.add(new RestconfError(rpcError));
+        }
         return errorList;
     }
 
-    public List<RestconfError> getErrors() {
-        return errors;
+    @Override
+    public String getMessage() {
+        return "errors: " + errors;
     }
 
-    public Status getStatus() {
-        return status;
+    /**
+     * Reference to {@link EffectiveModelContext} in which this exception was generated. This method will return
+     * {@code null} if this exception was serialized or if the context is not available.
+     *
+     * @return Reference model context
+     */
+    public @Nullable EffectiveModelContext modelContext() {
+        return modelContext;
     }
 
-    @Override
-    public String getMessage() {
-        return "errors: " + errors + (status != null ? ", status: " + status : "");
+    public List<RestconfError> getErrors() {
+        return errors;
     }
 }