Skip to content

Instantly share code, notes, and snippets.

@joshavant
Created October 12, 2016 17:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joshavant/3b848171624560b2545011afc10924aa to your computer and use it in GitHub Desktop.
Save joshavant/3b848171624560b2545011afc10924aa to your computer and use it in GitHub Desktop.
import Foundation
enum Result<T,U> {
case firstType(T)
case secondType(U)
}
enum Validation<T,Error> {
case valid(T)
case invalid(Error)
init(input: T, comparison: ((T) -> (Result<T,Error>))) {
switch comparison(input) {
case .firstType(let value):
self = .valid(value)
case .secondType(let error):
self = .invalid(error)
}
}
}
//////////
// Data Structure:
struct Birthdate {
enum Component {
case month
case day
case year
func debugDescription() -> String {
switch self {
case .month: return "Month"
case .day: return "Day"
case .year: return "Year"
}
}
}
let month: Int?
let day: Int?
let year: Int?
enum DateGenerationError: Error {
case missingComponents(Set<Component>)
case couldntGenerateDateFromComponents
}
func generateDate() throws -> Date {
guard
let aMonth = self.month,
let aDay = self.day,
let aYear = self.year
else {
var components = Set<Component>()
if self.month == nil { components.insert(.month) }
if self.day == nil { components.insert(.day) }
if self.year == nil { components.insert(.year) }
throw DateGenerationError.missingComponents(components)
}
var calendar = Calendar(identifier: .gregorian)
calendar.locale = Locale.autoupdatingCurrent
guard let generatedDate = DateComponents(calendar: calendar,
timeZone: nil,
era: nil,
year: aYear,
month: aMonth,
day: aDay,
hour: nil,
minute: nil,
second: nil,
nanosecond: nil,
weekday: nil,
weekdayOrdinal: nil,
quarter: nil,
weekOfMonth: nil,
weekOfYear: nil,
yearForWeekOfYear: nil).date
else { throw DateGenerationError.couldntGenerateDateFromComponents }
return generatedDate
}
func under18Threshold() -> Date {
return Date(timeIntervalSinceNow: -1 * (60 * 60 * 24 * 365 * 18))
}
func under21Threshold() -> Date {
return Date(timeIntervalSinceNow: -1 * (60 * 60 * 24 * 365 * 21))
}
}
//////////
// Validation Pieces:
enum BirthdateValidationError {
struct AgeLevels: OptionSet {
let rawValue: Int
static let under18 = AgeLevels(rawValue: 1 << 0)
static let under21 = AgeLevels(rawValue: 1 << 1)
}
case missingComponents(Set<Birthdate.Component>)
case componentsDontFormValidDate
case underage(AgeLevels)
}
let validator: ((Birthdate) -> (Result<Birthdate,BirthdateValidationError>)) = {
birthdate in
do {
let date = try birthdate.generateDate()
let under18Comparison = date.compare(birthdate.under18Threshold())
let under21Comparison = date.compare(birthdate.under21Threshold())
switch (under18Comparison, under21Comparison) {
case (.orderedDescending, _):
return .secondType(.underage(.under18))
case (.orderedSame, _),
(.orderedAscending, .orderedDescending),
(.orderedAscending, .orderedSame):
return .secondType(.underage(.under21))
case (.orderedAscending, .orderedAscending):
return .firstType(birthdate)
}
} catch Birthdate.DateGenerationError.missingComponents(let components) {
return .secondType(.missingComponents(components))
} catch {
return .secondType(.componentsDontFormValidDate)
}
}
// Prints debug string for result
func parseValidationResult(_ result: Validation<Birthdate,BirthdateValidationError>) {
switch result {
case .valid(let birthdate):
let date = try? birthdate.generateDate()
print("Valid: \(date)")
case .invalid(let error):
switch error {
case .missingComponents(let components):
let componentsList = components.map({ $0.debugDescription() }).joined(separator: " ")
print("Missing Components: \(componentsList)")
case .componentsDontFormValidDate:
print("Components don't form valid date")
case .underage(let ageLevels):
var message = "Underage:"
if ageLevels.contains(.under21) {
message += " Under 21"
} else if ageLevels.contains(.under18) {
message += " Under 18"
}
print(message)
}
}
}
let missingMonthBirthdate = Birthdate(month: nil, day: 1, year: nil)
let missingMonthValidationResult = Validation<Birthdate,BirthdateValidationError>(input: missingMonthBirthdate, comparison: validator)
parseValidationResult(missingMonthValidationResult) // Missing Components: Year Month
// So, generating an invalid date from DateComponents(calendar:...) is actually pretty hard and I couldn't figure out a way to do it. :)
let under18Birthdate = Birthdate(month: 1, day: 2, year: 1997)
let under18ValidationResult = Validation<Birthdate,BirthdateValidationError>(input: under18Birthdate, comparison: validator)
parseValidationResult(under18ValidationResult) // Underage: Under 21
let under21Birthdate = Birthdate(month: 10, day: 16, year: 2016)
let under21ValidationResult = Validation<Birthdate,BirthdateValidationError>(input: under21Birthdate, comparison: validator)
parseValidationResult(under21ValidationResult) // Underage: Under 18
let validBirthdate = Birthdate(month: 1, day: 2, year: 1900)
let validValidationResult = Validation<Birthdate,BirthdateValidationError>(input: validBirthdate, comparison: validator)
parseValidationResult(validValidationResult) // Valid: Optional(1900-01-02 08:00:00 +0000)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment