Kotlin使用Arrow验证超10字段时zip方法超限的优雅方案咨询
Kotlin Arrow库超过10个值的验证实现方案
在Kotlin中使用Arrow库做数据校验时,Validated类型内置的zip方法默认最多支持组合10个校验结果,新增第11个校验逻辑就会触发类型不匹配报错,现有实现代码如下:
fun CreateEventDTO.validate(): Validated<IncorrectInput, CreateEventDTO> = name.isEventNameValid() .zip( about.isAboutValid(), phone.isPhoneValid(), price.isPriceValid(), location.isLocationValid(), startDate.isStartDateValid(), // TODO add common validation for date // endDate.isEndDateValid(), status.isEventStatusValid(), access.isEventAccessValid(), category.isEventCategoryValid(), musicStyles.isMusicStyleValid() ) { name, _, _, price, location, status, access, category, musicStyles -> CreateEventDTO( name = name, about = about, phone = phone, price = price, location = location, startDate = startDate, endDate = endDate, status = status, access = access, category = category, musicStyles = musicStyles ) } .mapLeft(ApiError::IncorrectInput)
新增校验逻辑后抛出的类型报错如下:
Required: Semigroup<TypeVariable(E)> Found: ValidatedNel<InvalidAbout, String?> /* = Validated<NonEmptyList<InvalidAbout>, String?> */
可选实现方案
嵌套zip组合
不需要额外扩展API,直接多层嵌套zip即可保留错误累积能力,嵌套后调用flatten()拍平多余的Validated包装层即可:
fun CreateEventDTO.validate(): Validated<IncorrectInput, CreateEventDTO> = name.isEventNameValid() .zip( about.isAboutValid(), phone.isPhoneValid(), price.isPriceValid(), location.isLocationValid(), startDate.isStartDateValid(), endDate.isEndDateValid(), status.isEventStatusValid(), access.isEventAccessValid(), ) { name, about, phone, price, location, startDate, endDate, status, access -> category.isEventCategoryValid() .zip(musicStyles.isMusicStyleValid()) { category, musicStyles -> CreateEventDTO( name = name, about = about, phone = phone, price = price, location = location, startDate = startDate, endDate = endDate, status = status, access = access, category = category, musicStyles = musicStyles ) } }.flatten() .mapLeft(ApiError::IncorrectInput)
注意:嵌套时不要混用错误类型,保证所有校验分支的错误类型可以被
Semigroup合并(比如统一用NonEmptyList<ValidationError>作为错误类型)。
Validated计算块(生产环境推荐)
直接使用Arrow提供的validated上下文构建器,通过bind()方法逐个绑定校验结果,没有参数数量限制,代码可读性更高:
fun CreateEventDTO.validate(): Validated<IncorrectInput, CreateEventDTO> = validated { val name = name.isEventNameValid().bind() val about = about.isAboutValid().bind() val phone = phone.isPhoneValid().bind() val price = price.isPriceValid().bind() val location = location.isLocationValid().bind() val startDate = startDate.isStartDateValid().bind() val endDate = endDate.isEndDateValid().bind() val status = status.isEventStatusValid().bind() val access = access.isEventAccessValid().bind() val category = category.isEventCategoryValid().bind() val musicStyles = musicStyles.isMusicStyleValid().bind() // 后续新增校验字段直接追加bind即可,无数量上限 CreateEventDTO( name = name, about = about, phone = phone, price = price, location = location, startDate = startDate, endDate = endDate, status = status, access = access, category = category, musicStyles = musicStyles ) }.mapLeft { ApiError.IncorrectInput(it) }
这个方案的优势:
- 无参数数量限制,扩展字段成本极低
- 字段赋值和校验结果一一对应,不会出现原zip写法中用
_占位、误引用外部字段的问题 - 完全保留zip的错误累积特性,所有校验错误会被一次性收集返回,不会提前终止
自定义高元数zip扩展
如果偏好zip的编码风格,可以参照Arrow内置zip的源码,自行扩展11参、12参甚至更高参数数量的zip扩展函数。但这种方式需要为每个元数单独写扩展,维护成本较高,不推荐优先使用。
内容的提问来源于stack exchange,提问作者Victor Orlyk
相关产品推荐
相关产品推荐

