io.github.artkonr.result.FlagResult Maven / Gradle / Ivy
package io.github.artkonr.result;
import lombok.NonNull;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* A {@link BaseResult result} container that carries no value.
* @param error type
*/
public class FlagResult extends BaseResult {
/**
* Runs a specified {@link Wrap.Runnable}, catches an expected exception
* and returns it as a {@link FlagResult}.
* The expected exception can be any {@link Exception} type,
* this method internally checks if the caught exception type
* matches the expected type or its subtype. Normally, the client
* code should pass the narrowest type possible.
*
If no error is thrown by the supplied function, the call
* resolves to {@code OK}.
* @param errType expected type
* @param action fallible action
* @return result of the invocation
* @param error type
* @throws IllegalArgumentException if either of the arguments not provided
* @throws IllegalStateException if the exception during invocation is not
* of an expected type or its subtype
*/
public static FlagResult wrap(@NonNull Class errType,
@NonNull Wrap.Runnable action) {
try {
action.run();
return FlagResult.ok();
} catch (Exception exception) {
if (errType.isAssignableFrom(exception.getClass())) {
@SuppressWarnings("unchecked")
E cast = (E) exception;
return FlagResult.err(cast);
} else {
throw BaseResult.unexpectedWrappedException(errType, exception);
}
}
}
/**
* Creates a new instance from an existing {@link BaseResult result}.
* The {@code OK}/{@code ERR} state is taken from the source entity.
* Note: as {@link FlagResult} doesn't contain a value in {@code OK}
* state, any value carried by the source {@code OK} result is dropped.
* @param source source {@link BaseResult result}
* @return new instance
* @param error type
* @throws IllegalArgumentException if no argument provided
*/
public static FlagResult from(@NonNull BaseResult source) {
if (source.isOk()) {
return FlagResult.ok();
} else {
return FlagResult.err(source.error);
}
}
/**
* Creates an {@code OK} item with no value.
* @return new {@code OK} instance
* @param error type
*/
public static FlagResult ok() {
return new FlagResult<>(null);
}
/**
* Creates an {@code ERR} item with the provided error.
* @param error error
* @return new {@code ERR} instance
* @param error type
* @throws IllegalArgumentException if no argument provided
*/
public static FlagResult err(@NonNull E error) {
return new FlagResult<>(error);
}
/**
* Joins a {@link Collection} of {@link BaseResult any result} objects into
* a single {@link FlagResult}.
* The eventual {@link FlagResult} will have the {@code OK} state
* iff. all {@link BaseResult source results} are {@code OK}. Otherwise,
* the internal error of the fist occurring {@code ERR} result is taken.
*
As {@link Collection} does not specify the ordering, the eventual
* {@link FlagResult} is not guaranteed to contain the same internal
* error state every time.
*
Null-safe: should the input contain {@code null} items,
* this method will remove them.
* @param results collection of {@link BaseResult results}
* @return results joined into a {@link FlagResult}
* @param error type
* @throws IllegalArgumentException if no argument provided
*/
public static FlagResult join(@NonNull Collection> results) {
return join(results, TakeFrom.HEAD);
}
/**
* Joins a {@link Collection} of {@link BaseResult any result} objects into
* a single {@link FlagResult}.
* The eventual {@link FlagResult} will have the {@code OK} state
* iff. all {@link BaseResult source results} are {@code OK}. Otherwise,
* the internal error of is taken as described by {@link TakeFrom}.
*
As {@link Collection} does not specify the ordering, the eventual
* {@link FlagResult} is not guaranteed to contain the same internal
* error state every time.
*
Null-safe: should the input contain {@code null} items,
* this method will remove them.
* @param results collection of {@link BaseResult results}
* @param rule fusing rule
* @return results joined into a {@link FlagResult}
* @param error type
* @throws IllegalArgumentException if no argument provided
*/
public static FlagResult join(@NonNull Collection> results,
@NonNull TakeFrom rule) {
List> errored = results.stream()
.filter(Objects::nonNull)
.filter(BaseResult::isErr)
.toList();
if (!errored.isEmpty()) {
BaseResult picked = switch (rule) {
case HEAD -> errored.get(0);
case TAIL -> errored.get(errored.size() - 1);
};
return FlagResult.err(picked.error);
} else {
return FlagResult.ok();
}
}
/**
* Converts {@code this} {@link FlagResult} into a {@link Result
* value result} with a given item if {@code this} result is
* {@code OK}; simply carriers internal error state otherwise.
* @param item item to populate the result with
* @return new {@link Result} instance
* @param type of {@code OK} item
* @throws IllegalArgumentException if no argument provided
*/
public Result populate(@NonNull V item) {
if (isOk()) {
return Result.ok(item);
} else {
return Result.err(error);
}
}
/**
* Produces a new {@link FlagResult} out of {@code this}
* and another {@link FlagResult}.
* The eventual {@link FlagResult} will have {@code OK}
* state iff. both instances are {@code OK}; {@code ERR}
* state is assumed otherwise, with the error taken from
* the only {@code ERR} from the pair or from {@code this}
* instance if both are {@code ERR}.
*
In order to be fuse-able, the fused items must contain
* the same error type.
* @param another fuse with
* @return a new {@link FlagResult}
* @throws IllegalArgumentException if no argument provided
*/
public FlagResult fuse(@NonNull FlagResult another) {
return fuse(another, TakeFrom.HEAD);
}
/**
* Produces a new {@link FlagResult} out of {@code this}
* and another {@link FlagResult}.
* The eventual {@link FlagResult} will have {@code OK}
* state iff. both instances are {@code OK}; {@code ERR}
* state is assumed otherwise, with the error taken from
* the only {@code ERR} from the pair. If both instances
* are {@code ERR}, the {@link TakeFrom rule arg} allows
* to point which of the {@code ERR} items passes the
* error on.
*
In order to be fuse-able, the fused items must contain
* the same error type.
* @param another fuse with
* @param rule fusing rule
* @return a new {@link FlagResult}
* @throws IllegalArgumentException if no argument provided
*/
public FlagResult fuse(@NonNull FlagResult another,
@NonNull TakeFrom rule) {
return rule.takeError(this, another)
.map(FlagResult::err)
.orElseGet(FlagResult::ok);
}
/**
* {@inheritDoc}
* @param remap callback
* @return mapped {@link FlagResult}
* @param new error type
* @throws IllegalArgumentException if no argument provided or
* if the callback function returns {@code null}
*/
@Override
public FlagResult mapErr(@NonNull Function remap) {
if (isErr()) {
return FlagResult.err(remap.apply(error));
} else {
return FlagResult.ok();
}
}
/**
* Converts an {@code OK} result into {@code ERR}
* using the specified factory if {@code this}
* instance is {@code OK}. Recreates {@code this}
* with its internal error state otherwise.
* Note: the resulting type parameter is up-cast
* to the most generic type supported: {@link Exception}.
* @param factory exception factory
* @return converted instance
* @throws IllegalArgumentException if no argument provided or if
* the factory function returns {@code null}
*/
@Override
public FlagResult taint(@NonNull Supplier extends Exception> factory) {
if (isOk()) {
return err(factory.get());
} else {
return err(error);
}
}
/**
* Wraps the internal {@code ERR} state into a {@link
* Failure wrapping exception} and throws if {@code
* this} instance is an {@code ERR}.
* @throws Failure result wrapping exception
*/
public void unwrap() {
if (isErr()) {
throw new Failure(error);
}
}
/**
* Builds a text representation.
* @return text representation
*/
@Override
public String toString() {
return "FlagResult[" + (isOk() ? "ok" : "err=" + error) + ']';
}
/**
* Checks if {@code this} equals {@code that}.
* @param that that
* @return comparison result
*/
@Override
public boolean equals(Object that) {
if (this == that) return true;
if (that == null || getClass() != that.getClass()) return false;
FlagResult> result = (FlagResult>) that;
return Objects.equals(error, result.error);
}
/**
* Computes hashcode.
* @return computed hashcode
*/
@Override
public int hashCode() {
if (isOk()) {
return 31;
} else {
return 31 * error.hashCode();
}
}
/**
* Internal constructor.
* Setting the argument to {@code null} must
* follow the class contract.
* @param error nullable error value
*/
protected FlagResult(E error) {
super(error);
}
}