Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add validate(query:) method to Validatable protocol #2419

Merged
merged 6 commits into from Jul 16, 2020
6 changes: 6 additions & 0 deletions Sources/Vapor/Deprecations/Validatable+validate.swift
@@ -0,0 +1,6 @@
extension Validatable {
@available(*, deprecated, renamed: "validate(content:)")
public static func validate(_ request: Request) throws {
try self.validations().validate(request: request).assert()
}
}
12 changes: 10 additions & 2 deletions Sources/Vapor/Validation/Validatable.swift
Expand Up @@ -13,14 +13,22 @@ public protocol Validatable {
}

extension Validatable {
public static func validate(_ request: Request) throws {
try self.validations().validate(request).assert()
public static func validate(content request: Request) throws {
try self.validations().validate(request: request).assert()
}

public static func validate(query request: Request) throws {
try self.validations().validate(query: request.url).assert()
}

public static func validate(json: String) throws {
try self.validations().validate(json: json).assert()
}

public static func validate(query: URI) throws {
try self.validations().validate(query: query).assert()
}

public static func validate(_ decoder: Decoder) throws {
try self.validations().validate(decoder).assert()
}
Expand Down
8 changes: 7 additions & 1 deletion Sources/Vapor/Validation/Validations.swift
Expand Up @@ -34,7 +34,7 @@ public struct Validations {
self.storage.append(validation)
}

public func validate(_ request: Request) throws -> ValidationsResult {
public func validate(request: Request) throws -> ValidationsResult {
guard let contentType = request.headers.contentType else {
throw Abort(.unprocessableEntity)
}
Expand All @@ -46,6 +46,12 @@ public struct Validations {
return try self.validate(decoder.decoder)
}

public func validate(query: URI) throws -> ValidationsResult {
let urlDecoder = try ContentConfiguration.global.requireURLDecoder()
let decoder = try urlDecoder.decode(DecoderUnwrapper.self, from: query)
return try self.validate(decoder.decoder)
}

public func validate(json: String) throws -> ValidationsResult {
let decoder = try JSONDecoder().decode(DecoderUnwrapper.self, from: Data(json.utf8))
return try self.validate(decoder.decoder)
Expand Down
2 changes: 1 addition & 1 deletion Tests/VaporTests/RouteTests.swift
Expand Up @@ -180,7 +180,7 @@ final class RouteTests: XCTestCase {
defer { app.shutdown() }

app.post("users") { req -> User in
try User.validate(req)
try User.validate(content: req)
return try req.content.decode(User.self)
}

Expand Down
42 changes: 40 additions & 2 deletions Tests/VaporTests/ValidationTests.swift
Expand Up @@ -20,7 +20,9 @@ class ValidationTests: XCTestCase {
"isAdmin": true
}
"""
let validUrl: URI = "https://tanner.xyz/user?name=Tanner&age=24&gender=male&email=me@tanner.xyz&luckyNumber=5&profilePictureURL=https://foo.jpg&preferredColors=[blue]&pet[name]=Zizek&pet[age]=3&isAdmin=true"
XCTAssertNoThrow(try User.validate(json: valid))
XCTAssertNoThrow(try User.validate(query: validUrl))
let invalidUser = """
{
"name": "Tan!ner",
Expand All @@ -37,9 +39,15 @@ class ValidationTests: XCTestCase {
"isAdmin": true
}
"""
let invalidUserUrl: URI = "https://tanner.xyz/user?name=Tan!ner&age=24&gender=other&email=me@tanner.xyz&luckyNumber=5&profilePictureURL=https://foo.jpg&preferredColors=[blue]&pet[name]=Zizek&pet[age]=3&isAdmin=true"
XCTAssertThrowsError(try User.validate(json: invalidUser)) { error in
XCTAssertEqual("\(error)",
"name contains '!' (allowed: A-Z, a-z, 0-9)")

}
XCTAssertThrowsError(try User.validate(query: invalidUserUrl)) { error in
XCTAssertEqual("\(error)",
"name contains '!' (allowed: A-Z, a-z, 0-9)")
}
let invalidPet = """
{
Expand All @@ -57,10 +65,15 @@ class ValidationTests: XCTestCase {
"isAdmin": true
}
"""
let invalidPetURL: URI = "https://tanner.xyz/user?name=Tanner&age=24&gender=male&email=me@tanner.xyz&luckyNumber=5&profilePictureURL=https://foo.jpg&preferredColors=[blue]&pet[name]=Zi!ek&pet[age]=3&isAdmin=true"
XCTAssertThrowsError(try User.validate(json: invalidPet)) { error in
XCTAssertEqual("\(error)",
"pet name contains '!' (allowed: whitespace, A-Z, a-z, 0-9)")
}
XCTAssertThrowsError(try User.validate(query: invalidPetURL)) { error in
XCTAssertEqual("\(error)",
"pet name contains '!' (allowed: whitespace, A-Z, a-z, 0-9)")
}
let invalidBool = """
{
"name": "Tanner",
Expand All @@ -77,11 +90,15 @@ class ValidationTests: XCTestCase {
"isAdmin": "true"
}
"""
let invalidPetBool: URI = "https://tanner.xyz/user?name=Tanner&age=24&gender=male&email=me@tanner.xyz&luckyNumber=5&profilePictureURL=https://foo.jpg&preferredColors=[blue]&pet[name]=Zizek&pet[age]=3&isAdmin='true'"
XCTAssertThrowsError(try User.validate(json: invalidBool)) { error in
XCTAssertEqual("\(error)",
"isAdmin is not a(n) Bool")
}

XCTAssertThrowsError(try User.validate(query: invalidPetBool)) { error in
XCTAssertEqual("\(error)",
"isAdmin is not a(n) Bool")
}
let validOptionalFavoritePet = """
{
"name": "Tanner",
Expand All @@ -102,8 +119,9 @@ class ValidationTests: XCTestCase {
"isAdmin": true
}
"""
let validOptionalFavoritePetUrl: URI = "https://tanner.xyz/user?name=Tanner&age=24&gender=male&email=me@tanner.xyz&luckyNumber=5&profilePictureURL=https://foo.jpg&preferredColors=[blue]&pet[name]=Zizek&pet[age]=3&favoritePet[name]=Zizek&favoritePet[age]=3&&isAdmin=true"
XCTAssertNoThrow(try User.validate(json: validOptionalFavoritePet))

XCTAssertNoThrow(try User.validate(query: validOptionalFavoritePetUrl))
let invalidOptionalFavoritePet = """
{
"name": "Tanner",
Expand All @@ -124,10 +142,15 @@ class ValidationTests: XCTestCase {
"isAdmin": true
}
"""
let invalidOptionalFavoritePetUrl: URI = "https://tanner.xyz/model?name=Tanner&age=24&gender=male&email=me@tanner.xyz&luckyNumber=5&profilePictureURL=https://foo.jpg&preferredColors=[blue]&pet[name]=Zizek&pet[age]=3&favoritePet[name]=Zi!ek&favoritePet[age]=3&&isAdmin=true"
XCTAssertThrowsError(try User.validate(json: invalidOptionalFavoritePet)) { error in
XCTAssertEqual("\(error)",
"favoritePet name contains '!' (allowed: whitespace, A-Z, a-z, 0-9)")
}
XCTAssertThrowsError(try User.validate(query: invalidOptionalFavoritePetUrl)) { error in
XCTAssertEqual("\(error)",
"favoritePet name contains '!' (allowed: whitespace, A-Z, a-z, 0-9)")
}
}

func testCatchError() throws {
Expand All @@ -147,6 +170,7 @@ class ValidationTests: XCTestCase {
"isAdmin": true
}
"""
let invalidUserUrl: URI = "https://tanner.xyz/user?name=Tan!ner&age=24&gender=other&email=me@tanner.xyz&luckyNumber=5&profilePictureURL=https://foo.jpg&preferredColors=[blue]&pet[name]=Zizek&pet[age]=3&isAdmin=true"
do {
try User.validate(json: invalidUser)
} catch let error as ValidationsError {
Expand All @@ -161,6 +185,20 @@ class ValidationTests: XCTestCase {
let character = and.right as! ValidatorResults.CharacterSet
XCTAssertEqual(character.invalidSlice, "!")
}
do {
try User.validate(query: invalidUserUrl)
} catch let error as ValidationsError {
XCTAssertEqual(error.failures.count, 1)
let name = error.failures[0]
XCTAssertEqual(name.key.stringValue, "name")
XCTAssertEqual(name.result.isFailure, true)
XCTAssertEqual(name.result.failureDescription, "contains '!' (allowed: A-Z, a-z, 0-9)")
let and = name.result as! ValidatorResults.And
let count = and.left as! ValidatorResults.Range<Int>
XCTAssertEqual(count.result, .greaterThanOrEqualToMin(5))
let character = and.right as! ValidatorResults.CharacterSet
XCTAssertEqual(character.invalidSlice, "!")
}
}

func testNotReadability() {
Expand Down