Skip to content

Instantly share code, notes, and snippets.

@lukesutton
Created April 25, 2017 04:56
Show Gist options
  • Save lukesutton/357615cb8e4b53ae25165c50d2958da1 to your computer and use it in GitHub Desktop.
Save lukesutton/357615cb8e4b53ae25165c50d2958da1 to your computer and use it in GitHub Desktop.

A small sketch for a composable validation library.

enum ValidationFailure {
case valueExpected
case outOfRange(ValidationValue, ValidationValue)
case belowMinimum(ValidationValue)
case aboveMaximum(ValidationValue)
case onlyAllowed(ValidationValue)
case invalidFormat(ValidationValue)
case other(String, ValidationValue)
}
protocol ValidationValue {
var validationSummary: String { get }
}
extension Int: ValidationValue {
var validationSummary: String {
return description
}
}
extension Float: ValidationValue {
var validationSummary: String {
return description
}
}
extension Dictionary: ValidationValue {
var validationSummary: String {
return description
}
}
extension String: ValidationValue {
var validationSummary: String {
return description
}
}
extension Set: ValidationValue {
var validationSummary: String {
return description
}
}
extension Array: ValidationValue {
var validationSummary: String {
return description
}
}
struct ValidationResult {
let failures: [ValidationFailure]
var hasFailed: Bool {
return !failures.isEmpty
}
var hasPassed: Bool {
return failures.isEmpty
}
}
typealias Validator<T> = (T?) -> [ValidationFailure]
struct Validate<A> {
private let validator: Validator<A>
init(_ validator: @escaping Validator<A>) {
self.validator = validator
}
func run(_ value: A?) -> ValidationResult {
return ValidationResult(failures: validator(value))
}
}
func required<A>(value: A?) -> [ValidationFailure] {
return value == nil ? [.valueExpected] : []
}
func containOnly<A: Collection>(_ values: Set<A.Iterator.Element>) -> (A?) -> [ValidationFailure] {
return { value in
guard let value = value else { return [] }
return Set(value) == values ? [] : [.onlyAllowed(values)]
}
}
func oneOf<A: Equatable>(_ values: Set<A>) -> (A?) -> [ValidationFailure] {
return { value in
guard let value = value else { return [] }
return values.contains(value) ? [] : [.onlyAllowed(values)]
}
}
func minCount<A>(_ count: A.IndexDistance) -> (A?) -> [ValidationFailure]
where A: Collection, A.IndexDistance: ValidationValue {
return { value in
guard let value = value else { return [] }
return value.count >= count ? [] : [.belowMinimum(count)]
}
}
func maxCount<A: Collection>(_ count: A.IndexDistance) -> (A?) -> [ValidationFailure]
where A: Collection, A.IndexDistance: ValidationValue {
return { value in
guard let value = value else { return [] }
return value.count <= count ? [] : [.aboveMaximum(1)]
}
}
func maxValue<A>(_ count: A) -> (A?) -> [ValidationFailure]
where A: Comparable, A: ValidationValue {
return { value in
guard let value = value else { return [] }
return value <= count ? [] : [.belowMinimum(count)]
}
}
func minValue<A>(_ count: A) -> (A?) -> [ValidationFailure]
where A: Comparable, A: ValidationValue {
return { value in
guard let value = value else { return [] }
return value >= count ? [] : [.aboveMaximum(count)]
}
}
func &&<A>(lhs: @escaping Validator<A>, rhs: @escaping Validator<A>) -> Validator<A> {
return { value in
return lhs(value) + rhs(value)
}
}
func ||<A>(lhs: @escaping Validator<A>, rhs: @escaping Validator<A>) -> Validator<A> {
return { value in
let x = lhs(value)
let y = rhs(value)
if x.isEmpty || y.isEmpty {
return []
}
else {
return x + y
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment