Created
April 27, 2019 14:38
-
-
Save gonzula/3bdfb73957d4aae776cb656ee738c897 to your computer and use it in GitHub Desktop.
Examples for validated string
This file contains hidden or 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
// Use cases of ValidatedString (https://gist.github.com/gonzula/dbc7194fd9afde7b7b59fb772a543e91) | |
// With very little code you create very safe data types with a lot of cool features | |
// Let's create a username validator | |
typealias Username = ValidatedString<UsernameValidator> | |
enum UsernameValidator: StringValidator { | |
/// Accepts any username with length between 3 and 10 inclusive | |
static func validate(_ string: String) -> String? { | |
let username = string.trimmingCharacters(in: .whitespacesAndNewlines) | |
guard (3...10).contains(username.count) else {return nil} | |
return username | |
} | |
} | |
// and a SSN validator | |
typealias SSN = ValidatedString<SSNValidator> | |
enum SSNValidator: StringValidator { | |
/// Accepts ssn with or without the dash, but it always stores without the dashes | |
static func validate(_ string: String) -> String? { | |
let string = string.trimmingCharacters(in: .whitespacesAndNewlines) | |
let regex = #"\d{3}-?\d{2}-?\d{4}"# | |
guard NSPredicate(format: "SELF MATCHES %@", regex).evaluate(with: string) else {return nil} | |
return string.replacingOccurrences(of: "-", with: "") | |
} | |
} | |
// Now the features you get for free | |
// Convertible from string literals | |
let usernames: [Username] = [ | |
"Gonzula", | |
"jgcmarins" | |
] | |
let ssn: SSN = "078-05-1120" | |
// or from expressions, resulting in a optional | |
let userInput = "Gonzula" | |
let optionalUsername1 = Username(userInput) // Optional("Gonzula") | |
let invalidUserInput = "InvalidUserInputBucauseItsVeryBig" | |
let optionalUsername2 = Username(invalidUserInput) // nil | |
// and LosslessStringConvertible/CustomStringConvertible | |
let username: Username = "Gonzula" | |
let convertedToString = String(username) // Gonzula | |
print("The username is \(username)") // The username is Gonzula | |
// Codable support | |
struct User: Codable { | |
let username: Username | |
let ssn: SSN | |
} | |
let json = """ | |
{ | |
"username": "Gonzula", | |
"ssn": "078051120" | |
} | |
""".data(using: .utf8)! | |
let decoder = JSONDecoder() | |
let decodedUser = try! decoder.decode(User.self, from: json) | |
let encoder = JSONEncoder() | |
let encodedJson = try! encoder.encode(decodedUser) | |
String(data: encodedJson, encoding: .utf8)! // {"ssn":"078051120","username":"Gonzula"} | |
// Custom hashables and comparisons, useful for case insensitive comparison, but still preserving input's original case | |
extension UsernameValidator: StringNormalizer, StringComparator{ | |
static func normalize(_ rawValue: String) -> String { | |
return rawValue.lowercased() | |
} | |
static func areInIncreasingOrder(_ lhs: String, _ rhs: String) -> Bool { | |
return normalize(lhs).localizedCaseInsensitiveCompare(normalize(rhs)) == .orderedAscending | |
} | |
} | |
let scores: [Username: Int] = ["Foo": 10, "Bar": 5] | |
scores["FOO"] // 10 | |
scores["bar"] // 5 | |
let players: [Username] = ["Foo", "bar"].sorted() // [bar, Foo] | |
// while a simple string sorting will result in a different order | |
let stringPlayers: [String] = ["Foo", "bar"].sorted() // [Foo, bar] | |
// Custom computed properties, useful for human printable formats | |
extension SSN { | |
var formatted: String { | |
let digits = Array(rawValue) | |
let groups = [digits[0..<3], digits[3..<5], digits[5..<9]] | |
let formatted = groups.joined(separator: "-") | |
return String(formatted) | |
} | |
} | |
let ssnNumber = "078051120" | |
SSN(ssnNumber)?.formatted // 078-05-1120 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment