Remove RestconfDocumentedException.status
[netconf.git] / restconf / restconf-common / src / main / java / org / opendaylight / restconf / common / errors / RestconfDocumentedException.java
index 0d1868e03d3c9a933b9895e21527f0c103e4d1d0..c04d9d98e8b5ef4a3df9c7d53351622f1c98ff21 100644 (file)
@@ -5,37 +5,40 @@
  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
  * and is available at http://www.eclipse.org/legal/epl-v10.html
  */
-
 package org.opendaylight.restconf.common.errors;
 
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
+import static java.util.Objects.requireNonNull;
+
+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.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
-import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.restconf.common.ErrorTags;
+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.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 {
+public class RestconfDocumentedException extends RuntimeException {
+    @java.io.Serial
+    private static final long serialVersionUID = 3L;
 
-    private static final long serialVersionUID = 1L;
+    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
@@ -45,7 +48,7 @@ public class RestconfDocumentedException extends WebApplicationException {
      *            A string which provides a plain text string describing the error.
      */
     public RestconfDocumentedException(final String message) {
-        this(message, RestconfError.ErrorType.APPLICATION, RestconfError.ErrorTag.OPERATION_FAILED);
+        this(message, ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED);
     }
 
     /**
@@ -62,8 +65,7 @@ public class RestconfDocumentedException extends WebApplicationException {
      */
     public RestconfDocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
                                        final Throwable cause) {
-        this(cause, new RestconfError(errorType, errorTag, message, null,
-                cause.getMessage(), null));
+        this(cause, new RestconfError(errorType, errorTag, message, null, cause.getMessage(), null));
     }
 
     /**
@@ -107,8 +109,8 @@ public class RestconfDocumentedException extends WebApplicationException {
      *            The underlying exception cause.
      */
     public RestconfDocumentedException(final String message, final Throwable cause) {
-        this(cause, new RestconfError(RestconfError.ErrorType.APPLICATION, RestconfError.ErrorTag.OPERATION_FAILED,
-                message, null, cause.getMessage(), null));
+        this(cause, new RestconfError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, message, null,
+            cause.getMessage(), null));
     }
 
     /**
@@ -125,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(RestconfError.ErrorType.APPLICATION,
-                    RestconfError.ErrorTag.OPERATION_FAILED, message));
+            this.errors = List.copyOf(errors);
         }
 
-        status = null;
+        modelContext = null;
     }
 
     /**
@@ -143,46 +144,136 @@ public class RestconfDocumentedException extends WebApplicationException {
         this(message, cause, convertToRestconfErrors(rpcErrors));
     }
 
+    public RestconfDocumentedException(final Throwable cause, final RestconfError error) {
+        super(cause);
+        errors = List.of(error);
+        modelContext = 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() == ErrorType.TRANSPORT && error.getTag().equals(ErrorTag.RESOURCE_DENIED)) {
+                throw new RestconfDocumentedException(error.getMessage(), ErrorType.TRANSPORT,
+                    ErrorTags.RESOURCE_DENIED_TRANSPORT, cause);
+            }
+        }
+        throw new RestconfDocumentedException(message, cause, cause.getErrorList());
+    }
+
     /**
-     * Constructs an instance with an HTTP status and no error information.
+     * Throw an instance of this exception if an expression evaluates to true. If the expression evaluates to false,
+     * this method does nothing.
      *
-     * @param status
-     *            the HTTP status.
+     * @param expression Expression to be evaluated
+     * @param errorType The enumerated type indicating the layer where the error occurred.
+     * @param errorTag The enumerated tag representing a more specific error cause.
+     * @param format Format string, according to {@link String#format(String, Object...)}.
+     * @param args Format string arguments, according to {@link String#format(String, Object...)}
+     * @throws RestconfDocumentedException if the expression evaluates to true.
      */
-    public RestconfDocumentedException(final Status status) {
-        Preconditions.checkNotNull(status, "Status can't be null");
-        errors = ImmutableList.of();
-        this.status = status;
+    public static void throwIf(final boolean expression, final ErrorType errorType, final ErrorTag errorTag,
+            final @NonNull String format, final Object... args) {
+        if (expression) {
+            throw new RestconfDocumentedException(String.format(format, args), errorType, errorTag);
+        }
     }
 
-    public RestconfDocumentedException(final Throwable cause, final RestconfError error) {
-        super(cause);
-        Preconditions.checkNotNull(error, "RestconfError can't be null");
-        errors = ImmutableList.of(error);
-        status = null;
+    /**
+     * Throw an instance of this exception if an expression evaluates to true. If the expression evaluates to false,
+     * this method does nothing.
+     *
+     * @param expression Expression to be evaluated
+     * @param message error message
+     * @param errorType The enumerated type indicating the layer where the error occurred.
+     * @param errorTag The enumerated tag representing a more specific error cause.
+     * @throws RestconfDocumentedException if the expression evaluates to true.
+     */
+    public static void throwIf(final boolean expression, final @NonNull String message,
+            final ErrorType errorType, final ErrorTag errorTag) {
+        if (expression) {
+            throw new RestconfDocumentedException(message, errorType, errorTag);
+        }
+    }
+
+    /**
+     * Throw an instance of this exception if an object is null. If the object is non-null, it will
+     * be returned as the result of this method.
+     *
+     * @param obj Object reference to be checked
+     * @param errorType The enumerated type indicating the layer where the error occurred.
+     * @param errorTag The enumerated tag representing a more specific error cause.
+     * @param format Format string, according to {@link String#format(String, Object...)}.
+     * @param args Format string arguments, according to {@link String#format(String, Object...)}
+     * @throws RestconfDocumentedException if the expression evaluates to true.
+     */
+    public static <T> @NonNull T throwIfNull(final @Nullable T obj, final ErrorType errorType, final ErrorTag errorTag,
+            final @NonNull String format, final Object... args) {
+        if (obj == null) {
+            throw new RestconfDocumentedException(String.format(format, args), errorType, errorTag);
+        }
+        return obj;
+    }
+
+    /**
+     * 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 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 = Lists.newArrayList();
-        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;
     }
 }