Spring
Spring

Reputation: 11855

Chain optionals with void return type

JDK 8:

I'm validating input request. Is there a way to nicely chain these optionals? Or some other way to make it look a bit nicer and get rid off too many if optional checks? kinda looks ugly.

In REST controller class:

{
   ...

   Optional<ResponseEntity<?>> operationError = requestValidator.handleOperationError(bodyParams, myHandler, serviceInstance, null);
    if( operationError.isPresent()){
        return operationError.get();
    }

    Optional<ResponseEntity<?>> otherError = requestValidator.checkRequestOther(serviceInstance, bodyParams, instanceAction);
    if( otherError.isPresent()){
        return otherError.get();
    }

    Optional<ResponseEntity<?>> someWeirdError = requestValidator.checksomeWeird(serviceInstance, instanceAction);
    if( someWeirdError.isPresent()){
        return someWeirdError.get();
    }

    Optional<ResponseEntity<?>> conflictError = requestValidator.conflictExists(instanceAction,serviceInstance);
    if( conflictError.isPresent()){
        return conflictError.get();
    }

   //If All fine start doing business logic
}

Upvotes: 1

Views: 277

Answers (3)

M. Prokhorov
M. Prokhorov

Reputation: 3993

I'm just going to start by saying: cleanest solution would require a better abstraction than Optional provides, because it's main purpose is to represent a missing value, not validation output.

However, what you want to achieve is still possible with use of Optional::or added since JDK 9:

Optional<ResponseEntity<?>> validationError = 
    requestValidator.handleOperationError(bodyParams, myHandler, serviceInstance, null)
    .or(() -> requestValidator.checkRequestOther(serviceInstance, bodyParams, instanceAction))
    .or(() -> requestValidator.checkSomeWeird(serviceInstance, instanceAction))
    .or(() -> requestValidator.conflictExists(instanceAction,serviceInstance));
if( validationError.isPresent() ){
    return validationError.get();
}

If you can't use Java 9, you will have to roll your own type, something like this:

public final class Error<T> {
    private T value;

    private Error(T value) {
        this.value = value;
    }

    public static Error withValue(T value) {
        requireNonNull(value);
        return new Error(value);
    }

    public static <T> Error<T> start() {
        return new Error(null);
    }

    public static <T> Error<T> startWith(Supplier<T> firstCheck) {
        requireNonNull(firstCheck);
        return new Error(firstCheck.get());
    }

    public Error<T> thenCheck(Supplier<Error<? extends T>> nextCheck) {
        requireNonNull(nextCheck);

        return isPresent() ? this : requireNonNull(nextCheck.get());
    }

    public boolean isPresent() {
        return value != null;
    }

    public T get() {
        return value; // I think no need to be particularly fancy here, null means "No Error"
    }
}

Then, assuming your validator now returns Error instances:

Error<ResponseEntity<?>> validationError = 
    requestValidator.handleOperationError(...)
    .thenCheck(() -> requestValidator.checkRequestOther(...))
    .thenCheck(() -> requestValidator.checkSomeWeird(...))
    .thenCheck(() -> requestValidator.conflictExists(...));
if (validationError.isPresent()) {
    return validationError.get();
}

Upvotes: 3

Benoit
Benoit

Reputation: 5394

If you need a strict Java 8 solution, you can create an helper class:

public class OptionalHelper {

    public static <T> Optional<T> findFirstPresent(Supplier<Optional<T>>... optionals) {
        return Arrays.stream(optionals)
                .map(Supplier::get)
                .filter(Optional::isPresent)
                .findFirst()
                .orElse(Optional.empty());
    }
}

And use it like this:

    Optional<ResponseEntity<?>> validationError = OptionalHelper.findFirstPresent(
            () -> requestValidator.handleOperationError(bodyParams, myHandler, serviceInstance, null),
            () -> requestValidator.checkRequestOther(serviceInstance, bodyParams, instanceAction),
            () -> requestValidator.checkSomeWeird(serviceInstance, instanceAction),
            () -> requestValidator.conflictExists(instanceAction, serviceInstance));

    if (validationError.isPresent()) {
        return validationError.get();
    }

Upvotes: 1

Marv
Marv

Reputation: 3557

Yes, you can chain the Optional values in a Stream, but I would only do this if getting the Optionals is inexpensive, as they are all evaluated even when the first one is present:

Optional<ResponseEntity<?>> operationError = 
    requestValidator.handleOperationError(bodyParams, myHandler, serviceInstance, null);
Optional<ResponseEntity<?>> otherError = 
    requestValidator.checkRequestOther(serviceInstance, bodyParams, instanceAction);
Optional<ResponseEntity<String>> some409Error = 
    requestValidator.checkSome409(serviceInstance, instanceAction);
Optional<ResponseEntity<?>> conflictError = 
    requestValidator.conflictExists(instanceAction,serviceInstance);

Optional<ResponseEntity<String>> anyError = 
    Stream.of(operationError, otherError, some409Error, conflictError)
          .filter(Optional.isPresent)
          .map(Optional::get)
          .findFirst();

if (anyError.isPresent) {
    return anyError.get();
}

A Java 8 Solution that evaluates lazily:

@SafeVarargs
public static <E> Optional<E> any(final Supplier<Optional<E>>... optionalSuppliers) {
    return Stream.of(optionalSuppliers)
                 .map(Supplier::get)
                 .filter(Optional::isPresent)
                 .map(Optional::get)
                 .findFirst();
}

And the callsite:

Optional<ResponseEntity<?>> anyError = any(
    () -> requestValidator.handleOperationError(bodyParams, myHandler, serviceInstance, null),
    () -> requestValidator.checkRequestOther(serviceInstance, bodyParams, instanceAction),
    () -> requestValidator.checkSome409(serviceInstance, instanceAction),
    () -> requestValidator.conflictExists(instanceAction, serviceInstance)
);

if (anyError.isPresent) {
    return anyError.get();
}

Upvotes: 1

Related Questions