package moe.nea.pcj; import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.function.Function; public sealed interface Result permits Result.Ok, Result.Fail { default boolean isOk() { return errors().isEmpty(); } Optional value(); Optional partial(); default Optional valueOrPartial() { return value().or(this::partial); } List errors(); default Result map(Function mapper) { return flatMap(mapper.andThen(Result::ok)); } Result flatMap(Function> mapper); default Result mapError(Function mapper) { return mapErrors(it -> it.stream().map(mapper).toList()); } Result mapErrors(Function, List> mapper); default Result appendError(Bad error) { return appendErrors(List.of(error)); } Result appendErrors(List error); record Ok(Good okValue) implements Result { @Override public Result appendErrors(List errors) { if (errors.isEmpty()) return new Ok<>(okValue); return new Fail<>(okValue, errors); } @Override public Result mapErrors(Function, List> mapper) { return new Ok<>(okValue); } @Override public Optional partial() { return Optional.empty(); } @Override public List errors() { return List.of(); } @Override public Result flatMap(Function> mapper) { return Result.cast(mapper.apply(okValue)); } @Override public Optional value() { return Optional.of(okValue); } @Override public int hashCode() { return Objects.hash(okValue); } @Override public boolean equals(Object obj) { if (obj == this) return true; if (obj instanceof Result.Ok ok) { return Objects.equals(ok.okValue, this.okValue); } return false; } } record Fail(@Nullable Good partialValue, List badValue) implements Result { public Fail { if (badValue.isEmpty()) throw new IllegalArgumentException("Cannot create failure without any error values"); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj instanceof Result.Fail fail) { return Objects.equals(partialValue, fail.partialValue) && badValue.equals(fail.badValue); } return false; } @Override public int hashCode() { return Objects.hash(partialValue, badValue); } @Override public Optional value() { return Optional.empty(); } @Override public Optional partial() { return Optional.ofNullable(partialValue); } @Override public List errors() { return Collections.unmodifiableList(badValue); } @Override public Result flatMap(Function> mapper) { if (partialValue != null) { return Result.cast(mapper.apply(partialValue)).appendErrors(badValue); } return new Fail<>(null, badValue); } @Override public Result mapErrors(Function, List> mapper) { return new Fail<>(partialValue, mapper.apply(badValue)); } @Override public Result appendErrors(List errors) { var nextErrors = new ArrayList<>(badValue); nextErrors.addAll(errors); return new Fail<>(partialValue, nextErrors); } } static Result ok(Good value) { return new Ok<>(value); } static Result.Fail fail(Bad error) { return new Fail<>(null, List.of(error)); } static Result cast(Result c) { //noinspection unchecked return (Result) c; } static Result.Fail partial(@Nullable Good partial, Bad error) { return new Fail<>(partial, List.of(error)); } }