ExceptionHelper.java

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package software.amazon.lambda.durable.util;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletionException;
import software.amazon.awssdk.services.lambda.model.ErrorObject;
import software.amazon.lambda.durable.serde.SerDes;

/** Utility class for handling exceptions */
public class ExceptionHelper {

    /**
     * Throws any exception as if it were unchecked using type erasure. This preserves the original exception type and
     * stack trace.
     *
     * @param exception the exception to throw
     * @param <T> the exception type (erased at runtime)
     * @throws T the exception as an unchecked exception
     */
    @SuppressWarnings("unchecked")
    public static <T extends Throwable> void sneakyThrow(Throwable exception) throws T {
        throw (T) exception;
    }

    /**
     * unwrap the exception that is wrapped by CompletionException
     *
     * @param throwable the throwable to unwrap
     * @return the original Throwable that is not a CompletionException
     */
    public static Throwable unwrapCompletableFuture(Throwable throwable) {
        while (throwable instanceof CompletionException) {
            throwable = throwable.getCause();
        }
        return throwable;
    }

    /**
     * build an ErrorObject from a Throwable
     *
     * @param throwable the Throwable from which to build the errorObject
     * @return the ErrorObject
     */
    public static ErrorObject buildErrorObject(Throwable throwable, SerDes serDes) {
        return ErrorObject.builder()
                .errorType(throwable.getClass().getName())
                .errorMessage(throwable.getMessage())
                .errorData(serDes.serialize(throwable))
                .stackTrace(serializeStackTrace(throwable.getStackTrace()))
                .build();
    }

    // StackTraceElement.toString() is implementation-dependent, so we'll define our
    // own format.
    public static List<String> serializeStackTrace(StackTraceElement[] stackTrace) {
        return Arrays.stream(stackTrace)
                .map((element) -> String.format(
                        "%s|%s|%s|%d",
                        element.getClassName(),
                        element.getMethodName(),
                        element.getFileName(),
                        element.getLineNumber()))
                .toList();
    }

    public static StackTraceElement[] deserializeStackTrace(List<String> stackTrace) {
        return stackTrace.stream()
                .map((s) -> {
                    String[] tokens = s.split("\\|");
                    return new StackTraceElement(tokens[0], tokens[1], tokens[2], Integer.parseInt(tokens[3]));
                })
                .toArray(StackTraceElement[]::new);
    }
}