-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
fca0ffb
commit b8f872e
Showing
22 changed files
with
1,240 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
public extension ValidatorResults { | ||
/// `ValidatorResult` representing | ||
/// validation has failed. | ||
struct Invalid { | ||
public let reason: String | ||
} | ||
} | ||
|
||
extension ValidatorResults.Invalid: ValidatorResult { | ||
public var isFailure: Bool { | ||
true | ||
} | ||
|
||
public var successDescriptions: [String] { | ||
[] | ||
} | ||
|
||
public var failureDescriptions: [String] { | ||
[ | ||
"is invalid: \(self.reason)" | ||
] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
public extension ValidatorResults { | ||
/// `ValidatorResult` representing | ||
/// a group of `ValidatorResult`s. | ||
struct Nested { | ||
public let results: [ValidatorResult] | ||
} | ||
} | ||
|
||
extension ValidatorResults.Nested: ValidatorResult { | ||
public var isFailure: Bool { | ||
self.results.first { $0.isFailure } != nil | ||
} | ||
|
||
public var successDescriptions: [String] { | ||
self.results.lazy.filter { !$0.isFailure } | ||
.flatMap { $0.successDescriptions } | ||
} | ||
|
||
public var failureDescriptions: [String] { | ||
self.results.lazy.filter { $0.isFailure } | ||
.flatMap { $0.failureDescriptions } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
public extension ValidatorResults { | ||
/// `ValidatorResult` representing | ||
/// validation is skipped by validator. | ||
struct Skipped {} | ||
} | ||
|
||
extension ValidatorResults.Skipped: ValidatorResult { | ||
public var isFailure: Bool { | ||
false | ||
} | ||
|
||
public var successDescriptions: [String] { | ||
[] | ||
} | ||
|
||
public var failureDescriptions: [String] { | ||
[] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/// A name space type containing all default | ||
/// ``ValidatorResult`` provided by this library. | ||
public struct ValidatorResults {} | ||
|
||
/// A type representing result of validations | ||
/// performed by ``Validator``. | ||
public protocol ValidatorResult { | ||
/// Whether validation succedded or failed. | ||
var isFailure: Bool { get } | ||
/// Descriptions to use in the event of validation succeeds. | ||
var successDescriptions: [String] { get } | ||
/// Descriptions to use in the event of validation fails. | ||
var failureDescriptions: [String] { get } | ||
} | ||
|
||
/// An error type representing validation failure. | ||
public struct ValidationError: Error, CustomStringConvertible { | ||
/// The actual result of validation. | ||
public let result: ValidatorResult | ||
|
||
/// A textual representation of this error. | ||
public var description: String { | ||
return result.failureDescriptions.joined(separator: "\n") | ||
} | ||
} | ||
|
||
internal extension ValidatorResult { | ||
/// The result success and failure descriptions combined. | ||
var resultDescription: String { | ||
var desc: [String] = [] | ||
desc.append("→ Successes") | ||
desc += self.failureDescriptions.indented() | ||
desc.append("→ Failures") | ||
desc += self.failureDescriptions.indented() | ||
return desc.joined(separator: "\n") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/// A type capable of being validated. | ||
/// | ||
/// While confirming a ``Validator`` needs to be provided for perorming | ||
/// validation and conformance adds a throwing ``validate()`` method. | ||
/// | ||
/// ```swift | ||
/// struct User: Validatable { | ||
/// let name: String | ||
/// let email: String | ||
/// let age: Int | ||
/// | ||
/// var validator: Validator<Self> { | ||
/// return Validator<Self> | ||
/// .name(!.isEmpty, .alphanumeric) | ||
/// .email(.isEmail) | ||
/// } | ||
/// } | ||
/// ``` | ||
public protocol Validatable { | ||
/// The ``Validator`` used to validate data. | ||
var validator: Validator<Self> { get } | ||
} | ||
|
||
public extension Validatable { | ||
/// Performs validation on current data | ||
/// using provided ``validator``. | ||
/// | ||
/// - Throws: ``ValidationError`` if validation fails. | ||
func validate() throws { | ||
let result = self.validatorResult() | ||
guard result.isFailure else { return } | ||
throw ValidationError(result: result) | ||
} | ||
|
||
/// Performs validation on current data and provides result. | ||
/// | ||
/// - Returns: The result of validation. | ||
internal func validatorResult() -> ValidatorResult { | ||
return self.validator.validate(self) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
/// Adds ``Validator``s for property type `U` of parent type `T`. | ||
/// | ||
/// Use the `@dynamicCallable` feature to provide ``Validator``s | ||
/// to add for the already provided property that the validation was created with. | ||
@dynamicCallable | ||
public struct Validation<T, U> { | ||
/// The `KeyPath` to property for which current validation added. | ||
internal let keyPath: KeyPath<T, U> | ||
/// The validations store for all the property based | ||
/// validations of parent type. | ||
internal var parent: Validations<T> | ||
|
||
/// Creates a new validation for the provided propety. | ||
/// | ||
/// - Parameters: | ||
/// - keyPath: The `KeyPath` for the property. | ||
/// - parent: The store for all the property based validations of parent type. | ||
/// | ||
/// - Returns: The newly created validation. | ||
internal init(keyPath: KeyPath<T, U>, parent: Validations<T>) { | ||
self.keyPath = keyPath | ||
self.parent = parent | ||
} | ||
|
||
/// Adds validators for a property of parent type `T`. | ||
/// | ||
/// When validation is performed on parent type data, | ||
/// properties will be validated with the provided validators. | ||
/// | ||
/// - Parameter args: The validators to add. | ||
/// - Returns: The ``Validations`` object that stores | ||
/// all property validation of parent type `T`. | ||
public func dynamicallyCall( | ||
withArguments args: [Validator<U>] | ||
) -> Validations<T> { | ||
for arg in args { parent.addValidator(at: keyPath, arg) } | ||
return parent | ||
} | ||
|
||
/// Adds validators for a property of parent type `T`. | ||
/// | ||
/// When validation is performed on parent type data, | ||
/// properties will be validated with the provided validators. | ||
/// | ||
/// - Parameter args: The validators to add. | ||
/// - Returns: The ``Validator`` of parent type `T` | ||
/// containing all the provided validations. | ||
public func dynamicallyCall( | ||
withArguments args: [Validator<U>] | ||
) -> Validator<T> { | ||
for arg in args { parent.addValidator(at: keyPath, arg) } | ||
return parent.validator | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
/// Stores property validations of data type `T` specialized with. | ||
/// | ||
/// Use the `@dynamicMemberLookup` | ||
/// feature to add validations based on property. | ||
@dynamicMemberLookup | ||
public class Validations<T> { | ||
/// `ValidatorResult` of a validator that validates | ||
/// all the properties and groups them with their `KeyPath`. | ||
public struct Property: ValidatorResult { | ||
/// The result of property validations associated with property `KeyPath`. | ||
public internal(set) var results: [PartialKeyPath<T>: ValidatorResult] = | ||
[:] | ||
|
||
public var isFailure: Bool { | ||
return self.results.first(where: \.value.isFailure) != nil | ||
} | ||
|
||
public var successDescriptions: [String] { | ||
var desc: [String] = [] | ||
for (keyPath, result) in self.results where !result.isFailure { | ||
desc.append("→ \(keyPath)") | ||
desc += result.successDescriptions.indented() | ||
} | ||
return desc | ||
} | ||
|
||
public var failureDescriptions: [String] { | ||
var desc: [String] = [] | ||
for (keyPath, result) in self.results where result.isFailure { | ||
desc.append("→ \(keyPath)") | ||
desc += result.failureDescriptions.indented() | ||
} | ||
return desc | ||
} | ||
} | ||
|
||
/// Stores all property based validations associated with the property `KeyPath`. | ||
private var storage: [PartialKeyPath<T>: [(T) -> ValidatorResult]] = [:] | ||
|
||
/// The parent type `T` data validator | ||
/// with all the property based validations. | ||
internal var validator: Validator<T> { | ||
return .init { self.validate($0) } | ||
} | ||
|
||
/// Stores provided property validator and `KeyPath`. | ||
/// | ||
/// - Parameters: | ||
/// - keyPath: The `KeyPath` for the property. | ||
/// - validator: The validator to add. | ||
@usableFromInline | ||
internal func addValidator<U>( | ||
at keyPath: KeyPath<T, U>, | ||
_ validator: Validator<U> | ||
) { | ||
let validation: (T) -> ValidatorResult = { | ||
let data = $0[keyPath: keyPath] | ||
return validator.validate(data) | ||
} | ||
|
||
if storage[keyPath] == nil { | ||
storage[keyPath] = [validation] | ||
} else { | ||
storage[keyPath]!.append(validation) | ||
} | ||
} | ||
|
||
/// Stores the validator associated with provided property `KeyPath`. | ||
/// | ||
/// - Parameter keyPath: The `KeyPath` for the property. | ||
@inlinable | ||
internal func addValidator<U: Validatable>(at keyPath: KeyPath<T, U>) { | ||
addValidator(at: keyPath, .init { $0.validator.validate($0) }) | ||
} | ||
|
||
/// Validates properties of data of type `T` with stored validators | ||
/// and returns the result. | ||
/// | ||
/// - Parameter data: The data to validate. | ||
/// - Returns: The result of validation. | ||
internal func validate(_ data: T) -> ValidatorResult { | ||
guard !storage.isEmpty else { return ValidatorResults.Skipped() } | ||
var result = Property() | ||
for (keyPath, validations) in storage where !validations.isEmpty { | ||
let results = validations.map { $0(data) } | ||
result.results[keyPath] = ValidatorResults.Nested(results: results) | ||
} | ||
return result | ||
} | ||
|
||
/// Exposes property of the specialized data type `T` to add validation on. | ||
/// | ||
/// Provide validator(s) to the returned ``Validation`` | ||
/// to validate that property with the validators. | ||
/// | ||
/// - Parameter keyPath: The `KeyPath` for the property. | ||
/// - Returns: ``Validation`` for the property. | ||
public subscript<U>( | ||
dynamicMember keyPath: KeyPath<T, U> | ||
) -> Validation<T, U> { | ||
let validation = Validation<T, U>(keyPath: keyPath, parent: self) | ||
return validation | ||
} | ||
|
||
/// Exposes property of the specialized data type `T` to add validation on. | ||
/// | ||
/// Adds the provided property ``Validatable/validator`` | ||
/// along with any provided validator(s) to the returned | ||
/// ``Validation`` to validate that property. | ||
/// | ||
/// - Parameter keyPath: The `KeyPath` for the property. | ||
/// - Returns: ``Validation`` for the property. | ||
public subscript<U: Validatable>( | ||
dynamicMember keyPath: KeyPath<T, U> | ||
) -> Validation<T, U> { | ||
addValidator(at: keyPath) | ||
let validation = Validation<T, U>(keyPath: keyPath, parent: self) | ||
return validation | ||
} | ||
} | ||
|
||
internal extension Array where Element == String { | ||
/// Indents provided array of `String`. | ||
/// | ||
/// - Returns: The indented `String`s. | ||
func indented() -> [String] { | ||
return self.map { " " + $0 } | ||
} | ||
} |
Oops, something went wrong.