Konform
Konform
概要
Konformは、Kotlinマルチプラットフォーム対応のポータブルなバリデーションライブラリです。タイプセーフなDSLを提供し、読みやすく保守しやすいバリデーションルールの定義を可能にします。純粋なKotlinで実装されており、JVM、Android、JS、Nativeなど、あらゆるKotlinプラットフォームで動作します。
特徴
- タイプセーフなDSL: プロパティ参照を使用した型安全なバリデーション定義
- マルチプラットフォーム対応: Kotlin/JVM、Kotlin/JS、Kotlin/Native全てをサポート
- 詳細なエラー情報: エラーパスとカスタムメッセージによる分かりやすいエラー報告
- カスタムバリデーション: 独自のバリデーションルールを簡単に追加可能
- コンテキストベースの検証: 動的な条件に基づくバリデーション
- 再帰的検証: ネストされた構造や再帰的なデータ構造の検証
- 軽量: 依存関係が少なく、パフォーマンスへの影響が最小限
インストール
kotlin {
sourceSets {
commonMain {
dependencies {
implementation("io.konform:konform:0.11.0")
}
}
}
}
使用例
基本的なバリデーション
import io.konform.validation.Validation
import io.konform.validation.Valid
import io.konform.validation.Invalid
data class UserProfile(
val fullName: String,
val email: String,
val age: Int?
)
val validateUser = Validation<UserProfile> {
UserProfile::fullName {
minLength(2)
maxLength(100)
}
UserProfile::email {
pattern(".+@.+\\..+") hint "有効なメールアドレスを入力してください"
}
UserProfile::age ifPresent {
minimum(0) hint "年齢は0以上である必要があります"
maximum(150) hint "年齢は150以下である必要があります"
}
}
// 使用例
val validUser = UserProfile("田中太郎", "[email protected]", 25)
val result = validateUser(validUser)
when (result) {
is Valid -> println("バリデーション成功: ${result.value}")
is Invalid -> {
result.errors.forEach { error ->
println("エラー: ${error.dataPath} - ${error.message}")
}
}
}
カスタムバリデーション
val validatePassword = Validation<String> {
minLength(8) hint "パスワードは8文字以上必要です"
// カスタム検証
constrain("大文字を含む") { password ->
password.any { it.isUpperCase() }
}
constrain("数字を含む") { password ->
password.any { it.isDigit() }
}
constrain("特殊文字を含む") { password ->
password.any { !it.isLetterOrDigit() }
}
}
// パスワード強度チェック付きユーザー登録
data class UserRegistration(
val username: String,
val password: String,
val confirmPassword: String
)
val validateRegistration = Validation<UserRegistration> {
UserRegistration::username {
minLength(3)
maxLength(20)
pattern("^[a-zA-Z0-9_]+$") hint "英数字とアンダースコアのみ使用可能"
}
UserRegistration::password {
run(validatePassword)
}
// クロスフィールドバリデーション
validate("パスワード確認") { registration ->
registration.password == registration.confirmPassword
} hint "パスワードが一致しません"
}
ネストされた構造の検証
data class Address(
val street: String,
val city: String,
val postalCode: String,
val country: String
)
data class Company(
val name: String,
val address: Address,
val employees: List<UserProfile>
)
val validateAddress = Validation<Address> {
Address::street {
minLength(5)
}
Address::city {
minLength(2)
}
Address::postalCode {
pattern("\\d{3}-\\d{4}") hint "郵便番号は000-0000形式で入力してください"
}
}
val validateCompany = Validation<Company> {
Company::name {
minLength(1)
maxLength(100)
}
// ネストされたオブジェクトの検証
Company::address {
run(validateAddress)
}
// コレクションの検証
Company::employees onEach {
run(validateUser)
}
Company::employees {
minSize(1) hint "少なくとも1人の従業員が必要です"
}
}
コンテキストベースの検証
sealed class PaymentMethod
data class CreditCard(val number: String, val cvv: String) : PaymentMethod()
data class BankTransfer(val accountNumber: String) : PaymentMethod()
data class PayPal(val email: String) : PaymentMethod()
data class Order(
val amount: Double,
val paymentMethod: PaymentMethod
)
val validateOrder = Validation<Order> {
Order::amount {
minimum(0.01) hint "注文金額は0.01以上必要です"
}
// 動的な型チェックとバリデーション
Order::paymentMethod validate { order ->
when (val method = order.paymentMethod) {
is CreditCard -> Validation<CreditCard> {
CreditCard::number {
pattern("\\d{16}") hint "カード番号は16桁の数字です"
}
CreditCard::cvv {
pattern("\\d{3,4}") hint "CVVは3-4桁の数字です"
}
}(method)
is BankTransfer -> Validation<BankTransfer> {
BankTransfer::accountNumber {
pattern("\\d{7,14}") hint "口座番号は7-14桁の数字です"
}
}(method)
is PayPal -> Validation<PayPal> {
PayPal::email {
pattern(".+@.+\\..+") hint "有効なメールアドレスを入力してください"
}
}(method)
}
}
}
エラーの処理とカスタマイズ
enum class Severity {
ERROR, WARNING, INFO
}
val validateWithSeverity = Validation<UserProfile> {
UserProfile::fullName {
minLength(2) userContext Severity.ERROR
}
UserProfile::email {
pattern(".+@.+\\..+").replace(
hint = "メールアドレスの形式が正しくありません",
userContext = Severity.ERROR
)
}
UserProfile::age ifPresent {
validate("年齢の推奨範囲") { it in 18..65 }
.hint("このサービスは18-65歳の方に最適化されています")
.userContext(Severity.WARNING)
}
}
// エラー処理
val result = validateWithSeverity(user)
if (result is Invalid) {
val errors = result.errors.filter {
it.userContext == Severity.ERROR
}
val warnings = result.errors.filter {
it.userContext == Severity.WARNING
}
if (errors.isNotEmpty()) {
println("エラー:")
errors.forEach { println(" - ${it.dataPath}: ${it.message}") }
}
if (warnings.isNotEmpty()) {
println("警告:")
warnings.forEach { println(" - ${it.dataPath}: ${it.message}") }
}
}
フェイルファースト検証
// 高コストな検証を後に実行
val quickValidation = Validation<String> {
minLength(8)
maxLength(100)
}
val expensiveValidation = Validation<String> {
constrain("データベースチェック") { value ->
// データベースアクセスなど重い処理
checkDatabase(value)
}
}
val efficientValidation = Validation<String> {
// quickValidationが成功した場合のみexpensiveValidationを実行
run(quickValidation andThen expensiveValidation)
}
テストでの使用
import io.kotest.matchers.should
import io.kotest.matchers.shouldBe
import io.konform.validation.Invalid
import io.konform.validation.Valid
class UserValidationTest {
@Test
fun `有効なユーザーはバリデーションに成功する`() {
val user = UserProfile("山田花子", "[email protected]", 30)
val result = validateUser(user)
result shouldBe Valid(user)
}
@Test
fun `無効なメールアドレスはエラーになる`() {
val user = UserProfile("山田花子", "invalid-email", 30)
val result = validateUser(user)
result should { it is Invalid }
(result as Invalid).errors.any {
it.dataPath == ".email"
} shouldBe true
}
}
比較・代替手段
類似ライブラリとの比較
- Valiktor: より多くの組み込みバリデーターを提供するが、開発が停滞
- Arrow Validated: 関数型プログラミング寄りで、Arrowエコシステムとの統合が前提
- Android Form Validators: Android専用でUI統合に特化
Konformを選ぶべき場合
- Kotlinマルチプラットフォームプロジェクト
- シンプルで読みやすいDSLを重視
- 軽量なライブラリを求める場合
- カスタムバリデーションの柔軟性が必要
学習リソース
まとめ
KonformはKotlinエコシステムにおける優れたバリデーションライブラリです。タイプセーフなDSL、マルチプラットフォーム対応、詳細なエラー報告により、あらゆるKotlinプロジェクトでデータ検証を簡潔かつ効果的に実装できます。特にKotlinマルチプラットフォームプロジェクトでは、統一されたバリデーションロジックを全プラットフォームで共有できる点が大きな利点となります。