The basic idea is to separate the validation logic from the rest of your project. Validated self-encapsulates all validation logic into reusable rules. All rules are type-safe and tested. Additionally it features input formatting. Fields can also be marked as optional.
Usage
The project includes an Example project.
Getting started
Using Validated is very simple and straight-forward.
Example with Property Wrapper
@Validated(rules: [.notEmpty, .isEmail], formatters: [.trimmed, .lowercased])
var email: String?
The ValidationResult is an Enum that has two states:
case valid(_ value: Value?)
case notValid
.valid has an associated value, that will return the validated value, if there is one. This is optional, because the strategy can be .optional.
The ValidationResult also has two computed properties: isValid which returns a Bool and value which returns the optional value.
Additionally, ValidationResult houses the .validate(..) and .format(..) functions to manually invoke a validation and formatting of an input value.
Note: ValidationResult also conforms to the Equatable protocol.
Rule
A rule is defined with a closure, that receives a Value and returns a Bool. The Value is not optional.
Right now there are 42 rules included in the standard package.
Example
extension ValidationRule where Value: StringProtocol {
static var notEmpty: Self {
return ValidationRule {
return !$0.isEmpty
}
}
}
Note: return keywords can be ommitted, to make a rule even more lightweight.
Formatter
A formatter is similar to a rule. It is defined as a closure, that receives a Value and returns a Value. Neither are optional.
Right now there are 7 formatters included in the standard package.
Example
extension ValidationFormatter where Value == String {
static var uppercased: Self {
return ValidationFormatter {
return $0.uppercased()
}
}
}
Note: return keywords can be ommitted, to make a formatter even more lightweight.
Strategy
In some situations we only want to validate fields that actually contain something. To deal with this kind of situation Validated has two modes, that are reflected in ValidationStrategy.
The two modes are:
case required
case optional
If you are using a @Validated object, you can simply pass the strategy to the property wrapper like this:
@Validated(rule: .isURL, formatters: [.trimmed, .lowercased], strategy: .optional)
var website: String?
Note: By default, @Validated uses the .required strategy.
Validated
The main usage of this framework is bundled within the @Validated property wrapper. It takes care of all the validating, formatting and contains the strategy logic. Additionally it contains optional Combine support.
Defining it, is very straight-forward:
@Validated(rules: [.notEmpty, .isEmail], formatters: [.trimmed, .lowercased])
var email: String?
Note: Formatting will be done, before the rules are checked. Therefore, it will validate the formatted values.
Initialization
@Validated can be initialized in many ways:
one ValidationRule or an Array of ValidationRules
zero, one or an Array of ValidationFormatters
a ValidationStrategy. By default, if none provided, it uses the .required strategy.
Combine
@Validated also contains some Combine extensions. They will only take affect, if Combine can be imported. Basically, there are two publishers:
validationPublisher -> publishes a ValidationResult for every change to the value.
valuePublisher -> publishes a formatted value, whenever a .validValidationResult occurs.
Note: Even though Combine is supported, you can still use it without it. Validated is usable from iOS9+.
Projected Value
The projected value returns the validationPublisher(). It publishes a ValidationResult every time you make a change to the properties’ value.
Example
$email
.sink { [weak self] (result) in
switch result {
case .valid(let value):
self?.passwordValidLabel.text = "✅"
print("new password: valid -> \(value ?? "nil")")
case .notValid:
self?.passwordValidLabel.text = "❌"
print("new password: notValid")
}
}
.store(in: &cancellables)
Note: Make sure you connect your TextField to your @Validated object. This can be done by binding your TextField values to the property with Combine or using the NotificationCenter. Examples of how this can be done are included in the Example project.
Customization
Validated was designed to make it easily extensible. Therefore making your own rules and formatters is encouraged. If you think that you created a rule or a formatter that would fit into the standard library, feel free to create a pull request.
Note: Every Rule and Formatter should be tested.
Making your own Rule
Here is an example of how you could make your own rule called isAwesome.
extension ValidationRule where Value == String {
static var isAwesome: Self {
return ValidationRule {
return $0 == "Awesome"
}
}
}
There are certain naming conventions that should be followed to guarantee simple code and improve readability.
ValidationRule
Every ValidationRule should be named in the present tense and should be treated in the third person. They should also be as short as possible while also being as expressive as possible.
Examples:
notEmpty
contains
startsWith
isNumber
isEmail
ValidationFormatter
Every ValidationFormatter should be named in the simple past tense (e.g. trimmed or uppercased). The reason behind this is, that formatters are applied inside the @Validated object, before it is returned when accessing the value. Therefore, the formatters have already been applied and the text is now trimmed or uppercased.
Installation
Swift Package Manager (Recommended)
Add the following dependency to your Package.swift file:
Validated
A rule-based validation framework.
Key Features
About this project
The basic idea is to separate the validation logic from the rest of your project. Validated self-encapsulates all validation logic into reusable rules. All rules are type-safe and tested. Additionally it features input formatting. Fields can also be marked as optional.
Usage
The project includes an Example project.
Getting started
Using
Validatedis very simple and straight-forward.Example with Property Wrapper
Manual validation and formatting
Core Components
Result
The
ValidationResultis anEnumthat has two states:.validhas an associated value, that will return the validated value, if there is one. This is optional, because the strategy can be.optional.The
ValidationResultalso has two computed properties:isValidwhich returns aBoolandvaluewhich returns the optional value.Additionally,
ValidationResulthouses the.validate(..)and.format(..)functions to manually invoke a validation and formatting of an input value.Rule
A rule is defined with a closure, that receives a
Valueand returns aBool. TheValueis not optional.Example
Formatter
A formatter is similar to a rule. It is defined as a closure, that receives a
Valueand returns aValue. Neither are optional.Example
Strategy
In some situations we only want to validate fields that actually contain something. To deal with this kind of situation
Validatedhas two modes, that are reflected inValidationStrategy.The two modes are:
If you are using a
@Validatedobject, you can simply pass the strategy to the property wrapper like this:Validated
The main usage of this framework is bundled within the
@Validatedproperty wrapper. It takes care of all the validating, formatting and contains the strategy logic. Additionally it contains optional Combine support.Defining it, is very straight-forward:
Initialization
@Validatedcan be initialized in many ways:ValidationRuleor an Array ofValidationRulesValidationFormattersValidationStrategy. By default, if none provided, it uses the.requiredstrategy.Combine
@Validatedalso contains some Combine extensions. They will only take affect, ifCombinecan be imported. Basically, there are two publishers:validationPublisher-> publishes aValidationResultfor every change to the value.valuePublisher-> publishes a formatted value, whenever a.validValidationResultoccurs.Projected Value
The projected value returns the
validationPublisher(). It publishes aValidationResultevery time you make a change to the properties’ value.Example
Customization
Validatedwas designed to make it easily extensible. Therefore making your own rules and formatters is encouraged. If you think that you created a rule or a formatter that would fit into the standard library, feel free to create a pull request.Making your own Rule
Here is an example of how you could make your own rule called
isAwesome.This could also be written like this:
Making your own Formatter
Here is an example of how you could make your own formatter called
replacedAWithB, that replaces every occurance ofAwith aB.This could also be written like this:
Naming Conventions
There are certain naming conventions that should be followed to guarantee simple code and improve readability.
ValidationRule
Every
ValidationRuleshould be named in the present tense and should be treated in the third person. They should also be as short as possible while also being as expressive as possible.Examples:
notEmptycontainsstartsWithisNumberisEmailValidationFormatter
Every
ValidationFormattershould be named in the simple past tense (e.g.trimmedoruppercased). The reason behind this is, that formatters are applied inside the@Validatedobject, before it is returned when accessing the value. Therefore, the formatters have already been applied and the text is nowtrimmedoruppercased.Installation
Swift Package Manager (Recommended)
Add the following dependency to your
Package.swiftfile:Cartage
Add the following line to your Cartfile.
Then run
carthage update.Manually
Just drag and drop the
.swiftfiles from theValidatedfolder into your project.Version Compatibility
Validated is built with Swift 5.2. It supports iOS9+.
Contributing
Contact
You can check out my website or follow me on twitter.