Подтвердите объект с помощью Arrow-kt - PullRequest
1 голос
/ 20 мая 2019

У меня есть объект (книга), какие поля должны обновляться событием (автор изменен). Допустим, поле автора книги изменяется только в том случае, если автор женился и изменил свое имя, но книга не изменится, если автор только что переехал в новый город.

В этом простом случае я могу проверить, если book.authorName == event.author.name, и вернуть Either<NothingChangedFailure, Book>. Но как я могу проверить не только одно поле? Если бы я продолжил с любым из них, процесс остановился бы на первом NothingChangedFailure, с которым он столкнулся, но я хотел бы объединить все обновления и вернуть NothingChangedFailure, только если ни одно из полей в книге не изменилось.

Я пытался с Option, Either и читал на Validated, но мне кажется, что все они проваливают весь результат, если возникла одна ошибка. Так есть ли вариант, которого я просто не вижу?

1 Ответ

1 голос
/ 20 мая 2019

Существует пример для Validated , который показывает случай, когда мы можем составить ошибки проверки.

Для вашего случая (я собираюсь предположить что-то здесь, например, поля, доступные вкнига) Я думаю, это будет выглядеть примерно так:

data class Book(val title: String, val authorName: String, val pageCount: Int)

Здесь мы создаем ошибки с определением полугруппы для него:

sealed class BookValidationError {
  data class PropertyNotChanged(val propertyName: String) : BookValidationError()
  data class Multiple(val errors: Nel<BookValidationError>) : BookValidationError()
}

object BookValidationErrorSemigroup : Semigroup<BookValidationError> {
  override fun BookValidationError.combine(b: BookValidationError): BookValidationError = when {
      this is Multiple && b is Multiple -> Multiple(errors + b.errors)
      this is Multiple && b !is Multiple -> Multiple(errors + b)
      this !is Multiple && b is Multiple -> Multiple(this.nel() + b.errors)
      else -> BookValidationError.Multiple(NonEmptyList(this, b))
  } 
}

Затем мы можем определить соответствующие ApplicativeErrorдля типов ошибок:

private val bookApplicativeError : ApplicativeError<ValidatedPartialOf<BookValidationError>, BookValidationError> = 
  Validated.applicativeError(BookValidationErrorSemigroup)

И мы свели его вместе с вспомогательным классом:

class BookValidation(
  private val book: Book
) : ApplicativeError<ValidatedPartialOf<BookValidationError>, BookValidationError> by bookApplicativeError {

    fun <T> fieldIsNot(name: String, actualValue: T, incorrectValue: T): Kind<ValidatedPartialOf<BookValidationError>, Book> =
        if(actualValue == incorrectValue) raiseError(BookValidationError.PropertyNotChanged(name))
        else just(book)

}

и функцией расширения легкого доступа:

fun Book.validateThat(titleIsNot : String, authorNameIsNot: String, pageCountIsNot: Int) = 
    with(BookValidation(this)) {
        map(
            fieldIsNot("title", title, titleIsNot), 
            fieldIsNot("authorName", authorName, authorNameIsNot),
            fieldIsNot("pageCount", pageCount, pageCountIsNot)
        ) { this@validateThat }.handleErrorWith { 
            raiseError(it) 
        }
    }

Тогда, если вы выполните его следующим образом:

fun main() {
    Book("a", "b", 123).validateThat(
        titleIsNot = "c",
        authorNameIsNot = "d",
        pageCountIsNot = 124
    ).let(::println)
    Book("a", "b", 123).validateThat(
        titleIsNot = "a",
        authorNameIsNot = "b",
        pageCountIsNot = 123
    ).let(::println)
    Book("a", "b", 123).validateThat(
        titleIsNot = "c",
        authorNameIsNot = "b",
        pageCountIsNot = 124
    ).let(::println)
}

Первый будет действительным со следующим выводом:

Valid(a=Book(title=a, authorName=b, pageCount=123))

Но второй будет выводить:

Invalid(e=Multiple(errors=NonEmptyList(all=[PropertyNotChanged(propertyName=pageCount), PropertyNotChanged(propertyName=title), PropertyNotChanged(propertyName=authorName)])))

Внутри этого Invalid экземпляра у нас есть NonEmptyList, который содержит все поля, которые не прошли проверку.Если мы немного переформатируем вывод, мы можем их увидеть:

Invalid(e=Multiple(
  errors=NonEmptyList(all=[
    PropertyNotChanged(propertyName=pageCount), 
    PropertyNotChanged(propertyName=title), 
    PropertyNotChanged(propertyName=authorName)
  ])
))

Теперь для третьего случая, поскольку только один из них остается неизменным, мы получаем следующий вывод:

Invalid(e=PropertyNotChanged(propertyName=authorName))
...