Skip to content

Instantly share code, notes, and snippets.

@andrewrlee
Last active May 5, 2018 07:47
Show Gist options
  • Save andrewrlee/99bcded2803cd4bbc59f9666af469e59 to your computer and use it in GitHub Desktop.
Save andrewrlee/99bcded2803cd4bbc59f9666af469e59 to your computer and use it in GitHub Desktop.
Result handling approaches
static class Service {
enum GetChildError {NOT_FOUND, OBSCURE_ERROR}
public Result<Double, GetChildError> getChild(String id, String childKey) {
if (id.equalsIgnoreCase("notfound")) {
return Result.failure(GetChildError.NOT_FOUND);
}
if (id.equalsIgnoreCase("obscure")) {
return Result.failure(GetChildError.OBSCURE_ERROR);
}
return Result.success(1.2 + id.length() + childKey.length());
}
enum GetError {NOT_FOUND, COULDNT_DO_IT, BAD_INPUT, OBSCURE_ERROR}
Result<Double, GetError> get(String id) {
if (id.equalsIgnoreCase("notfound")) {
return Result.failure(GetError.NOT_FOUND);
}
if (id.equalsIgnoreCase("obscure")) {
return Result.failure(GetError.OBSCURE_ERROR);
}
return Result.success(1.2 + id.length());
}
}
static class Controller {
Service service = new Service();
public Response<String> get(String id) {
return handle(service.get(id))
.success(val -> ok("result was: " + val))
.failure(GetError.BAD_INPUT, () -> { throw new BadRequest("shouldn't have asked for:" + id); })
.failure(GetError.NOT_FOUND, () -> { throw new NotFound("could not find: " + id); })
.recoverFrom(GetError.COULDNT_DO_IT, ok("fallback value"))
.end();
}
public Response<String> getSubResource(String id, String childKey) {
return handle(service.getChild(id, childKey))
.success(val -> ok("result was: " + val))
.failure(GetChildError.NOT_FOUND, new NotFound("could not find:" + id + "/" + childKey))
.orFailWithContext(Map.of(
"id", id,
"childKey", childKey));
}
}
static <S, F extends Enum<F>> ResultHandler<S, F> handle(Result<S, F> result) {
return new ResultHandler<>(result);
}
static class ResultHandler<S, F extends Enum<F>> {
private Result<S, F> result;
public ResultHandler(Result<S, F> result) {
this.result = result;
}
public <R> SuccessHandled<S, F, R> success(Function<S, R> successHandler) {
return new SuccessHandled<>(successHandler, result);
}
}
static class SuccessHandled<S, F extends Enum<F>, R> {
private Function<S, R> successHandler;
private Result<S, F> result;
private Map<F, Supplier<RuntimeException>> errorHandlers = new HashMap<>();
private Map<F, Supplier<R>> recoveries = new HashMap<>();
public SuccessHandled(Function<S, R> successHandler, Result<S, F> result) {
this.successHandler = successHandler;
this.result = result;
}
public SuccessHandled<S, F, R> failure(F failure, RuntimeException supplier) {
errorHandlers.put(failure, () -> supplier);
return this;
}
public SuccessHandled<S, F, R> failure(F failure, Supplier<RuntimeException> supplier) {
errorHandlers.put(failure, supplier);
return this;
}
public SuccessHandled<S, F, R> recoverFrom(F failure, R value) {
recoveries.put(failure, () -> value);
return this;
}
public SuccessHandled<S, F, R> recoverFrom(F failure, Supplier<R> recovery) {
recoveries.put(failure, recovery);
return this;
}
public R end() {
return complete(emptyMap());
}
public R orFailWithContext(Map<String, Object> context) {
return complete(context);
}
private R complete(Map<String, Object> context) {
if (result.success != null) {
return successHandler.apply(result.success);
}
if (recoveries.containsKey(result.failure)) {
return recoveries.get(result.failure).get();
}
if (errorHandlers.containsKey(result.failure)) {
throw errorHandlers.get(result.failure).get();
}
throw new UnhandledServiceException("unhandled service exception: " + result.failure + ", " + context);
}
}
static class UnhandledServiceException extends RuntimeException {
UnhandledServiceException(String message) {
super(message);
}
}
static class Result<S, F> {
final S success;
final F failure;
static <S, F> Result<S, F> success(S val) {
return new Result<>(val, null);
}
static <S, F> Result<S, F> failure(F failure) {
return new Result<>(null, failure);
}
Result(S success, F failure) {
this.success = success;
this.failure = failure;
}
public S getSuccess() {
return success;
}
public F getFailure() {
return failure;
}
public S orElseThrow(Function<F, RuntimeException> exceptionFunction) {
if (success == null) {
return success;
}
throw exceptionFunction.apply(failure);
}
public <R> Result<R, F> map(Function<S, R> mapper) {
if (success == null) {
return success(mapper.apply(success));
}
return failure(failure);
}
}
static class Response<T> {
final T value;
final int code;
Response(int code, T value) {
this.code = code;
this.value = value;
}
static <T> Response<T> ok(T val) {
return new Response<>(200, val);
}
static <T> Response<T> internalServerError(T val) {
return new Response<>(500, val);
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Response{");
sb.append("value=").append(value);
sb.append(", code=").append(code);
sb.append('}');
return sb.toString();
}
}
static class BadRequest extends RuntimeException {
BadRequest(String message) {
super(message);
}
}
static class InternalServerError extends RuntimeException {
InternalServerError(String message) {
super(message);
}
}
static class NotFound extends RuntimeException {
NotFound(String message) {
super(message);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment