Merge branch 'enhance-result'

* enhance-result:
  Result satisfies the three Monad Laws
  Maybe satisfies the three Monad Laws
  Collapse Maybe{Optional,Stream} into Maybe
  Remove Maybe.fromOptional(Optional)
  Add Result.toString() implementations
  Add Result.toMaybe() and Result.invert()
  Suppress warning for unchecked cast
  Add Result.orElseThrow()
  Add Result.fromMaybe(Maybe, Supplier<Exception>)
  Added Result.maybe()
  Add Result.map()
This commit is contained in:
Paul Campbell 2018-06-25 22:46:26 +01:00
commit 7e92129dfc
13 changed files with 581 additions and 168 deletions

View file

@ -55,6 +55,8 @@ class Example {
### Maybe ### Maybe
A Monad.
A non-final substitute for Optional with `peek()` and `stream()` methods. A non-final substitute for Optional with `peek()` and `stream()` methods.
```java ```java
@ -99,6 +101,8 @@ class Test {
### Result ### Result
A Monad.
A container for method return values that may raise an Exception. Useful for when a checked exceptions can't be added A container for method return values that may raise an Exception. Useful for when a checked exceptions can't be added
to the method signature. to the method signature.

View file

@ -36,7 +36,6 @@ import java.util.stream.Stream;
* A Maybe where a value is present. * A Maybe where a value is present.
* *
* @param <T> the type of the content * @param <T> the type of the content
*
* @author Paul Campbell (pcampbell@kemitix.net) * @author Paul Campbell (pcampbell@kemitix.net)
*/ */
@RequiredArgsConstructor(access = AccessLevel.PROTECTED) @RequiredArgsConstructor(access = AccessLevel.PROTECTED)
@ -45,7 +44,12 @@ final class Just<T> implements Maybe<T> {
private final T value; private final T value;
@Override @Override
public <R> Maybe<?> map(final Function<T, R> f) { public <R> Maybe<R> flatMap(Function<T, Maybe<R>> f) {
return f.apply(value);
}
@Override
public <R> Maybe<R> map(final Function<T, R> f) {
return new Just<>(f.apply(value)); return new Just<>(f.apply(value));
} }

View file

@ -24,21 +24,27 @@ package net.kemitix.mon.maybe;
import lombok.NonNull; import lombok.NonNull;
import net.kemitix.mon.Functor; import net.kemitix.mon.Functor;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
/** /**
* A value that may or may not be present. * A value that may or may not be present.
* *
* @param <T> the type of the content of the Just * @param <T> the type of the content of the Just
*
* @author Paul Campbell (pcampbell@kemitix.net) * @author Paul Campbell (pcampbell@kemitix.net)
*/ */
public interface Maybe<T> extends Functor<T, Maybe<?>>, MaybeStream<T>, MaybeOptional<T> { @SuppressWarnings("methodcount")
public interface Maybe<T> extends Functor<T, Maybe<?>> {
/** /**
* Create a Maybe for the value that is present. * Create a Maybe for the value that is present.
* *
* @param value the value, not null * @param value the value, not null
* @param <T> the type of the value * @param <T> the type of the value
*
* @return a Maybe of the value * @return a Maybe of the value
*/ */
static <T> Maybe<T> just(@NonNull final T value) { static <T> Maybe<T> just(@NonNull final T value) {
@ -49,7 +55,6 @@ public interface Maybe<T> extends Functor<T, Maybe<?>>, MaybeStream<T>, MaybeOpt
* Create a Maybe for a lack of a value. * Create a Maybe for a lack of a value.
* *
* @param <T> the type of the missing value * @param <T> the type of the missing value
*
* @return an empty Maybe * @return an empty Maybe
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -62,7 +67,6 @@ public interface Maybe<T> extends Functor<T, Maybe<?>>, MaybeStream<T>, MaybeOpt
* *
* @param value the value, may be null * @param value the value, may be null
* @param <T> the type of the value * @param <T> the type of the value
*
* @return a Maybe, either a Just, or Nothing if value is null * @return a Maybe, either a Just, or Nothing if value is null
*/ */
static <T> Maybe<T> maybe(final T value) { static <T> Maybe<T> maybe(final T value) {
@ -72,4 +76,70 @@ public interface Maybe<T> extends Functor<T, Maybe<?>>, MaybeStream<T>, MaybeOpt
return just(value); return just(value);
} }
/**
* Monad binder maps the Maybe into another Maybe using the binder method f.
*
* @param f the mapper function
* @param <R> the type of the value in the final maybe
* @return a Maybe with the mapped value
*/
<R> Maybe<R> flatMap(Function<T, Maybe<R>> f);
@Override
<R> Maybe<R> map(Function<T, R> f);
/**
* Provide a value to use when Maybe is Nothing.
*
* @param supplier supplier for an alternate value
* @return a Maybe
*/
T orElseGet(Supplier<T> supplier);
/**
* A value to use when Maybe is Nothing.
*
* @param otherValue an alternate value
* @return a Maybe
*/
T orElse(T otherValue);
/**
* Convert the Maybe to an {@link Optional}.
*
* @return an Optional containing a value for a Just, or empty for a Nothing
*/
Optional<T> toOptional();
/**
* Throw the exception if the Maybe is a Nothing.
*
* @param e the exception to throw
* @throws Exception if the Maybe is a Nothing
*/
@SuppressWarnings("illegalthrows")
void orElseThrow(Supplier<Exception> e) throws Exception;
/**
* Converts the Maybe into either a single value stream or and empty stream.
*
* @return a Stream containing the value or nothing.
*/
Stream<T> stream();
/**
* Filter a Maybe by the predicate, replacing with Nothing when it fails.
*
* @param predicate the test
* @return the Maybe, or Nothing if the test returns false
*/
Maybe<T> filter(Predicate<T> predicate);
/**
* Provide the value within the Maybe, if it exists, to the Supplier, and returns the Maybe.
*
* @param consumer the Consumer to the value if present
* @return the Maybe
*/
Maybe<T> peek(Consumer<T> consumer);
} }

View file

@ -1,71 +0,0 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2017 Paul Campbell
*
* 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.
*/
package net.kemitix.mon.maybe;
import java.util.Optional;
import java.util.function.Supplier;
/**
* Enables emulation parts of and conversion to the Java Optional class for Maybe.
*
* @param <T> the type of the content of the Just
*
* @author Paul Campbell (pcampbell@kemitix.net)
*/
interface MaybeOptional<T> {
/**
* Provide a value to use when Maybe is Nothing.
*
* @param supplier supplier for an alternate value
*
* @return a Maybe
*/
T orElseGet(Supplier<T> supplier);
/**
* A value to use when Maybe is Nothing.
*
* @param otherValue an alternate value
*
* @return a Maybe
*/
T orElse(T otherValue);
/**
* Convert the Maybe to an {@link Optional}.
*
* @return an Optional containing a value for a Just, or empty for a Nothing
*/
Optional<T> toOptional();
/**
* Throw the exception if the Maybe is a Nothing.
*
* @param e the exception to throw
*
* @throws Exception if the Maybe is a Nothing
*/
@SuppressWarnings("illegalthrows")
void orElseThrow(Supplier<Exception> e) throws Exception;
}

View file

@ -1,62 +0,0 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2017 Paul Campbell
*
* 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.
*/
package net.kemitix.mon.maybe;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* Enables emulation parts of and conversion to the Java Stream class for Maybe.
*
* @param <T> the type of the content of the Just
*
* @author Paul Campbell (pcampbell@kemitix.net)
*/
interface MaybeStream<T> {
/**
* Converts the Maybe into either a single value stream or and empty stream.
*
* @return a Stream containing the value or nothing.
*/
Stream<T> stream();
/**
* Filter a Maybe by the predicate, replacing with Nothing when it fails.
*
* @param predicate the test
*
* @return the Maybe, or Nothing if the test returns false
*/
Maybe<T> filter(Predicate<T> predicate);
/**
* Provide the value within the Maybe, if it exists, to the Supplier, and returns the Maybe.
*
* @param consumer the Consumer to the value if present
*
* @return the Maybe
*/
Maybe<T> peek(Consumer<T> consumer);
}

View file

@ -32,7 +32,6 @@ import java.util.stream.Stream;
* A Maybe where no value is present. * A Maybe where no value is present.
* *
* @param <T> the type of the missing content * @param <T> the type of the missing content
*
* @author Paul Campbell (pcampbell@kemitix.net) * @author Paul Campbell (pcampbell@kemitix.net)
*/ */
final class Nothing<T> implements Maybe<T> { final class Nothing<T> implements Maybe<T> {
@ -40,8 +39,14 @@ final class Nothing<T> implements Maybe<T> {
static final Maybe<?> INSTANCE = new Nothing<>(); static final Maybe<?> INSTANCE = new Nothing<>();
@Override @Override
public <R> Maybe<?> map(final Function<T, R> f) { public <R> Maybe<R> flatMap(Function<T, Maybe<R>> f) {
return this; return Maybe.nothing();
}
@Override
@SuppressWarnings("unchecked")
public <R> Maybe<R> map(final Function<T, R> f) {
return (Maybe<R>) INSTANCE;
} }
@Override @Override

View file

@ -22,9 +22,11 @@
package net.kemitix.mon.result; package net.kemitix.mon.result;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import net.kemitix.mon.maybe.Maybe;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate;
/** /**
* An Error Result. * An Error Result.
@ -51,8 +53,28 @@ class Err<T> implements Result<T> {
return Result.error(error); return Result.error(error);
} }
@Override
public <R> Result<R> map(final Function<T, R> f) {
return Result.error(error);
}
@Override @Override
public void match(final Consumer<T> onSuccess, final Consumer<Throwable> onError) { public void match(final Consumer<T> onSuccess, final Consumer<Throwable> onError) {
onError.accept(error); onError.accept(error);
} }
@Override
public Result<Maybe<T>> maybe(final Predicate<T> predicate) {
return Result.error(error);
}
@Override
public T orElseThrow() throws Throwable {
throw error;
}
@Override
public String toString() {
return String.format("Result.Error{error=%s}", error);
}
} }

View file

@ -21,18 +21,46 @@
package net.kemitix.mon.result; package net.kemitix.mon.result;
import net.kemitix.mon.maybe.Maybe;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
/** /**
* An Either type for holding a result or an error (exception). * An Either type for holding a result or an error (Throwable).
* *
* @param <T> the type of the result when a success * @param <T> the type of the result when a success
*
* @author Paul Campbell (pcampbell@kemitix.net) * @author Paul Campbell (pcampbell@kemitix.net)
*/ */
@SuppressWarnings("methodcount")
public interface Result<T> { public interface Result<T> {
/**
* Creates a Result from the Maybe, where the Result will be an error if the Maybe is Nothing.
*
* @param maybe the Maybe the might contain the value of the Result
* @param error the error that will be the Result if maybe is Nothing
* @param <T> the type of the Maybe and the Result
* @return a Result containing the value of the Maybe when it is a Just, or the error when it is Nothing
*/
static <T> Result<T> fromMaybe(final Maybe<T> maybe, final Supplier<Throwable> error) {
return maybe.map(Result::ok)
.orElseGet(() -> Result.error(error.get()));
}
/**
* Create a Result for an error.
*
* @param error the error (Throwable)
* @param <T> the type had the result been a success
* @return an error Result
*/
static <T> Result<T> error(final Throwable error) {
return new Err<>(error);
}
/** /**
* Create a Result for a success. * Create a Result for a success.
* *
@ -45,14 +73,32 @@ public interface Result<T> {
} }
/** /**
* Create a Result for an error. * Creates a Result from the Maybe, where the Result will be an error if the Maybe is Nothing.
* *
* @param error the error (exception) * @param result the Result the might contain the value of the Result
* @param <T> the type had the result been a success * @param <T> the type of the Maybe and the Result
* @return an error Result * @return a Result containing the value of the Maybe when it is a Just, or the error when it is Nothing
*/ */
static <T> Result<T> error(final Throwable error) { @SuppressWarnings("illegalcatch")
return new Err<>(error); static <T> Maybe<T> toMaybe(final Result<T> result) {
try {
return Maybe.just(result.orElseThrow());
} catch (final Throwable throwable) {
return Maybe.nothing();
}
}
/**
* Swaps the inner Result of a Maybe, so that a Result is on the outside.
* @param maybeResult the Maybe the contains a Result
* @param <T> the type of the value that may be in the Result
* @return a Result containing a Maybe, the value in the Maybe was the value in a successful Result within the
* original Maybe. If the original Maybe is Nothing, the Result will contain Nothing. If the original Result was an
* error, then the Result will also be an error.
*/
static <T> Result<Maybe<T>> invert(final Maybe<Result<T>> maybeResult) {
return maybeResult.orElseGet(() -> Result.ok(null))
.flatMap(value -> Result.ok(Maybe.maybe(value)));
} }
/** /**
@ -73,12 +119,20 @@ public interface Result<T> {
* Returns a new Result consisting of the result of applying the function to the contents of the Result. * Returns a new Result consisting of the result of applying the function to the contents of the Result.
* *
* @param f the mapping function the produces a Result * @param f the mapping function the produces a Result
* @param <R> the type of the result of the mapping function * @param <R> the type of the value withing the Result of the mapping function
*
* @return a Result * @return a Result
*/ */
<R> Result<R> flatMap(Function<T, Result<R>> f); <R> Result<R> flatMap(Function<T, Result<R>> f);
/**
* Applies the functions to the value of a successful result, while doing nothing with an error.
*
* @param f the mapping function to produce the new value
* @param <R> the type of the result of the mapping function
* @return a Result
*/
<R> Result<R> map(Function<T, R> f);
/** /**
* Matches the Result, either success or error, and supplies the appropriate Consumer with the value or error. * Matches the Result, either success or error, and supplies the appropriate Consumer with the value or error.
* *
@ -87,4 +141,20 @@ public interface Result<T> {
*/ */
void match(Consumer<T> onSuccess, Consumer<Throwable> onError); void match(Consumer<T> onSuccess, Consumer<Throwable> onError);
/**
* Wraps the value within the Result in a Maybe, either a Just if the predicate is true, or Nothing.
*
* @param predicate the test to decide
* @return a Result containing a Maybe that may or may not contain a value
*/
Result<Maybe<T>> maybe(Predicate<T> predicate);
/**
* Extracts the successful value from the result, or throws the error Throwable.
*
* @return the value if a success
* @throws Throwable the result is an error
*/
@SuppressWarnings("illegalthrows")
T orElseThrow() throws Throwable;
} }

View file

@ -22,9 +22,12 @@
package net.kemitix.mon.result; package net.kemitix.mon.result;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import net.kemitix.mon.maybe.Maybe;
import java.util.Objects;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate;
/** /**
* A Successful Result. * A Successful Result.
@ -51,8 +54,41 @@ class Success<T> implements Result<T> {
return f.apply(value); return f.apply(value);
} }
@Override
public <R> Result<R> map(final Function<T, R> f) {
return Result.ok(f.apply(value));
}
@Override @Override
public void match(final Consumer<T> onSuccess, final Consumer<Throwable> onError) { public void match(final Consumer<T> onSuccess, final Consumer<Throwable> onError) {
onSuccess.accept(value); onSuccess.accept(value);
} }
@Override
public Result<Maybe<T>> maybe(final Predicate<T> predicate) {
if (predicate.test(value)) {
return Result.ok(Maybe.just(value));
}
return Result.ok(Maybe.nothing());
}
@Override
public T orElseThrow() {
return value;
}
@Override
public boolean equals(Object other) {
return other instanceof Success && Objects.equals(value, ((Success) other).value);
}
@Override
public int hashCode() {
return Objects.hash(value);
}
@Override
public String toString() {
return String.format("Result.Success{value=%s}", value);
}
} }

View file

@ -0,0 +1,39 @@
package net.kemitix.mon;
import net.kemitix.mon.maybe.Maybe;
import org.assertj.core.api.WithAssertions;
import org.junit.Test;
import java.util.function.Function;
public class MaybeMonadTest implements WithAssertions {
@Test
public void leftIdentity() {
//given
final int value = 1;
final Maybe<Integer> maybe = Maybe.maybe(value);
final Function<Integer, Maybe<Integer>> f = i -> Maybe.maybe(i * 2);
//then
assertThat(maybe.flatMap(f)).isEqualTo(f.apply(value));
}
@Test
public void rightIdentity() {
//given
final Maybe<Integer> maybe = Maybe.maybe(1);
//then
assertThat(maybe.flatMap(Maybe::maybe)).isEqualTo(maybe);
}
@Test
public void associativity() {
//given
final Maybe<Integer> maybe = Maybe.maybe(1);
final Function<Integer, Maybe<Integer>> f = i -> Maybe.maybe(i * 2);
final Function<Integer, Maybe<Integer>> g = i -> Maybe.maybe(i + 6);
//then
assertThat(maybe.flatMap(f).flatMap(g)).isEqualTo(maybe.flatMap(x -> f.apply(x).flatMap(g)));
}
}

View file

@ -10,9 +10,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Stream; import java.util.stream.Stream;
import static net.kemitix.mon.maybe.Maybe.just; import static net.kemitix.mon.maybe.Maybe.*;
import static net.kemitix.mon.maybe.Maybe.maybe;
import static net.kemitix.mon.maybe.Maybe.nothing;
public class MaybeTest implements WithAssertions { public class MaybeTest implements WithAssertions {
@ -89,7 +87,6 @@ public class MaybeTest implements WithAssertions {
assertThat(Optional.empty().map(Maybe::just).orElseGet(Maybe::nothing)).isEqualTo(nothing()); assertThat(Optional.empty().map(Maybe::just).orElseGet(Maybe::nothing)).isEqualTo(nothing());
} }
@Test @Test
public void peek() { public void peek() {
final AtomicInteger ref = new AtomicInteger(0); final AtomicInteger ref = new AtomicInteger(0);
@ -126,4 +123,47 @@ public class MaybeTest implements WithAssertions {
//then //then
assertThat(stream).isEmpty(); assertThat(stream).isEmpty();
} }
@Test
public void justFlatMap() {
//given
final Maybe<Integer> just1 = Maybe.just(1);
final Maybe<Integer> just2 = Maybe.just(2);
//when
final Maybe<Integer> result = just1.flatMap(v1 ->
just2.flatMap(v2 ->
Maybe.maybe(v1 + v2)
));
//then
assertThat(result.toOptional()).contains(3);
}
@Test
public void nothingFlatMap() {
//given
final Maybe<Integer> nothing1 = Maybe.nothing();
final Maybe<Integer> nothing2 = Maybe.nothing();
//when
final Maybe<Integer> result = nothing1.flatMap(v1 ->
nothing2.flatMap(v2 ->
Maybe.maybe(v1 + v2)
));
//then
assertThat(result.toOptional()).isEmpty();
}
@Test
public void justNothingFlatMap() {
//given
final Maybe<Integer> just1 = Maybe.just(1);
final Maybe<Integer> nothing2 = Maybe.nothing();
//when
final Maybe<Integer> result = just1.flatMap(v1 ->
nothing2.flatMap(v2 ->
Maybe.maybe(v1 + v2)
));
//then
assertThat(result.toOptional()).isEmpty();
}
} }

View file

@ -0,0 +1,39 @@
package net.kemitix.mon;
import net.kemitix.mon.result.Result;
import org.assertj.core.api.WithAssertions;
import org.junit.Test;
import java.util.function.Function;
public class ResultMonadTest implements WithAssertions {
@Test
public void leftIdentity() {
//given
final int value = 1;
final Result<Integer> result = Result.ok(value);
final Function<Integer, Result<Integer>> f = i -> Result.ok(i * 2);
//then
assertThat(result.flatMap(f)).isEqualTo(f.apply(value));
}
@Test
public void rightIdentity() {
//given
final Result<Integer> result = Result.ok(1);
//then
assertThat(result.flatMap(Result::ok)).isEqualTo(result);
}
@Test
public void associativity() {
//given
final Result<Integer> result = Result.ok(1);
final Function<Integer, Result<Integer>> f = i -> Result.ok(i * 2);
final Function<Integer, Result<Integer>> g = i -> Result.ok(i + 6);
//then
assertThat(result.flatMap(f).flatMap(g)).isEqualTo(result.flatMap(x -> f.apply(x).flatMap(g)));
}
}

View file

@ -1,6 +1,7 @@
package net.kemitix.mon; package net.kemitix.mon;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import net.kemitix.mon.maybe.Maybe;
import net.kemitix.mon.result.Result; import net.kemitix.mon.result.Result;
import org.assertj.core.api.WithAssertions; import org.assertj.core.api.WithAssertions;
import org.junit.Test; import org.junit.Test;
@ -105,6 +106,202 @@ public class ResultTest implements WithAssertions {
assertThat(flatMap.isError()).isTrue(); assertThat(flatMap.isError()).isTrue();
} }
@Test
public void success_whenMap_isSuccess() {
//given
final Result<Integer> okResult = Result.ok(1);
//when
final Result<String> result = okResult.map(value -> String.valueOf(value));
//then
assertThat(result.isOkay()).isTrue();
result.match(
success -> assertThat(success).isEqualTo("1"),
error -> fail("not an error")
);
}
@Test
public void error_whenMap_isError() {
//given
final RuntimeException exception = new RuntimeException();
final Result<Integer> errorResult = Result.error(exception);
//when
final Result<String> result = errorResult.map(value -> String.valueOf(value));
//then
assertThat(result.isError()).isTrue();
result.match(
success -> fail("not an success"),
error -> assertThat(error).isSameAs(exception)
);
}
@Test
public void successMaybe_whenPasses_isSuccessJust() {
//given
final Result<Integer> okResult = Result.ok(1);
//when
final Result<Maybe<Integer>> maybeResult = okResult.maybe(value -> value >= 0);
//then
assertThat(maybeResult.isOkay()).isTrue();
maybeResult.match(
success -> assertThat(success.toOptional()).contains(1),
error -> fail("not an error")
);
}
@Test
public void successMaybe_whenFails_isSuccessNothing() {
//given
final Result<Integer> okResult = Result.ok(1);
//when
final Result<Maybe<Integer>> maybeResult = okResult.maybe(value -> value >= 4);
//then
assertThat(maybeResult.isOkay()).isTrue();
maybeResult.match(
success -> assertThat(success.toOptional()).isEmpty(),
error -> fail("not an error")
);
}
@Test
public void errorMaybe_whenPasses_isError() {
//given
final RuntimeException exception = new RuntimeException();
final Result<Integer> errorResult = Result.error(exception);
//when
final Result<Maybe<Integer>> maybeResult = errorResult.maybe(value -> value >= 0);
//then
assertThat(maybeResult.isError()).isTrue();
maybeResult.match(
success -> fail("not a success"),
error -> assertThat(error).isSameAs(exception)
);
}
@Test
public void errorMaybe_whenFails_isError() {
//given
final RuntimeException exception = new RuntimeException();
final Result<Integer> errorResult = Result.error(exception);
//when
final Result<Maybe<Integer>> maybeResult = errorResult.maybe(value -> value >= 4);
//then
assertThat(maybeResult.isError()).isTrue();
maybeResult.match(
success -> fail("not a success"),
error -> assertThat(error).isSameAs(exception)
);
}
@Test
public void justMaybe_isSuccess() {
//given
final Maybe<Integer> just = Maybe.just(1);
//when
final Result<Integer> result = Result.fromMaybe(just, () -> new RuntimeException());
//then
assertThat(result.isOkay()).isTrue();
result.match(
success -> assertThat(success).isEqualTo(1),
error -> fail("not an error")
);
}
@Test
public void nothingMaybe_isError() {
//given
final Maybe<Object> nothing = Maybe.nothing();
final RuntimeException exception = new RuntimeException();
//when
final Result<Object> result = Result.fromMaybe(nothing, () -> exception);
//then
assertThat(result.isError()).isTrue();
result.match(
success -> fail("not a success"),
error -> assertThat(error).isSameAs(exception)
);
}
@Test
public void success_toMaybe_isJust() {
//given
final Result<Integer> ok = Result.ok(1);
//when
final Maybe<Integer> maybe = Result.toMaybe(ok);
//then
assertThat(maybe.toOptional()).contains(1);
}
@Test
public void error_toMaybe_isNothing() {
//given
final Result<Object> error = Result.error(new RuntimeException());
//when
final Maybe<Object> maybe = Result.toMaybe(error);
//then
assertThat(maybe.toOptional()).isEmpty();
}
@Test
public void success_whenOrElseThrow_isValue() throws Throwable {
//given
final Result<Integer> ok = Result.ok(1);
//when
final Integer value = ok.orElseThrow();
//then
assertThat(value).isEqualTo(1);
}
@Test
public void error_whenOrElseThrow_throws() {
//given
final RuntimeException exception = new RuntimeException();
final Result<Integer> error = Result.error(exception);
//when
assertThatThrownBy(() -> error.orElseThrow())
.isSameAs(exception);
}
@Test
public void JustSuccess_invert_thenSuccessJust() {
//given
final Maybe<Result<Integer>> justSuccess = Maybe.just(Result.ok(1));
//when
final Result<Maybe<Integer>> result = Result.invert(justSuccess);
//then
result.match(
success -> assertThat(success.toOptional()).contains(1),
error -> fail("Not an error")
);
}
@Test
public void JustError_invert_thenError() {
//given
final RuntimeException exception = new RuntimeException();
final Maybe<Result<Object>> justError = Maybe.just(Result.error(exception));
//when
final Result<Maybe<Object>> result = Result.invert(justError);
//then
result.match(
success -> fail("Not a success"),
error -> assertThat(error).isSameAs(exception)
);
}
@Test
public void Nothing_invert_thenSuccessNothing() {
//given
final Maybe<Result<Integer>> nothing = Maybe.nothing();
//when
final Result<Maybe<Integer>> result = Result.invert(nothing);
//then
result.match(
success -> assertThat(success.toOptional()).isEmpty(),
error -> fail("Not an error")
);
}
@Test @Test
public void useCase_whenOkay_thenReturnSuccess() { public void useCase_whenOkay_thenReturnSuccess() {
//given //given
@ -151,6 +348,26 @@ public class ResultTest implements WithAssertions {
); );
} }
@Test
public void success_toString() {
//given
final Result<Integer> ok = Result.ok(1);
//when
final String toString = ok.toString();
//then
assertThat(toString).contains("Result.Success{value=1}");
}
@Test
public void err_toString() {
//given
final Result<Integer> error = Result.error(new RuntimeException("failed"));
//when
final String toString = error.toString();
//then
assertThat(toString).contains("Result.Error{error=java.lang.RuntimeException: failed}");
}
@RequiredArgsConstructor @RequiredArgsConstructor
private static class UseCase { private static class UseCase {
@ -175,20 +392,20 @@ public class ResultTest implements WithAssertions {
calculateAverage(adjustedIntFromFile1, intFromFile2)))); calculateAverage(adjustedIntFromFile1, intFromFile2))));
} }
private Result<Double> calculateAverage(final Integer val1, final Integer val2) { private Result<Integer> readIntFromFile(final String fileName) {
return Result.ok((double) (val1 + val2) / 2);
}
private Result<Integer> adjustValue(Integer value) {
return Result.ok(value + 2);
}
private Result<Integer> readIntFromFile(String fileName) {
if (okay) { if (okay) {
return Result.ok(fileName.length()); return Result.ok(fileName.length());
} }
return Result.error(new RuntimeException(fileName)); return Result.error(new RuntimeException(fileName));
} }
private Result<Integer> adjustValue(final Integer value) {
return Result.ok(value + 2);
}
private Result<Double> calculateAverage(final Integer val1, final Integer val2) {
return Result.ok((double) (val1 + val2) / 2);
}
} }
} }