Result.java

package lucidglitch;
//        MIT License
//
//        Copyright (c) 2025 Stefan von Stein
//        Permission is hereby granted, free of charge, to any person obtaining a copy
//        of this software and associated documentation files (the "Software"), to deal
//        in the Software without restriction, including without limitation the rights
//        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//        copies of the Software, and to permit persons to whom the Software is
//        furnished to do so, subject to the following conditions:
//
//        The above copyright notice and this permission notice shall be included in all
//        copies or substantial portions of the Software.
//
//        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//        SOFTWARE.


import java.util.function.Consumer;
import java.util.function.Function;

public sealed interface Result<T, E> permits Result.Success, Result.Failure {
    /**
     * Result in Success context
     */
    record Success<T, E>(T value) implements Result<T, E> {
    }

    /**
     * Result in Failure context
     */
    record Failure<T, E>(E error) implements Result<T, E> {
        @Override
        public boolean isSuccess() {
            return false;
        }
    }

    default  T value () {
        throw new IllegalStateException ( "Failure: " + error());
    };
    default E error () {
        throw new IllegalStateException ( "Success: " + value());
    };
    default boolean isSuccess(){
        return true;
    }
    @FunctionalInterface
    interface ThrowingSupplier<T, E extends Exception> {
        T get() throws E;
    }

    /**
     * Result in Success context
     */
    static <T, E> Result<T, E> success(T value) {
        return new Success<>(value);
    }
    /**
     * Result in Failure context
     */
    static <T, E> Result<T, E> failure(E error) {
        return new Failure<>(error);
    }

    /**
     * Result in Success context, or Failure id Exception is thrown by supplier
     */
    static <T, E extends Exception> Result<T, E> trial(ThrowingSupplier<T, E> supplier) {
        try {
            return success(supplier.get());
        } catch (Exception e) {
            //noinspection unchecked
            return failure((E) e); // Cast to E since we expect it to extend Exception
        }
    }

    /**
     * Run an expression withing a try block, and return any exception in a Failure
     */
    static <T, E extends Exception, F> Result<T, F> trial(ThrowingSupplier<T, E> supplier,
                                                          Function<? super Exception, F> exceptionMapper) {
        try {
            return success(supplier.get());
        }catch (Exception e) {
            return failure(exceptionMapper.apply(e));
        }
    }


    /**
     * Apply mapper to Success but not to Failure
     */
    default <U> Result<U, E> map(Function<? super T, ? extends U> mapper) {
        return switch (this) {
            case Success( var value) -> success(mapper.apply(value));
            case Failure( var error) -> failure(error);
        };
    }

    default Result<T,E> whenOk(Consumer<T> consumer) {
        if(isSuccess())
            consumer.accept(value());
        return this;
    }
    default Result<T,E> whenError(Consumer<E> consumer) {
        if(!isSuccess())
            consumer.accept(error());
        return this;
    }
    /**
     * Apply mapper to Success but not to Failure, and emmit Failure i mapper throws a runtime exception
     */
    default <U> Result<U, E> mapTrial(Function<? super T, ? extends U> mapper) {
        return switch (this) {
            case Success(var value) -> {
                try {
                    yield success(mapper.apply(value));
                }catch (Exception e) {
                    yield failure((E) e);
                }
            }
            case Failure(var error) -> failure( error);
        };
    }

    /**
     * Apply mapper to Success but not to Failure, and emmit Failure i mapper throws, but transform any
     * exception with exceptionMapper
     */
    default <U> Result<U, E> mapTrial(Function<? super T, ? extends U> mapper,
                                      Function<? super Exception, E> exceptionMapper) {
        return switch (this) {
            case Success(var value) -> {
                try {
                    yield success(mapper.apply(value));
                }catch (Exception e) {
                    yield failure(exceptionMapper.apply(e));
                }
            }
            case Failure(var error) -> failure( error);
        };
    }

    /**
     * Apply mapper to Failure, rather than Success, which is just propagated.
     */
    default <F> Result<T, F> mapFailure(Function<? super E, ? extends F> mapper) {
        return switch (this) {
            case Success(var value) -> success(value);
            case Failure(var failure) -> failure(mapper.apply(failure));
        };
    }

    /**
     * Apply mapper to Success value, but let mapper decide if it is Success or Failure
     */
    default <U> Result<U, E> flatMap(Function<? super T, Result<U, E>> mapper) {
        return switch (this) {
            case Success(var value) -> mapper.apply(value);
            case Failure(var error) -> failure(error);
        };
    }

    @SuppressWarnings("unchecked")
    default  <U> Result<U, E> join() {
        return switch (this){
            case Success(Result<?,?> inner) -> (Result<U,E>) inner;
            case Success(var e) -> throw new IllegalStateException("Value is not a Result");
            case Failure (var e) -> failure(e);
        };
    }
}