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);
};
}
}