Проверка нескольких ADT с проверкой Scala Cats - PullRequest
0 голосов
/ 09 января 2019

Я пытаюсь проверить конфигурацию в Scala. Сначала я конвертирую config json в соответствующий класс case, а затем проверяю его. Поскольку я хочу работать медленно, я собираю все проверки, которые не проходят, а не возвращаются после первой проверки, которая не удалась. Я планирую использовать аппликативные функторы, предоставленные библиотекой кошек Проверка кошек .

Проблема, с которой я сталкиваюсь, заключается в проверке формы, показанной в ссылке, для простого класса

final case class RegistrationData(username: String, password: String, firstName: String, lastName: String, age: Int)

// Below is the code snippet for applying validation from the link   itself
 {
(validateUserName(username),
validatePassword(password),
validateFirstName(firstName),
validateLastName(lastName),
validateAge(age)).mapN(RegistrationData)}

// A more complex case for validations
final case class User(name:String,adds:List[Addresses])
final case class Address(street:String,lds:List[LandMark])
final case class LandMark(wellKnown:Boolean,street:String)

В этом случае проверка в поле 'username' не зависит от проверки, скажем, 'firstName'. Но что, если

  1. Мне пришлось наложить некоторую валидацию типа, которая принимала бы 'firstName' и 'userName' (скажем, гипотетически расстояние Левенштейна, равное двум, должно быть <= некоторое число). </li>
  2. case-класс не был сделан из простых примитивов (Int, String), но имел в своем составе другие классы case. например, пользовательский класс, как указано выше.

В целом подходит ли аппликативный функтор для этого случая? Должен ли я даже собрать все неудачные проверки?

PS: простите, если упомянул что-то неправильно, я новичок в scala.

1 Ответ

0 голосов
/ 09 января 2019

На основании примера проверки кошки

import cats.data._
import cats.data.Validated._
import cats.implicits._

final case class RegistrationData(name: Name, age: Int, workAge: Int)

final case class Name(firstName: String, lastName: String)

sealed trait DomainValidation {
  def errorMessage: String
}

case object FirstNameHasSpecialCharacters extends DomainValidation {
  def errorMessage: String =
    "First name cannot contain spaces, numbers or special characters."
}

case object LastNameHasSpecialCharacters extends DomainValidation {
  def errorMessage: String =
    "Last name cannot contain spaces, numbers or special characters."
}

case object AgeIsInvalid extends DomainValidation {
  def errorMessage: String =
    "You must be aged 18 and not older than 75 to use our services."
}

case object AgeIsLessThanWorkInvalid extends DomainValidation {
  def errorMessage: String =
    "You must be aged 18 and not older than 75 to use our services."
}

sealed trait FormValidatorNec {

  type ValidationResult[A] = ValidatedNec[DomainValidation, A]

  private def validateFirstName(firstName: String): ValidationResult[String] =
    if (firstName.matches("^[a-zA-Z]+$")) firstName.validNec
    else FirstNameHasSpecialCharacters.invalidNec

  private def validateLastName(lastName: String): ValidationResult[String] =
    if (lastName.matches("^[a-zA-Z]+$")) lastName.validNec
    else LastNameHasSpecialCharacters.invalidNec

  private def validateAge(age: Int,
                          workAge: Int): ValidationResult[(Int, Int)] = {
    if (age >= 18 && age <= 75 && workAge >= 0)
      if (age > workAge)
        (age, workAge).validNec
      else
        AgeIsLessThanWorkInvalid.invalidNec
    else
      AgeIsInvalid.invalidNec
  }

  def validateForm(firstName: String,
                   lastName: String,
                   age: Int,
                   workAge: Int): ValidationResult[RegistrationData] = {
    (
      (validateFirstName(firstName), validateLastName(lastName)).mapN(Name),
      validateAge(age, workAge)
    ).mapN {
      case (n, (a, w)) => RegistrationData(name = n, age = a, workAge = w)
    }
  }

}

object FormValidatorNec extends FormValidatorNec

println(FormValidatorNec.validateForm("firstname", "lastname", 40, 30))
println(FormValidatorNec.validateForm("firs2tname", "lastname", 20, 30))

Проверьте это скрипка

Функция mapN вызывается, только если данные в кортеже (ValidationResult[_], ValidationResult[_], ...) равны Valid. Если один или несколько элементов в кортеже Invalid, они собираются в NotEmtpyChain.

Таким образом, все validate методы вызываются, и когда все они возвращают Valid[_], применяется mapN функция.

...