Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions enigma/src/main/java/org/quiltmc/enigma/util/ErrResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.quiltmc.enigma.util;

import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;

record ErrResult<T, E>(E error) implements Result<T, E> {
public ErrResult {
Objects.requireNonNull(error);
}

@Override
public boolean isOk() {
return false;
}

@Override
public boolean isErr() {
return true;
}

@Override
public Optional<T> ok() {
return Optional.empty();
}

@Override
public Optional<E> err() {
return Optional.of(this.error);
}

@Override
public T unwrap() {
throw new IllegalStateException(String.format("Called Result.unwrap on an Err value: %s", this.error));
}

@Override
public E unwrapErr() {
return this.error;
}

@Override
public T unwrapOr(T fallback) {
return fallback;
}

@Override
public T unwrapOrElse(Function<E, T> fallback) {
return fallback.apply(this.error);
}

@Override
@SuppressWarnings("unchecked")
public <U> Result<U, E> map(Function<T, U> mapper) {
return (Result<U, E>) this;
}

@Override
public <F> Result<T, F> mapErr(Function<E, F> mapper) {
return Result.err(mapper.apply(this.error));
}

@Override
@SuppressWarnings("unchecked")
public <U> Result<U, E> and(Result<U, E> next) {
return (Result<U, E>) this;
}

@Override
@SuppressWarnings("unchecked")
public <U> Result<U, E> andThen(Function<T, Result<U, E>> next) {
return (Result<U, E>) this;
}
}
72 changes: 72 additions & 0 deletions enigma/src/main/java/org/quiltmc/enigma/util/OkResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package org.quiltmc.enigma.util;

import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;

record OkResult<T, E>(T value) implements Result<T, E> {
public OkResult {
Objects.requireNonNull(value);
}

@Override
public boolean isOk() {
return true;
}

@Override
public boolean isErr() {
return false;
}

@Override
public Optional<T> ok() {
return Optional.of(this.value);
}

@Override
public Optional<E> err() {
return Optional.empty();
}

@Override
public T unwrap() {
return this.value;
}

@Override
public E unwrapErr() {
throw new IllegalStateException(String.format("Called Result.unwrapErr on an Ok value: %s", this.value));
}

@Override
public T unwrapOr(T fallback) {
return this.value;
}

@Override
public T unwrapOrElse(Function<E, T> fallback) {
return this.value;
}

@Override
public <U> Result<U, E> map(Function<T, U> mapper) {
return Result.ok(mapper.apply(this.value));
}

@Override
@SuppressWarnings("unchecked")
public <F> Result<T, F> mapErr(Function<E, F> mapper) {
return (Result<T, F>) this;
}

@Override
public <U> Result<U, E> and(Result<U, E> next) {
return next;
}

@Override
public <U> Result<U, E> andThen(Function<T, Result<U, E>> next) {
return next.apply(this.value);
}
}
232 changes: 132 additions & 100 deletions enigma/src/main/java/org/quiltmc/enigma/util/Result.java
Original file line number Diff line number Diff line change
@@ -1,106 +1,138 @@
package org.quiltmc.enigma.util;

import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;

public final class Result<T, E> {
private final T ok;
private final E err;

private Result(T ok, E err) {
this.ok = ok;
this.err = err;
}

public static <T, E> Result<T, E> ok(T ok) {
return new Result<>(Objects.requireNonNull(ok), null);
}

public static <T, E> Result<T, E> err(E err) {
return new Result<>(null, Objects.requireNonNull(err));
}

public boolean isOk() {
return this.ok != null;
}

public boolean isErr() {
return this.err != null;
}

public Optional<T> ok() {
return Optional.ofNullable(this.ok);
}

public Optional<E> err() {
return Optional.ofNullable(this.err);
}

public T unwrap() {
if (this.isOk()) return this.ok;
throw new IllegalStateException(String.format("Called Result.unwrap on an Err value: %s", this.err));
}

public E unwrapErr() {
if (this.isErr()) return this.err;
throw new IllegalStateException(String.format("Called Result.unwrapErr on an Ok value: %s", this.ok));
}

public T unwrapOr(T fallback) {
if (this.isOk()) return this.ok;
return fallback;
}

public T unwrapOrElse(Function<E, T> fn) {
if (this.isOk()) return this.ok;
return fn.apply(this.err);
}

@SuppressWarnings("unchecked")
public <U> Result<U, E> map(Function<T, U> op) {
if (!this.isOk()) return (Result<U, E>) this;
return Result.ok(op.apply(this.ok));
}

@SuppressWarnings("unchecked")
public <F> Result<T, F> mapErr(Function<E, F> op) {
if (!this.isErr()) return (Result<T, F>) this;
return Result.err(op.apply(this.err));
}

@SuppressWarnings("unchecked")
public <U> Result<U, E> and(Result<U, E> next) {
if (this.isErr()) return (Result<U, E>) this;
return next;
}

@SuppressWarnings("unchecked")
public <U> Result<U, E> andThen(Function<T, Result<U, E>> op) {
if (this.isErr()) return (Result<U, E>) this;
return op.apply(this.ok);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || this.getClass() != o.getClass()) return false;
Result<?, ?> result = (Result<?, ?>) o;
return Objects.equals(this.ok, result.ok)
&& Objects.equals(this.err, result.err);
}

@Override
public int hashCode() {
return Objects.hash(this.ok, this.err);
}

@Override
public String toString() {
if (this.isOk()) {
return String.format("Result.Ok(%s)", this.ok);
} else {
return String.format("Result.Err(%s)", this.err);
}
}
/**
* Represents the result of an operation which may or may not have been successful.
*
* <p> A result holds either a value of type {@code T} which is the result of a successful operation,
* or an error of type {@code E} which is the result of a failed operation.
*
* @param <T> the value type held by a successful result
* @param <E> the error type held by an unsuccessful result
*/
public sealed interface Result<T, E> permits OkResult, ErrResult {
/**
* Creates a successful result holding the passed {@code value}.
*
* @param value the value held by the returned result; must not be {@code null}
*
* @param <T> the value type held by a successful result
* @param <E> the error type held by an unsuccessful result
*
* @return a successful result holding the passed {@code value}
*
* @throws NullPointerException if the passed {@code value} is {@code null}
*/
static <T, E> Result<T, E> ok(T value) {
return new OkResult<>(value);
}

/**
* Creates an error result holding the passed {@code error}.
*
* @param error the error held by the returned result; must not be {@code null}
*
* @param <T> the value type held by a successful result
* @param <E> the error type held by an unsuccessful result
*
* @return an error result holding the passed {@code error}
*
* @throws NullPointerException if the passed {@code error} is {@code null}
*/
static <T, E> Result<T, E> err(E error) {
return new ErrResult<>(error);
}

/**
* @return {@code true} if this result is a success, or {@code false} otherwise;
* always the inverse of {@link #isErr()}
*/
boolean isOk();

/**
* @return {@code true} if this result is an error, or {@code false otherwise};
* always the inverse of {@link #isOk()}
*/
boolean isErr();

/**
* @return an {@link Optional} holding this result's value if this result is a success,
* or {@link Optional#empty()} otherwise
*/
Optional<T> ok();

/**
* @return an {@link Optional} holding this result's error if this result is an error,
* or {@link Optional#empty()} otherwise
*/
Optional<E> err();

/**
* @return the result of a {@linkplain #isOk() successful} operation
*
* @throws IllegalStateException if this result {@linkplain #isErr() is an error}
*/
T unwrap();

/**
* @return the error of an {@linkplain #isErr() unsuccessful} operation
*
* @throws IllegalStateException if this result {@linkplain #isOk() is a success}
*/
E unwrapErr();

/**
* @return this result's value if this result {@linkplain #isOk() is a success},
* or the passed {@code fallback} otherwise
*/
T unwrapOr(T fallback);

/**
* @param fallback a function that supplies a value to return if this result is an error
*
* @return this result's value if this result {@linkplain #isOk() is a success},
* or the return value of the passed {@code fallback} function applied to this result's error otherwise
*/
T unwrapOrElse(Function<E, T> fallback);

/**
* @param mapper a function that generates a value from this result's value if this result is a success
*
* @return the result of applying the passed {@code mapper} to this result's value if this result is a success,
* or this result if this is an error
*
* @param <U> the value type held by a returned successful result
*/
<U> Result<U, E> map(Function<T, U> mapper);

/**
* @param mapper a function that creates a value from this result's error if this result is an error
*
* @return the result of applying the passed {@code mapper} to this result's value if this result is an error,
* or this result if this is a success
*
* @param <F> the error type held by a returned error result
*/
<F> Result<T, F> mapErr(Function<E, F> mapper);

/**
* @param next another result, to be returned if this result is a success
*
* @return the passed {@code next} result of this result is a success, or this result otherwise
*
* @param <U> the value type held by a returned successful result
*/
<U> Result<U, E> and(Result<U, E> next);

/**
* @param next a function that generates a new result from this result's value if this result is a success
*
* @return the result created by applying the passed {@code next} function to this result's value
* if this result is a success, or this result otherwise
*
* @param <U> the value type held by a returned successful result
*/
<U> Result<U, E> andThen(Function<T, Result<U, E>> next);
}