Skip to content

Commit

Permalink
Adds case(of:) Validation (#2172)
Browse files Browse the repository at this point in the history
* Added CaseOf Validation with tests

Signed-off-by: Mohammad <porooshani@gmail.com>

* Changed caseOf to case and improved error messages and updated tests.

Signed-off-by: Mohammad <porooshani@gmail.com>

* Removed non-CaseIterable support from case(of:) validation.

Signed-off-by: Mohammad <porooshani@gmail.com>

* Removed unwanted map in error generator

Signed-off-by: Mohammad <porooshani@gmail.com>

* Improved error Generation.

Signed-off-by: Mohammad <porooshani@gmail.com>

* Add myself as a maintainer (#2188)

* Adds EndpointCache for handling GET resource caching (#2184)

* Adds a generic EndpointCache<T> class

* Review fixups

* Changed internal to private

* Changed internal to private

* Renamed the error

* add EndpointCache tests

* add CacheControl convienience init

* rename badJSON error case

Co-authored-by: Tanner <me@tanner.xyz>

* expose methods for hex encoded strings (#2189)

* Removed non-CaseIterable support from case(of:) validation.

Signed-off-by: Mohammad <porooshani@gmail.com>

* Removed unwanted map in error generator

Signed-off-by: Mohammad <porooshani@gmail.com>

* Improved error Generation.

Signed-off-by: Mohammad <porooshani@gmail.com>

* Improved description message for case(of:)

Signed-off-by: Mohammad <porooshani@gmail.com>

Co-authored-by: Joannis Orlandos <joannis@orlandos.nl>
Co-authored-by: grosch <scott.grosch@icloud.com>
Co-authored-by: Tanner <me@tanner.xyz>
  • Loading branch information
4 people committed Feb 19, 2020
1 parent 7bd5d33 commit 71cd6a7
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 1 deletion.
52 changes: 52 additions & 0 deletions Sources/Vapor/Validation/Validators/Case.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
extension Validator {
/// Validates that the data can be converted to a value of an enum type with iterable cases.
public static func `case`<E>(of enum: E.Type) -> Validator<T>
where E: RawRepresentable & CaseIterable, E.RawValue == T, T: CustomStringConvertible
{
.init {
ValidatorResults.Case(enumType: E.self, rawValue: $0)
}
}

}

extension ValidatorResults {
/// `ValidatorResult` of a validator thaat validates whether the data can be represented as a specific Enum case.
public struct Case<T, E> where E: RawRepresentable & CaseIterable, E.RawValue == T, T: CustomStringConvertible {
/// The type of the enum to check.
let enumType: E.Type
/// The raw value that would be tested agains the enum type.
let rawValue: T
}
}

extension ValidatorResults.Case: ValidatorResult {
public var isFailure: Bool {
return enumType.init(rawValue: rawValue) == nil
}

public var successDescription: String? {
makeDescription(not: false)
}

public var failureDescription: String? {
makeDescription(not: true)
}

func makeDescription(not: Bool) -> String {
let items = E.allCases.map { "\($0.rawValue)" }
let description: String
switch items.count {
case 1:
description = items[0].description
case 2:
description = "\(items[0].description) or \(items[1].description)"
default:
let first = items[0..<(items.count - 1)]
.map { $0.description }.joined(separator: ", ")
let last = items[items.count - 1].description
description = "\(first), or \(last)"
}
return "is\(not ? " not" : "") \(description)"
}
}
42 changes: 41 additions & 1 deletion Tests/VaporTests/ValidationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class ValidationTests: XCTestCase {
{
"name": "Tanner",
"age": 24,
"gender": "male",
"email": "me@tanner.xyz",
"luckyNumber": 5,
"profilePictureURL": "https://foo.jpg",
Expand All @@ -23,6 +24,7 @@ class ValidationTests: XCTestCase {
{
"name": "Tan!ner",
"age": 24,
"gender": "other",
"email": "me@tanner.xyz",
"luckyNumber": 5,
"profilePictureURL": "https://foo.jpg",
Expand All @@ -42,6 +44,7 @@ class ValidationTests: XCTestCase {
{
"name": "Tanner",
"age": 24,
"gender": "male",
"email": "me@tanner.xyz",
"luckyNumber": 5,
"profilePictureURL": "https://foo.jpg",
Expand All @@ -61,6 +64,7 @@ class ValidationTests: XCTestCase {
{
"name": "Tanner",
"age": 24,
"gender": "male",
"email": "me@tanner.xyz",
"luckyNumber": 5,
"profilePictureURL": "https://foo.jpg",
Expand All @@ -83,6 +87,7 @@ class ValidationTests: XCTestCase {
{
"name": "Tan!ner",
"age": 24,
"gender": "male",
"email": "me@tanner.xyz",
"luckyNumber": 5,
"profilePictureURL": "https://foo.jpg",
Expand Down Expand Up @@ -238,6 +243,33 @@ class ValidationTests: XCTestCase {
let error = try validations.validate(json: #"{"key": ""}"#).error
XCTAssertEqual(error?.description, "key is empty")
}

func testCaseOf() {

enum StringEnumType: String, CaseIterable {
case case1, case2, case3 = "CASE3"
}
assert("case1", passes: .case(of: StringEnumType.self))
assert("case2", passes: .case(of: StringEnumType.self))
assert("case1", fails: !.case(of: StringEnumType.self), "is case1, case2, or CASE3")
assert("case3", fails: .case(of: StringEnumType.self), "is not case1, case2, or CASE3")

enum IntEnumType: Int, CaseIterable {
case case1 = 1, case2 = 2
}
assert(1, passes: .case(of: IntEnumType.self))
assert(2, passes: .case(of: IntEnumType.self))
assert(1, fails: !.case(of: IntEnumType.self), "is 1 or 2")
assert(3, fails: .case(of: IntEnumType.self), "is not 1 or 2")

enum SingleCaseEnum: String, CaseIterable {
case case1 = "CASE1"
}
assert("CASE1", passes: .case(of: SingleCaseEnum.self))
assert("CASE1", fails: !.case(of: SingleCaseEnum.self), "is CASE1")
assert("CASE2", fails: .case(of: SingleCaseEnum.self), "is not CASE1")

}
}

private func assert<T>(
Expand All @@ -263,9 +295,14 @@ private func assert<T>(
}

private final class User: Validatable, Codable {
enum Gender: String, CaseIterable, Codable {
case male, female, other
}

var id: Int?
var name: String
var age: Int
var gender: Gender
var email: String?
var pet: Pet
var luckyNumber: Int?
Expand All @@ -282,10 +319,11 @@ private final class User: Validatable, Codable {
}
}

init(id: Int? = nil, name: String, age: Int, pet: Pet, preferredColors: [String] = [], isAdmin: Bool) {
init(id: Int? = nil, name: String, age: Int, gender: Gender, pet: Pet, preferredColors: [String] = [], isAdmin: Bool) {
self.id = id
self.name = name
self.age = age
self.gender = gender
self.pet = pet
self.preferredColors = preferredColors
self.isAdmin = isAdmin
Expand All @@ -296,6 +334,8 @@ private final class User: Validatable, Codable {
v.add("name", as: String.self, is: .count(5...) && .alphanumeric)
// validate age is 18 or older
v.add("age", as: Int.self, is: .range(18...))
// validate gender is of type Gender
v.add("gender", as: String.self, is: .case(of: Gender.self))
// validate the email is valid and is not nil
v.add("email", as: String?.self, is: !.nil && .email)
v.add("email", as: String?.self, is: .email && !.nil) // test other way
Expand Down

0 comments on commit 71cd6a7

Please sign in to comment.