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
28 changes: 28 additions & 0 deletions src/main/java/com/johnnywey/flipside/failable/Failable.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

import com.johnnywey.flipside.marker.DidItWork;

import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
* This is a simple wrapper for communicating failure data. It is loosely based on a Scala Option but provides
* a common interface for failure cases that can be easily mapped to things like HTTP codes, etc.
Expand All @@ -12,4 +17,27 @@ public interface Failable<T> {
Fail getReason();
String getDetail();
DidItWork toDidItWork();
default void ifSuccess(Consumer<? super T> consumer) {
if (isSuccess() && get() != null) {
consumer.accept(get());
} else {
throw new FailableException(this);
}
}
default void ifFailed(Consumer<Fail> consumer) {
if (!isSuccess()) {
consumer.accept(getReason());
}
}
Failable<T> filter(Predicate<? super T> predicate);
<U> Failable<U> map(Function<? super T, ? extends U> mapper);
<U> Failable<U> flatMap(Function<? super T, Failable<U>> mapper);
default T orElse(T other) {
return get() != null ? get() : other;
}
default T orElseGet(Supplier<? extends T> other) {
return get() != null ? get() : other.get();
}


}
34 changes: 33 additions & 1 deletion src/main/java/com/johnnywey/flipside/failable/Failed.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

import com.johnnywey.flipside.marker.DidNotWork;
import com.johnnywey.flipside.marker.DidItWork;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
* Something failed.
*/
public class Failed<T> implements Failable<T> {
public final class Failed<T> implements Failable<T> {

private final Fail reason;
private final String detail;
Expand All @@ -16,6 +19,10 @@ public Failed(final Fail reason, final String detail) {
this.detail = detail;
}

public static <T> Failed<T> of(final Fail reason, final String detail) {
return new Failed<>(reason, detail);
}

@Override
public T get() {
throw new FailableException(this);
Expand Down Expand Up @@ -45,4 +52,29 @@ public String toString() {
public DidItWork toDidItWork() {
return new DidNotWork(this.reason, this.detail);
}

@Override
public Failable<T> filter(Predicate<? super T> predicate) {
return this;
}

@Override
public <U> Failable<U> map(Function<? super T, ? extends U> mapper) {
throw new FailableException(this);
}

@Override
public <U> Failable<U> flatMap(Function<? super T, Failable<U>> mapper) {
throw new FailableException(this);
}

@Override
public T orElse(T other) {
throw new FailableException(this);
}

@Override
public T orElseGet(Supplier<? extends T> other) {
throw new FailableException(this);
}
}
45 changes: 45 additions & 0 deletions src/main/java/com/johnnywey/flipside/failable/Success.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
import com.johnnywey.flipside.marker.Worked;
import com.johnnywey.flipside.marker.DidItWork;

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

/**
* Something succeeded.
*/
public class Success<T> implements Failable<T> {
private final T result;
private static final Success<?> EMPTY = new Success<>(null);

public Success(final T result) {
this.result = result;
Expand All @@ -18,6 +23,16 @@ public T get() {
return result;
}

public static <T> Success<T> of(final T result) {
return new Success<>(result);
}

public static<T> Success<T> empty() {
@SuppressWarnings("unchecked")
Success<T> t = (Success<T>) EMPTY;
return t;
}

@Override
public Boolean isSuccess() {
return true;
Expand All @@ -37,4 +52,34 @@ public String getDetail() {
public DidItWork toDidItWork() {
return new Worked();
}

@Override
public Failable<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isSuccess()) {
return this;
} else {
return predicate.test(get()) ? this : empty();
}
}

@Override
public <U> Failable<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isSuccess())
return empty();
else {
return Success.of(mapper.apply(get()));
}
}

@Override
public <U> Failable<U> flatMap(Function<? super T, Failable<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isSuccess())
return empty();
else {
return Objects.requireNonNull(mapper.apply(get()));
}
}
}
38 changes: 38 additions & 0 deletions src/test/groovy/com/johnnywey/flipside/FailedSpec.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.johnnywey.flipside

import com.johnnywey.flipside.failable.Fail
import com.johnnywey.flipside.failable.FailableException
import com.johnnywey.flipside.failable.Failed
import spock.lang.Specification

class FailedSpec extends Specification {

def "test failed"() {
setup:
def errMsg = "This is a test failed"
def unit = Failed.of(Fail.BAD_REQUEST, errMsg)

expect:
!unit.isSuccess()
unit.detail.matches(errMsg)
unit.reason.equals(Fail.BAD_REQUEST)
unit.toString().equals(Failed.of(Fail.BAD_REQUEST, errMsg).toString())
unit.reason.httpResponseCode.equals(Fail.BAD_REQUEST.httpResponseCode)

unit.toDidItWork().detail.matches(errMsg)
unit.toDidItWork().reason.equals(Fail.BAD_REQUEST)
}

def "test exception"() {
setup:
def errMsg = "This is a test failed"
def unit = Failed.of(Fail.BAD_REQUEST, errMsg)

when:
unit.get()

then:
thrown(FailableException)
}

}
40 changes: 40 additions & 0 deletions src/test/groovy/com/johnnywey/flipside/SuccessSpec.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.johnnywey.flipside

import com.johnnywey.flipside.failable.Fail
import com.johnnywey.flipside.failable.Success
import spock.lang.Specification

class SuccessSpec extends Specification {

def "test success"() {
setup:
def successMsg = "mySuccess"
def unit = Success.of(successMsg)

expect:
unit.isSuccess()
unit.reason.equals(Fail.SUCCESS)
unit.detail.equals(Fail.SUCCESS.name())
unit.get().equals(successMsg)
unit.toDidItWork().isSuccess()
}

def "test success with consumer"() {
setup:
def successMsg = "mySuccess"
def result = "not set"
Success.of(successMsg).ifSuccess({ m -> result = successMsg })

expect:
result == successMsg
}

def "test success with mapping"() {
setup:
def successMsg = "mySuccess"
Double result = Success.of(successMsg).map({s -> 23d}).orElse(24d);

expect:
result == 23d
}
}