Comprehensive form validation system for the Summon framework with built-in validators, custom validation logic, and error handling.
The form validation system provides type-safe validators that can be composed together to validate form fields. It includes common validation patterns and supports custom validation logic.
import code.yousef.summon.components.forms.Validator
import code.yousef.summon.components.forms.validateValue
// Validate a single value
val error = validateValue(
value = "test@example.com",
validators = listOf(
Validator.required("Email is required"),
Validator.email("Must be a valid email")
)
)
// error is null if valid, or contains error message if invalid
Validates that a field is not empty:
Validator.required("This field is required")
Validates email format:
Validator.email("Must be a valid email address")
Validates minimum length:
Validator.minLength(8, "Must be at least 8 characters")
Validates maximum length:
Validator.maxLength(100, "Must be at most 100 characters")
Validates against a regular expression:
Validator.pattern(
"^[A-Za-z0-9]+$",
"Only alphanumeric characters allowed"
)
Validates minimum numeric value:
Validator.min(0.0, "Must be at least 0")
Validates maximum numeric value:
Validator.max(100.0, "Must be at most 100")
Validates URL format:
Validator.url("Must be a valid URL")
Validates that value matches another field:
Validator.matches(passwordValue, "Passwords do not match")
Creates a custom validator with user-defined logic:
Validator.custom { value ->
if (value.contains(" ")) {
"Value cannot contain spaces"
} else {
null // null means valid
}
}
Base sealed class for all validators:
sealed class Validator {
abstract fun validate(value: String): String?
}
Each validator implements the validate method which returns:
null if the value is valid// Create validators using companion object methods
Validator.required(message: String = "This field is required")
Validator.email(message: String = "Must be a valid email address")
Validator.minLength(length: Int, message: String? = null)
Validator.maxLength(length: Int, message: String? = null)
Validator.pattern(pattern: String, message: String = "Invalid format")
Validator.min(min: Double, message: String? = null)
Validator.max(max: Double, message: String? = null)
Validator.url(message: String = "Must be a valid URL")
Validator.custom(validationFn: (String) -> String?)
Validator.matches(otherValue: String, message: String = "Values do not match")
// Validate and return first error
fun validateValue(value: String, validators: List<Validator>): String?
// Validate and return all errors
fun validateValueAll(value: String, validators: List<Validator>): List<String>
val emailValidators = listOf(
Validator.required("Email is required"),
Validator.email("Must be a valid email")
)
val error = validateValue(userEmail, emailValidators)
if (error != null) {
// Show error message
}
val passwordValidators = listOf(
Validator.required("Password is required"),
Validator.minLength(8, "Must be at least 8 characters"),
Validator.pattern(
"^(?=.*[A-Z])(?=.*[0-9]).*$",
"Must contain uppercase letter and number"
)
)
val password = "MyP@ssw0rd"
val confirmValidators = listOf(
Validator.required("Please confirm password"),
Validator.matches(password, "Passwords do not match")
)
val usernameValidators = listOf(
Validator.required("Username is required"),
Validator.minLength(3, "Must be at least 3 characters"),
Validator.maxLength(20, "Must be at most 20 characters"),
Validator.pattern(
"^[a-zA-Z0-9_]+$",
"Only letters, numbers, and underscores allowed"
),
Validator.custom { value ->
if (value.startsWith("_")) {
"Cannot start with underscore"
} else null
}
)
val ageValidators = listOf(
Validator.required("Age is required"),
Validator.min(18.0, "Must be at least 18 years old"),
Validator.max(120.0, "Please enter a valid age")
)
val urlValidators = listOf(
Validator.required("URL is required"),
Validator.url("Please enter a valid URL")
)
val validators = listOf(
Validator.required("Email is required"),
Validator.email("Must be a valid email"),
Validator.custom { value ->
if (!value.endsWith("@company.com")) {
"Must use company email address"
} else null
}
)
Get all validation errors instead of just the first:
val errors = validateValueAll(value, validators)
errors.forEach { error ->
println("Error: $error")
}
While the framework doesn't yet have a FormTextField component that directly uses validators, you can integrate validation into your own components:
@Composable
fun ValidatedTextField(
value: String,
onValueChange: (String) -> Unit,
validators: List<Validator>,
label: String
) {
val error = remember(value) {
validateValue(value, validators)
}
Column {
TextField(
value = value,
onValueChange = onValueChange,
modifier = Modifier()
.apply {
if (error != null) {
style("border-color", "red")
}
}
)
if (error != null) {
Text(
error,
modifier = Modifier()
.style("color", "red")
.style("font-size", "12px")
)
}
}
}
Validate an entire form:
data class FormData(
val email: String,
val password: String,
val confirmPassword: String
)
fun validateForm(data: FormData): Map<String, String?> {
return mapOf(
"email" to validateValue(data.email, listOf(
Validator.required(),
Validator.email()
)),
"password" to validateValue(data.password, listOf(
Validator.required(),
Validator.minLength(8)
)),
"confirmPassword" to validateValue(data.confirmPassword, listOf(
Validator.required(),
Validator.matches(data.password)
))
)
}
val errors = validateForm(formData)
val hasErrors = errors.values.any { it != null }
@Composable
fun MyForm() {
val email = remember { mutableStateOf("") }
val showErrors = remember { mutableStateOf(false) }
val validators = listOf(
Validator.required("Email is required"),
Validator.email("Invalid email")
)
TextField(
value = email.value,
onValueChange = {
email.value = it
// Optionally validate on change
if (showErrors.value) {
val error = validateValue(it, validators)
// Update error display
}
}
)
Button(
onClick = {
showErrors.value = true
val error = validateValue(email.value, validators)
if (error == null) {
// Submit form
}
},
label = "Submit"
)
}
All validators support custom error messages:
// Default message
Validator.required() // "This field is required"
// Custom message
Validator.required("Please enter your name")
// For validators with parameters, message can be customized
Validator.minLength(8, "Password must be at least 8 characters long")
listOf(
Validator.required("Email is required"),
Validator.email()
)
Only validate format if not empty:
listOf(
Validator.email() // Email validator only checks if not empty
)
listOf(
Validator.required(),
Validator.minLength(8),
Validator.pattern(".*[A-Z].*", "Must contain uppercase"),
Validator.pattern(".*[a-z].*", "Must contain lowercase"),
Validator.pattern(".*[0-9].*", "Must contain number"),
Validator.pattern(".*[@#$%^&+=].*", "Must contain special character")
)
listOf(
Validator.pattern("^[0-9]+$", "Must be a positive integer"),
Validator.min(0.0)
)
required first, then format validators