Search code examples
javavavr

VAVR Match(Option).of() not working properly


I am trying to explore the the use of Vavr library within my Java application. I am trying to reduce the if-else ladder using the Vavr library's Match.of() construct.

Here is a piece of code that I have written:

Match(Option.of(clientId)).of(
                Case($None(), run(() -> {
                    metricsService.recordValidationStageMetrics(CLIENT_ID_NOT_PRESENT, type, BLANK);
                    log.error("Client ID not present in the request header: [{}]", clientId);
                    throw new ValidationException(EX_REQUEST_CLIENT_ID_EMPTY);
                })),
                Case($Some($(StringUtils::isBlank)), run(() -> {
                    metricsService.recordValidationStageMetrics(CLIENT_ID_NOT_PRESENT, type, BLANK);
                    log.error("Client ID not present in the request header: [{}]", clientId);
                    throw new ValidationException(EX_REQUEST_CLIENT_ID_EMPTY);
                })),
                Case($(), run(() -> {
                    if (isClientIdNotAllowed(clientId)) {
                        log.error(LOG_CLIENT_ID_NOT_ALLOWED, clientId);
                        metricsService.recordValidationStageMetrics(CLIENT_ID_NOT_ALLOWED, type, clientId);
                        throw new ValidationException(EX_ALLOWED_CLIENT_ID_ERROR);
                    }
                }))
        );

The problem here is that the Option is always matching the first Case statement. Even if the clientId is non-null, it is validated to None and throws the exception.

So the question is:

  1. Why is it not behaving in an intended way? What is it that I am missing here?
  2. Is there a cleaner way to write this?

Solution

  • Actually - as far as I remember - you need to prepend the run with () -> which indicates the Supplier variant of the second argument for Case being used. Otherwise it will be evaluated eagerly and hence the first Case will be run.

    The following code works fine:

    package lol;
    
    import static io.vavr.API.$;
    import static io.vavr.API.Case;
    import static io.vavr.API.Match;
    import static io.vavr.API.run;
    import static io.vavr.Patterns.$None;
    import static io.vavr.Patterns.$Some;
    
    import io.vavr.control.Option;
    
    public class Lol {
    
      public static void main(String[] args) {
        String val = "lol";
        Match(Option.of(val)).of(
            Case($None(), () -> run(() -> {
              System.out.println(1);
              throw new IllegalArgumentException("null");
            })),
            Case($Some($(String::isEmpty)), () -> run(() -> {
              System.out.println(2);
              throw new IllegalArgumentException("empty");
            })),
            Case($(), () -> run(() -> {
              System.out.println(3);
              if ("lol".equals(val)) {
                throw new IllegalArgumentException("lol");
              }
            }))
        )
        ;
      }
    }
    

    Try with removing the () -> and you'll start getting the first Case being matched no matter what the input is.

    Whether I can be written cleaner. It depends on the version of java you use. I see it as a switch statement or pattern matching if the latest is used.