Pre-1.0 Notice: This library is under active development. The API may change between minor versions until 1.0.
A modern, composable validation library for Swift 6 with strict concurrency support.
- Protocol-Oriented: Built around the
Validatorprotocol for maximum composability - Type-Safe: Leverage Swift's type system to catch errors at compile time
- Composable: Combine validators using
and,or, andnotoperators - Swift 6 Ready: Full support for Swift 6 with strict concurrency
- Sendable: All public types conform to
Sendablefor safe concurrent usage - Zero Dependencies: No external dependencies except swift-docc-plugin
- Comprehensive: Validators for strings, numbers, collections, and custom types
- Schema Validation: Result builder DSL for validating complex objects
Add the following to your Package.swift file:
dependencies: [
.package(url: "https://github.com/0xLeif/swift-valid.git", from: "0.1.0")
]Then add Valid to your target dependencies:
targets: [
.target(
name: "YourTarget",
dependencies: [
.product(name: "Valid", package: "swift-valid")
]
)
]import Valid
// String validation
let emailValidator = EmailValidator()
let result = emailValidator.validate("[email protected]")
if result.isValid {
print("Valid email!")
} else {
print("Errors: \(result.errors)")
}
// Numeric validation
let ageValidator = RangeValidator(range: 18...120)
let isAdult = ageValidator.isValid(25) // true// Combine validators with logical operators
let usernameValidator = LengthValidator(range: 3...20)
.and(NotEmptyValidator())
.and(PatternValidator(pattern: "^[a-zA-Z0-9_]+$"))
// Or create alternatives
let passwordValidator = LengthValidator(range: 8...128)
.or(LengthValidator(range: 12...64).and(ContainsValidator(substring: "@")))struct User: Sendable {
let username: String
let email: String
let age: Int
}
let userValidator = Schema<User>.build {
Schema.property(\User.username, fieldName: "username", validator: LengthValidator(range: 3...20))
Schema.property(\User.email, fieldName: "email", validator: EmailValidator())
Schema.property(\User.age, fieldName: "age", validator: RangeValidator(range: 18...120))
}
let user = User(username: "johndoe", email: "[email protected]", age: 25)
let result = userValidator.validate(user)struct Product: Validatable, Sendable {
let name: String
let price: Double
let tags: [String]
func validate() -> ValidationResult {
Schema<Product>.build {
Schema.property(\Product.name, fieldName: "name", validator: NotEmptyValidator())
Schema.property(\Product.price, fieldName: "price", validator: PositiveValidator<Double>())
Schema.property(\Product.tags, fieldName: "tags", validator: CountValidator<[String]>.minimum(1))
}
.validate(self)
}
}
let product = Product(name: "Widget", price: 19.99, tags: ["sale"])
if product.isValid {
print("Product is valid!")
}LengthValidator: Validate string lengthNotEmptyValidator: Ensure string is not emptyNotBlankValidator: Ensure string contains non-whitespace charactersEmailValidator: Validate email formatPatternValidator: Match against regex patternsContainsValidator: Check for substring presencePrefixValidator: Validate string prefixSuffixValidator: Validate string suffix
RangeValidator: Validate value is within a rangeMinimumValidator: Validate minimum value (inclusive or exclusive)MaximumValidator: Validate maximum value (inclusive or exclusive)PositiveValidator: Ensure value is positiveNegativeValidator: Ensure value is negativeEvenValidator: Validate even integersOddValidator: Validate odd integersMultipleOfValidator: Check if value is a multiple of another
CountValidator: Validate collection countCollectionNotEmptyValidator: Ensure collection is not emptyEachValidator: Validate each element in a collectionUniqueValidator: Ensure all elements are uniqueContainsElementValidator: Check if collection contains an elementAllSatisfyValidator: Ensure all elements satisfy a predicateSortedValidator: Validate collection is sorted
AndValidator: Combine validators with logical ANDOrValidator: Combine validators with logical ORNotValidator: Negate a validatorAnyValidator: Type-erased validator
Create custom validators by conforming to the Validator protocol:
struct IPAddressValidator: Validator, Sendable {
func validate(_ value: String) -> ValidationResult {
let pattern = #"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$"#
let regex = try? NSRegularExpression(pattern: pattern)
let range = NSRange(value.startIndex..., in: value)
let matches = regex?.firstMatch(in: value, range: range) != nil
return .from(matches, message: "Invalid IP address format")
}
}Use AnyValidator for inline validation logic:
let evenNumberValidator = AnyValidator<Int>.predicate(
message: "Number must be even"
) { $0.isMultiple(of: 2) }// Get all errors
let result = validator.validate(value)
if case .invalid(let errors) = result {
errors.forEach { error in
print("\(error.message)")
if let field = error.context["field"] {
print("Field: \(field)")
}
}
}
// Throw on invalid
do {
try validator.validateOrThrow(value)
} catch let error as ValidError {
print("Validation failed: \(error.message)")
}Valid follows the distinctive 0xLeif Swift development patterns:
- Protocol-Oriented: Favor protocols and protocol extensions over inheritance
- Functional: Embrace map, flatMap, and other functional patterns
- Modern Swift: Leverage async/await, actors, property wrappers, and result builders
- Type Safety: Use the type system to prevent errors at compile time
- Clarity: Write code that is immediately understandable
- Composition: Break down complex problems into small, composable pieces
- iOS 16.0+
- macOS 13.0+
- tvOS 16.0+
- watchOS 9.0+
- visionOS 1.0+
MIT License - see LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.