io.vavr.control.Validation如何在这些示例中起作用?

时间:2018-01-25 22:15:15

标签: java validation generics currying vavr

我在理解验证库io.vavr.control.Validation时遇到了一些麻烦。有可能提出过于宽泛的问题,我确实有几个子问题 - 但我相信它们是密切相关的,并且会拼凑起来帮助我理解使用这种验证机制的正确方法。

我从这里的例子开始:https://softwaremill.com/javaslang-data-validation

Validation<String, ValidRegistrationRequest> validate(RegistrationRequest request) {
   return combine(
       validateCardId(request.getCardId()),
       validateTicketType(request.getTicketType()),
       validateGuestId(request.getGuestId())
)
       .ap(ValidRegistrationRequest::new)
       .mapError(this::errorsAsJson);
}

private Validation<String, Card> validateCardId(String cardId) {
    // validate cardId
    // if correct then return an instance of entity the cardId corresponds to
}

private Validation<String, TicketType> validateTicketType(String ticketType) {
    // validate ticketType
    // if known then return enumeration representing the ticket
}

private Validation<String, Guest> validateGuest(String guestId) {
    // validate guestId
    // if correct then return an instance of entity the questId corresponds to
}

起初,我不明白Validation<String, ValidRegistrationRequest>的通用参数来自哪里。我现在明白它们分别与传递给mapErrorap的方法的返回类型相关联。但是:

  1. combine如何知道返回Validation<String, ValidRegistrationRequest>?我认为唯一可行的方法是combine实际上是Validation<String, ValidRegistrationRequest>::combine,以便从此模板定义apmapError。但我不相信编译器应该能够暗示combine引用返回类型的类中的静态实现。这里发生了什么?

  2. [次要]使用ValidRegistrationRequest而不仅仅是RegistrationRequest的用例是什么?在我的编码中,我很想做后者,直到我看到一个例子。

  3. 我正在阅读的第二个例子是:http://www.vavr.io/vavr-docs/#_validation

    class PersonValidator {
    
        private static final String VALID_NAME_CHARS = "[a-zA-Z ]";
        private static final int MIN_AGE = 0;
    
        public Validation<Seq<String>, Person> validatePerson(String name, int age) {
            return Validation.combine(validateName(name), validateAge(age)).ap(Person::new);
        }
    
        private Validation<String, String> validateName(String name) {
            return CharSeq.of(name).replaceAll(VALID_NAME_CHARS, "").transform(seq -> seq.isEmpty()
                    ? Validation.valid(name)
                    : Validation.invalid("Name contains invalid characters: '"
                    + seq.distinct().sorted() + "'"));
        }
    
        private Validation<String, Integer> validateAge(int age) {
            return age < MIN_AGE
                    ? Validation.invalid("Age must be at least " + MIN_AGE)
                    : Validation.valid(age);
        }
    
    }
    
    1. Seq来自哪里?这是没有提供mapError时的默认值吗?但我正在查看Validation.class的反编译.class文件,而Seq的引用在这里:

        static <E, T> Validation<List<E>, Seq<T>> sequence(Iterable<? extends Validation<List<E>, T>> values) {
          Objects.requireNonNull(values, "values is null");
          List<E> errors = List.empty();
          List<T> list = List.empty();
          Iterator var3 = values.iterator();
      
          while(var3.hasNext()) {
            Validation<List<E>, T> value = (Validation)var3.next();
            if (value.isInvalid()) {
              errors = errors.prependAll(((List)value.getError()).reverse());
            } else if (errors.isEmpty()) {
              list = list.prepend(value.get());
            }
          }
      
          return errors.isEmpty() ? valid(list.reverse()) : invalid(errors.reverse());
        }
      

      其中,我认为不相关。也许我使用的是过时的Validation? (我的导入完全是javaslang.control.Validation,而不是io.vavr.control.Validation。)

    2. 我对这两个例子都有这个问题:combine如何知道要传递给构造函数(ap)的参数,以及按什么顺序?答案是“所有参数,按照给定的顺序”?

    3. 提前致谢。

2 个答案:

答案 0 :(得分:7)

在第一次寻找Vavr的验证机制时,您遇到了同样的问题和怀疑。

以下是我对前两个问题的回答:

  1. combine(...)方法返回一个验证构建器的实例,在这种情况下,这是一个Builder3类,包含validate*(...)个函数的三个结果。 ap(...)方法是此构建器的一种方法,可触发构建Validation实例。
  2. 调用它时,验证结果将逐个应用于作为参数提供的函数的curried版本:

    v3.ap(v2.ap(v1.ap(Validation.valid(f.curried()))))
    

    在示例中,fValidRegistrationRequest类的构造函数。最后,我们有一个持有有效请求实例的验证。

    另一方面,如果任何结果无效,则该方法会使用错误消息列表创建无效结果。并且这次调用mapError(this::errorsAsJson)(在Validation实例上!)将其转换为JSON格式。

    1. 使用ValidRegistrationRequest
    2. 的用例是什么?

      我在我的一个项目中使用了Vavr的验证。我有一个请求带有一些实体标识符。为了验证它的正确性,我不得不查询数据库以检查每个id是否有东西。

      因此,如果验证与原始请求一起返回,我将不得不再次从数据库中获取这些对象。因此,我决定返回ValidRegistrationRequest持有域对象。只调用一次数据库,请求处理速度明显加快。

      回答第二对问题:

      1. 是的,你是对的。如果结果无效,Validation.combine(...).ap(...)将返回Invalid类的实例,其中包含从验证方法返回的错误消息列表。
      2. 如果您查看来源,使用Validation.ap(...)方法,您会发现无效结果会收集到Vavr的List中。由于它继承自Seq,因此您可以在validatePerson示例Seq<String>中看到此类型。

        1. 是的,确切地说。 “所有参数,按照给定的顺序”:)
        2. combine中参数的顺序必须与提供给ap(...)方法的函数所采用的参数顺序相同。

          通过下载源代码,可以更轻松地跟踪Vavr的内部结构。

答案 1 :(得分:0)

好的,这是我尝试回答我自己的问题,但更有经验的人的确认会很好。我找到了最新的验证来源here

示例1

  1. 我从实际上复制了这个例子的文章,combine被静态导入以提高可读性。&#34;我错过了。所以,我是对的 - 我们正在调用静态方法。具体来说,这一个:

    static <E, T1, T2, T3> Builder3<E, T1, T2, T3> combine(Validation<E, T1> validation1, Validation<E, T2> validation2, Validation<E, T3> validation3) {
        Objects.requireNonNull(validation1, "validation1 is null");
        Objects.requireNonNull(validation2, "validation2 is null");
        Objects.requireNonNull(validation3, "validation3 is null");
        return new Builder3<>(validation1, validation2, validation3);
    }
    
  2. 我对使用ValidRegistrationRequest的猜测只是在编译时强制执行验证。也就是说,如果所有消费代码都需要RegistrationRequest,开发人员就不会意外地使用未经验证的ValidRegistrationRequest

  3. 示例2

    1. 我认为Set来自这里:

      /**
       * An invalid Validation
       *
       * @param <E> type of the errors of this Validation
       * @param <T> type of the value of this Validation
       */
      final class Invalid<E, T> implements Validation<E, T>, Serializable {
      
          ...
      
          @Override
          public Seq<E> getErrors() {
              return errors;
          }
      
          ...
      }
      

      然后与此有关:

      /**
       * Applies a given {@code Validation} that encapsulates a function to this {@code Validation}'s value or combines both errors.
       *
       * @param validation a function that transforms this value (on the 'sunny path')
       * @param <U>        the new value type
       * @return a new {@code Validation} that contains a transformed value or combined errors.
       */
      @SuppressWarnings("unchecked")
      default <U> Validation<E, U> ap(Validation<E, ? extends Function<? super T, ? extends U>> validation) {
          Objects.requireNonNull(validation, "validation is null");
          if (isValid()) {
              return validation.map(f -> f.apply(get()));
          } else if (validation.isValid()) {
              return (Validation<E, U>) this;
          } else {
              return invalidAll(getErrors().prependAll(validation.getErrors()));
          }
      }
      
      1. @mchmiel在写我的时候回答了我的问题。