All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.nike.backstopper.exception.ApiException Maven / Gradle / Ivy

There is a newer version: 2.0.1
Show newest version
package com.nike.backstopper.exception;

import com.nike.backstopper.apierror.ApiError;
import com.nike.internal.util.Pair;
import com.nike.internal.util.StringUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import static java.util.Collections.singletonList;

/**
 * Generic API RuntimeException to explicitly invoke the error handling system. Most of the time you'll want to create
 * an instance using the {@link ApiException.Builder}, e.g.
 * {@code
 * ApiException.newBuilder().withApiErrors(SOME_ERROR, OTHER_ERROR).withExceptionMessage("super useful message").build()}.
 * The builder has many options that directly affect the response and what is logged, so take a close look at the
 * available methods.
 *
 * @author Nic Munroe
 */
public class ApiException extends RuntimeException {

    /**
     * The {@link ApiError}s associated with this instance. Will never be null or empty.
     */
    private final List apiErrors;
    /**
     * Any extra details you want logged when this error is handled. Will never be null, but might be empty.
     * NOTE: This will always be a mutable list so it can be modified at any time.
     */
    private final List> extraDetailsForLogging;
    /**
     * Any extra headers you want sent to the caller when this error is handled. Will never be null, but might be empty.
     * NOTE: This will always be a mutable list so it can be modified at any time.
     */
    private final List>> extraResponseHeaders;
    /**
     * Allows users to override the default behavior (logging stack traces for 5xx errors but not 4xx errors) and
     * instead force stack trace on/off if they want to override the default 4xx vs. 5xx decision behavior.
     */
    private final StackTraceLoggingBehavior stackTraceLoggingBehavior;

    /**
     * Handles the simple common case where you just want to throw a single {@link ApiError} and nothing else.
     */
    public ApiException(ApiError error) {
        super(extractMessage(error));
        if (error == null)
            throw new IllegalArgumentException("error cannot be null");
        this.apiErrors = new ArrayList<>(singletonList(error));
        this.extraDetailsForLogging = new ArrayList<>();
        this.extraResponseHeaders = new ArrayList<>();
        this.stackTraceLoggingBehavior = StackTraceLoggingBehavior.DEFER_TO_DEFAULT_BEHAVIOR;
    }

    /**
     * Initializes this instance based on the values from the given {@link Builder}.
     *
     * @param builder The {@link Builder} to use for initializing this instance's fields.
     */
    public ApiException(Builder builder) {
        super(extractMessage(builder.apiErrors, builder.message));

        if (builder.cause != null) {
            this.initCause(builder.cause);
        }

        if (builder.apiErrors.isEmpty()) {
            throw new IllegalArgumentException("The Builder's apiErrors cannot be empty");
        }

        this.apiErrors = new ArrayList<>(builder.apiErrors);
        this.extraDetailsForLogging = new ArrayList<>(builder.extraDetailsForLogging);
        this.extraResponseHeaders = new ArrayList<>(builder.extraResponseHeaders);
        this.stackTraceLoggingBehavior = (builder.stackTraceLoggingBehavior == null)
                                         ? StackTraceLoggingBehavior.DEFER_TO_DEFAULT_BEHAVIOR
                                         : builder.stackTraceLoggingBehavior;
    }

    /**
     * Creates an instance on top of {@link Exception#Exception(String)} super constructor. You can safely pass in null
     * for message if you have no message. If another exception caused this to be thrown then you'll want
     * {@link #ApiException(List, List, String, Throwable)} instead so that the proper super constructor is used.
     *
     * 

NOTE: Most of the time you wouldn't want to use this constructor directly - use the * {@link ApiException.Builder} instead. * * @deprecated Use the {@link Builder} (or the {@link ApiException#ApiException(Builder) constructor} that takes a * builder) instead. */ @Deprecated public ApiException(List apiErrors, List> extraDetailsForLogging, String message) { this(apiErrors, extraDetailsForLogging, null, message); } /** * Creates an instance on top of {@link Exception#Exception(String)} super constructor. You can safely pass in null * for message if you have no message. If another exception caused this to be thrown then you'll want * {@link #ApiException(List, List, List, String, Throwable)} instead so that the proper super constructor is used. * *

NOTE: Most of the time you wouldn't want to use this constructor directly - use the * {@link ApiException.Builder} instead. * * @deprecated Use the {@link Builder} (or the {@link ApiException#ApiException(Builder) constructor} that takes a * builder) instead. */ @Deprecated public ApiException(List apiErrors, List> extraDetailsForLogging, List>> extraResponseHeaders, String message) { super(extractMessage(apiErrors, message)); if (apiErrors == null || apiErrors.isEmpty()) throw new IllegalArgumentException("apiErrors cannot be null or empty"); if (extraDetailsForLogging == null) extraDetailsForLogging = Collections.emptyList(); if (extraResponseHeaders == null) extraResponseHeaders = Collections.emptyList(); this.apiErrors = new ArrayList<>(apiErrors); this.extraDetailsForLogging = new ArrayList<>(extraDetailsForLogging); this.extraResponseHeaders = new ArrayList<>(extraResponseHeaders); this.stackTraceLoggingBehavior = StackTraceLoggingBehavior.DEFER_TO_DEFAULT_BEHAVIOR; } /** * Creates an instance on top of {@link Exception#Exception(String, Throwable)} super constructor. You can safely * pass in null for message if you have no message. If another exception did *not* cause this to be thrown then * you'll want {@link #ApiException(List, List, String)} instead so that the proper super constructor is used. * *

NOTE: Most of the time you wouldn't want to use this constructor directly - use the * {@link ApiException.Builder} instead. * * @deprecated Use the {@link Builder} (or the {@link ApiException#ApiException(Builder) constructor} that takes a * builder) instead. */ @Deprecated public ApiException(List apiErrors, List> extraDetailsForLogging, String message, Throwable cause) { this(apiErrors, extraDetailsForLogging, null, message, cause); } /** * Creates an instance on top of {@link Exception#Exception(String, Throwable)} super constructor. You can safely * pass in null for message if you have no message. If another exception did *not* cause this to be thrown then * you'll want {@link #ApiException(List, List, List, String)} instead so that the proper super constructor is used. * *

NOTE: Most of the time you wouldn't want to use this constructor directly - use the * {@link ApiException.Builder} instead. * * @deprecated Use the {@link Builder} (or the {@link ApiException#ApiException(Builder) constructor} that takes a * builder) instead. */ @Deprecated public ApiException(List apiErrors, List> extraDetailsForLogging, List>> extraResponseHeaders, String message, Throwable cause) { super(extractMessage(apiErrors, message), cause); if (apiErrors == null || apiErrors.isEmpty()) throw new IllegalArgumentException("apiErrors cannot be null or empty"); if (extraDetailsForLogging == null) extraDetailsForLogging = Collections.emptyList(); if (extraResponseHeaders == null) extraResponseHeaders = Collections.emptyList(); this.apiErrors = new ArrayList<>(apiErrors); this.extraDetailsForLogging = new ArrayList<>(extraDetailsForLogging); this.extraResponseHeaders = new ArrayList<>(extraResponseHeaders); this.stackTraceLoggingBehavior = StackTraceLoggingBehavior.DEFER_TO_DEFAULT_BEHAVIOR; } /** * Shortcut for calling {@link ApiException.Builder#newBuilder()} directly. */ public static Builder newBuilder() { return Builder.newBuilder(); } /** * The {@link ApiError}s associated with this instance. Will never be null or empty. */ public List getApiErrors() { return apiErrors; } /** * Any extra details you want logged when this error is handled. Will never be null, but might be empty. */ public List> getExtraDetailsForLogging() { return extraDetailsForLogging; } /** * Any extra headers you want sent to the caller when this error is handled. Will never be null, but might be empty. */ public List>> getExtraResponseHeaders() { return extraResponseHeaders; } /** * Extracts message from input {@link ApiError}. Will return null if the input error is null */ protected static String extractMessage(ApiError error) { if (error == null) return null; return error.getMessage(); } /** * Extracts and joins all messages from the input List<{@link ApiError}> if the desired message is null. * * Will return null if the input error List is null */ protected static String extractMessage(List apiErrors, String desiredMessage) { if (desiredMessage != null) { return desiredMessage; } if (apiErrors == null || apiErrors.isEmpty()) { return null; } if (apiErrors.size() == 1) { return extractMessage(apiErrors.get(0)); } List apiErrorMessages = new ArrayList<>(apiErrors.size()); for (ApiError error : apiErrors) { apiErrorMessages.add(error.getMessage()); } return StringUtils.join(apiErrorMessages, ", ", "[", "]"); } /** * @return The desired stack trace logging behavior for this error. Will never be null. * See {@link StackTraceLoggingBehavior} for details on the options. */ public StackTraceLoggingBehavior getStackTraceLoggingBehavior() { return stackTraceLoggingBehavior; } /** * Builder for {@link ApiException}. */ @SuppressWarnings("WeakerAccess") public static class Builder { private List apiErrors = new ArrayList<>(); private List> extraDetailsForLogging = new ArrayList<>(); private List>> extraResponseHeaders = new ArrayList<>(); private String message; private Throwable cause; private StackTraceLoggingBehavior stackTraceLoggingBehavior; public Builder() {} public static Builder newBuilder() { return new Builder(); } /** * Adds the given errors to what will ultimately become {@link ApiException#apiErrors}. */ public Builder withApiErrors(Collection apiErrors) { this.apiErrors.addAll(apiErrors); return this; } /** * Adds the given errors to what will ultimately become {@link ApiException#apiErrors}. */ public Builder withApiErrors(ApiError... apiErrors) { return withApiErrors(Arrays.asList(apiErrors)); } /** * Adds the given logging details to what will ultimately become {@link ApiException#extraDetailsForLogging}. */ public Builder withExtraDetailsForLogging(Collection> extraDetailsForLogging) { this.extraDetailsForLogging.addAll(extraDetailsForLogging); return this; } /** * Adds the given logging details to what will ultimately become {@link ApiException#extraDetailsForLogging}. */ @SafeVarargs public final Builder withExtraDetailsForLogging(Pair... extraDetailsForLogging) { return withExtraDetailsForLogging(Arrays.asList(extraDetailsForLogging)); } /** * Adds the given response headers to what will ultimately become {@link ApiException#extraResponseHeaders}. */ public Builder withExtraResponseHeaders(Collection>> extraResponseHeaders) { this.extraResponseHeaders.addAll(extraResponseHeaders); return this; } /** * Adds the given response headers to what will ultimately become {@link ApiException#extraResponseHeaders}. */ @SafeVarargs public final Builder withExtraResponseHeaders(Pair>... extraResponseHeaders) { return withExtraResponseHeaders(Arrays.asList(extraResponseHeaders)); } /** * The given message will be used as part of the {@link Exception#Exception(String)} or * {@link Exception#Exception(String, Throwable)} super constructor. Could be used as context for what went * wrong if the API Errors aren't self explanatory. */ public Builder withExceptionMessage(String message) { this.message = message; return this; } /** * The given throwable will be applied to the {@link Exception#Exception(String, Throwable)} super constructor. * If another error caused this {@link ApiException} in the first place then you should definitely include it * here so it can be logged and help with debugging the issue. */ public Builder withExceptionCause(Throwable cause) { this.cause = cause; return this; } /** * Sets the given {@link StackTraceLoggingBehavior} for what will ultimately become * {@link ApiException#stackTraceLoggingBehavior}. */ public Builder withStackTraceLoggingBehavior(StackTraceLoggingBehavior stackTraceLoggingBehavior) { this.stackTraceLoggingBehavior = stackTraceLoggingBehavior; return this; } /** * Creates the {@link ApiException} from the data this builder contains. */ public ApiException build() { return new ApiException(this); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy