joelklabo/swiftdatavalidator
A comprehensive validation framework for SwiftData models with a clean, declarative API.
Features
- π― Type-safe validation with Swift generics
- π Declarative API for defining validation rules
- π Localized error messages with recovery suggestions
- π§ Built-in validators for common use cases
- π¨ Custom validation rules for business logic
- π¦ Zero dependencies - pure Swift implementation
- π Swift 6 ready with full concurrency support
- π± Multi-platform - iOS, macOS, tvOS, watchOS, visionOS
Requirements
- Swift 6.0+
- iOS 18.0+ / macOS 15.0+ / tvOS 18.0+ / watchOS 11.0+ / visionOS 2.0+
Installation
Swift Package Manager
Add SwiftDataValidator to your Package.swift file:
dependencies: [
.package(url: "https://github.com/joelklabo/SwiftDataValidator", from: "1.0.0")
]Or add it through Xcode:
- File β Add Package Dependencies...
- Enter:
https://github.com/joelklabo/SwiftDataValidator - Click Add Package
Quick Start
1. Make your SwiftData model conform to Validatable:
import SwiftData
import SwiftDataValidator
@Model
final class User: Validatable {
var name: String = ""
var email: String = ""
var age: Int = 0
func validate() -> [ValidationError] {
var validator = Validator()
validator.validate(field: "name", value: name) { validator in
validator.required()
validator.notEmpty()
validator.maxLength(50)
}
validator.validate(field: "email", value: email) { validator in
validator.required()
validator.matchesEmail()
}
validator.validate(field: "age", value: age) { validator in
validator.required()
validator.range(min: 18, max: 120)
}
return validator.errors()
}
}2. Validate before saving:
let user = User()
user.name = "John Doe"
user.email = "john@example.com"
user.age = 25
let errors = user.validate()
if errors.isEmpty {
context.insert(user)
try context.save()
} else {
// Handle validation errors
for error in errors {
print("\(error.field): \(error.localizedDescription)")
if let suggestion = error.recoverySuggestion {
print(" β \(suggestion)")
}
}
}Available Validators
String Validators
validator.validate(field: "username", value: username) { validator in
validator.required() // Not nil
validator.notEmpty() // Not empty or whitespace
validator.minLength(3) // Minimum length
validator.maxLength(20) // Maximum length
validator.matchesEmail() // Valid email format
validator.matchesURL() // Valid URL format
validator.matchesPhoneNumber() // Valid phone number
}Numeric Validators
validator.validate(field: "age", value: age) { validator in
validator.required()
validator.range(min: 0, max: 150) // Within range
}Date Validators
validator.validate(field: "birthDate", value: birthDate) { validator in
validator.required()
validator.notFuture() // Not in the future
validator.notPast() // Not in the past
}Field Matching
validator.validate(field: "confirmPassword", value: confirmPassword) { validator in
validator.required()
validator.matches(password, fieldName: "password")
}Custom Validators
validator.validate(field: "username", value: username) { validator in
validator.custom({ value in
// Return true if valid
return value != "admin"
}, error: .businessRule(reason: "Username 'admin' is reserved"))
}Advanced Usage
Custom Validation Rules
You can create custom validation rules using the ValidationRule enum:
extension ValidationRule {
static func mustContainSpecialCharacter() -> ValidationRule {
.custom(message: "Password must contain at least one special character")
}
}
// Usage
validator.validate(field: "password", value: password) { validator in
validator.custom({ password in
guard let password else { return false }
let specialCharacters = CharacterSet(charactersIn: "!@#$%^&*()_+-=[]{}|;:,.<>?")
return password.rangeOfCharacter(from: specialCharacters) != nil
}, error: .mustContainSpecialCharacter())
}Validation in SwiftUI
Create a view model that handles validation:
@Observable
final class UserFormViewModel {
var name = ""
var email = ""
var errors: [ValidationError] = []
func validate() {
let user = User()
user.name = name
user.email = email
errors = user.validate()
}
func save(in context: ModelContext) throws {
validate()
guard errors.isEmpty else { return }
let user = User()
user.name = name
user.email = email
context.insert(user)
try context.save()
}
}Localization
All error messages are localizable. Create a Localizable.strings file and override the default messages:
"email is required" = "Email address is required";
"email must be a valid email address" = "Please enter a valid email address";
"Please provide a value for email" = "Email cannot be left blank";API Documentation
Full API documentation is available at Swift Package Index.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request. Before contributing, please:
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
Make sure all tests pass and add new tests for any new functionality.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments
- Built with β€οΈ using Swift 6 and SwiftData
Package Metadata
Repository: joelklabo/swiftdatavalidator
Default branch: main
README: README.md