
Type-safe, fluent validation DSL for expressive, composable rules with zero-reflection performance, modular extensibility, detailed error reporting, fail-fast or collect-all modes, and optional datetime validators.
A fluent, declarative, and type-safe data validation DSL for Kotlin.
[!IMPORTANT] Version 3.0.0 is in active development — major rewrite with a new API. Documentation website and sample app coming soon.
⚠️ IMPORTANT NOTICE: V3 Relocation & Rename > If you used this library in the past, please note that the project has been entirely relocated and renamed. What was originally planned asv3.0.0of thevalidatorlibrary is nowatelier-validator. All modules have been renamed (e.g.,validator-coreis nowatelier-validator-core) to reflect a cleaner, more robust architecture.
Add the dependencies to your build.gradle.kts:
dependencies {
// Core validation library
implementation("dev.megatilus.atelier:validator-core:$version")
// Optional: kotlinx-datetime integration
implementation("dev.megatilus.atelier:validator-kotlinx-datetime:$version")
// Optional: Ktor server integration
implementation("dev.megatilus.atelier:validator-ktor-server:$version")
// Optional: Ktor client integration
implementation("dev.megatilus.atelier:validator-ktor-client:$version")
}Define your data class and create a validator:
data class User(
val name: String,
val email: String,
val password: String,
val age: Int?,
val tags: List<String>?
)
val userValidator = AtelierValidator<User> {
User::name {
notBlank()
minLength(2)
maxLength(50)
}
User::email {
notBlank()
email()
}
User::password {
notBlank()
strongPassword()
}
User::age {
range(18, 120)
}
User::tags {
isNotEmpty()
minSize(1)
maxSize(5)
}
}val result = userValidator.validate(user)
when (result) {
is ValidationResult.Success -> println("✅ Validation passed")
is ValidationResult.Failure -> {
result.errors.forEach { error ->
println("❌ ${error.fieldName}: ${error.message}")
}
}
}val result = userValidator.validateFirst(user)Override the message or error code on any rule with hint and withCode:
User::email {
notBlank() hint "Email is required"
email() hint "Invalid email format" withCode ValidationErrorCode("custom_email")
}User::name {
customRule { it == null || it.startsWith("user_") } hint "Must start with user_"
}@OptIn(InternalRuleDefinitionApi::class)
fun ValidationRule<String?>.username(): Rule = constrainIfNotNull(
message = "Must be 3–20 characters, letters, digits, _ or - only",
code = ValidationErrorCode("invalid_username"),
predicate = { it.length in 3..20 && it.all { c -> c.isLetterOrDigit() || c == '_' || c == '-' } }
)
// Usage
User::username {
username() hint "Invalid username"
}if (result is ValidationResult.Failure) {
val allErrors = result.errors
val emailErrors = result.errorsFor("email")
val firstEmail = result.firstErrorFor("email")
val byField = result.errorsByField
val count = result.errorCount
// Convenient formats
val fieldMessages = result.toFieldMessageMap() // Map<String, String>
val detailed = result.toDetailedList() // List<ErrorDetail>
}val addressValidator = AtelierValidator<Address> {
Address::city { notBlank() }
Address::zipCode { matches(Regex("^\\d{5}$")) }
}
val userValidator = AtelierValidator<User> {
User::name { notBlank() }
User::address { nested(addressValidator) }
}val tagValidator = AtelierValidator<Tag> {
Tag::name { notBlank(); minLength(2) }
}
val articleValidator = AtelierValidator<Article> {
Article::tags {
isNotEmpty()
each(tagValidator) hint "All tags must be valid"
}
}install(AtelierValidatorServer) {
register(userValidator)
}
post("/users") {
val user = call.receiveAndValidate<User>() ?: return@post
call.respond(HttpStatusCode.Created, user)
}val client = HttpClient {
install(AtelierValidatorClient) {
register(userValidator)
}
}
val user = client.getAndValidate<User>("https://api.example.com/users/1")| Module | Description |
|---|---|
validator-core |
Core validation rules — strings, numbers, collections, booleans, arrays, maps |
validator-kotlinx-datetime |
Date and time validators based on kotlinx-datetime
|
validator-ktor-server |
Ktor server plugin with automatic and manual validation |
validator-ktor-client |
Ktor client plugin for response validation |
Contributions are welcome! Feel free to open an issue or submit a pull request.
Licensed under the Apache 2.0 License.
A fluent, declarative, and type-safe data validation DSL for Kotlin.
[!IMPORTANT] Version 3.0.0 is in active development — major rewrite with a new API. Documentation website and sample app coming soon.
⚠️ IMPORTANT NOTICE: V3 Relocation & Rename > If you used this library in the past, please note that the project has been entirely relocated and renamed. What was originally planned asv3.0.0of thevalidatorlibrary is nowatelier-validator. All modules have been renamed (e.g.,validator-coreis nowatelier-validator-core) to reflect a cleaner, more robust architecture.
Add the dependencies to your build.gradle.kts:
dependencies {
// Core validation library
implementation("dev.megatilus.atelier:validator-core:$version")
// Optional: kotlinx-datetime integration
implementation("dev.megatilus.atelier:validator-kotlinx-datetime:$version")
// Optional: Ktor server integration
implementation("dev.megatilus.atelier:validator-ktor-server:$version")
// Optional: Ktor client integration
implementation("dev.megatilus.atelier:validator-ktor-client:$version")
}Define your data class and create a validator:
data class User(
val name: String,
val email: String,
val password: String,
val age: Int?,
val tags: List<String>?
)
val userValidator = AtelierValidator<User> {
User::name {
notBlank()
minLength(2)
maxLength(50)
}
User::email {
notBlank()
email()
}
User::password {
notBlank()
strongPassword()
}
User::age {
range(18, 120)
}
User::tags {
isNotEmpty()
minSize(1)
maxSize(5)
}
}val result = userValidator.validate(user)
when (result) {
is ValidationResult.Success -> println("✅ Validation passed")
is ValidationResult.Failure -> {
result.errors.forEach { error ->
println("❌ ${error.fieldName}: ${error.message}")
}
}
}val result = userValidator.validateFirst(user)Override the message or error code on any rule with hint and withCode:
User::email {
notBlank() hint "Email is required"
email() hint "Invalid email format" withCode ValidationErrorCode("custom_email")
}User::name {
customRule { it == null || it.startsWith("user_") } hint "Must start with user_"
}@OptIn(InternalRuleDefinitionApi::class)
fun ValidationRule<String?>.username(): Rule = constrainIfNotNull(
message = "Must be 3–20 characters, letters, digits, _ or - only",
code = ValidationErrorCode("invalid_username"),
predicate = { it.length in 3..20 && it.all { c -> c.isLetterOrDigit() || c == '_' || c == '-' } }
)
// Usage
User::username {
username() hint "Invalid username"
}if (result is ValidationResult.Failure) {
val allErrors = result.errors
val emailErrors = result.errorsFor("email")
val firstEmail = result.firstErrorFor("email")
val byField = result.errorsByField
val count = result.errorCount
// Convenient formats
val fieldMessages = result.toFieldMessageMap() // Map<String, String>
val detailed = result.toDetailedList() // List<ErrorDetail>
}val addressValidator = AtelierValidator<Address> {
Address::city { notBlank() }
Address::zipCode { matches(Regex("^\\d{5}$")) }
}
val userValidator = AtelierValidator<User> {
User::name { notBlank() }
User::address { nested(addressValidator) }
}val tagValidator = AtelierValidator<Tag> {
Tag::name { notBlank(); minLength(2) }
}
val articleValidator = AtelierValidator<Article> {
Article::tags {
isNotEmpty()
each(tagValidator) hint "All tags must be valid"
}
}install(AtelierValidatorServer) {
register(userValidator)
}
post("/users") {
val user = call.receiveAndValidate<User>() ?: return@post
call.respond(HttpStatusCode.Created, user)
}val client = HttpClient {
install(AtelierValidatorClient) {
register(userValidator)
}
}
val user = client.getAndValidate<User>("https://api.example.com/users/1")| Module | Description |
|---|---|
validator-core |
Core validation rules — strings, numbers, collections, booleans, arrays, maps |
validator-kotlinx-datetime |
Date and time validators based on kotlinx-datetime
|
validator-ktor-server |
Ktor server plugin with automatic and manual validation |
validator-ktor-client |
Ktor client plugin for response validation |
Contributions are welcome! Feel free to open an issue or submit a pull request.
Licensed under the Apache 2.0 License.