2

我有一个对象(书),事件应该更新哪些字段(作者更改)。假设这本书的作者字段只有在作者结婚并改名时才会改变,但如果作者刚搬到一个新城市,这本书就不会改变。

在这个简单的情况下,我可以检查是否book.authorName == event.author.name并返回一个Either<NothingChangedFailure, Book>. 但是我怎么能检查不止一个字段呢?如果我继续使用 Either,该过程将在NothingChangedFailure它遇到的第一次停止,但我想汇总所有更新,并且只有NothingChangedFailure在 book 中的所有字段都没有更改时才返回。

我尝试了OptionEither并继续阅读,Validated但如果出现单个失败,它们似乎都失败了整个结果。那么有没有我看不到的选项?

4

1 回答 1

2

Validated上有一个示例,它显示了我们可以组合验证失败的情况。

对于您的情况(我将在这里假设一些事情,例如书中可用的字段),我想它看起来像:

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

在这里,我们使用 Semigroup 的定义创建错误:

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))
于 2019-05-20T16:40:12.247 回答