Skip to content

Commit

Permalink
Merge pull request #8 from Skyback/master
Browse files Browse the repository at this point in the history
UniqueFieldValidator and RegexValidator
  • Loading branch information
Toby Griffin authored Feb 5, 2017
2 parents 2285104 + a53d4b2 commit df3ee3d
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 1 deletion.
37 changes: 37 additions & 0 deletions Sources/VaporForms/Validators/DatabaseValidators.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Vapor
import Fluent

/**
Validates if the value exists on the database
*/
public class UniqueFieldValidator<ModelType: Entity>: FieldValidator<String> {
let column: String
let additionalFilters: [(column:String, comparison:Filter.Comparison, value:String)]?
let message: String?
public init(column: String, additionalFilters: [(column:String, comparison:Filter.Comparison, value:String)]?=nil, message: String?=nil) {
self.column = column
self.additionalFilters = additionalFilters
self.message = message
}
public override func validate(input value: String) -> FieldValidationResult {
// Let's create the main filter
do {
let query = try ModelType.query()
try query.filter(self.column, value)
// If we have addition filters, add them
if let filters = self.additionalFilters {
for filter in filters {
try query.filter(filter.column, filter.comparison, filter.value)
}
}
// Check if any record exists
if(try query.count() > 0){
return .failure([.validationFailed(message: message ?? "Value \(self.column) must be unique.")])
}
// If not we have green light
return .success(Node(value))
} catch {
return .failure([.validationFailed(message: message ?? "Value \(self.column) must be unique.")])
}
}
}
19 changes: 19 additions & 0 deletions Sources/VaporForms/Validators/String+Validators.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,23 @@ extension String {
}
}

/**
Validates a string against the given regex
*/
public class RegexValidator: FieldValidator<String> {
let regex: String?
let message: String?
public init(regex: String?=nil, message: String?=nil) {
self.regex = regex
self.message = message
}
override public func validate(input value: String) -> FieldValidationResult {
if let regex = self.regex {
if let _ = value.range(of: regex, options: .regularExpression) {
return .success(Node(value))
}
}
return .failure([.validationFailed(message: message ?? "Value did not match required format.")])
}
}
}
69 changes: 68 additions & 1 deletion Tests/VaporFormsTests/VaporFormsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import XCTest
@testable import VaporForms
@testable import Vapor
import Leaf
import Fluent

/**
Layout of the vapor-forms library
Expand Down Expand Up @@ -45,6 +46,7 @@ class VaporFormsTests: XCTestCase {
("testFieldUnsignedIntegerValidation", testFieldUnsignedIntegerValidation),
("testFieldDoubleValidation", testFieldDoubleValidation),
("testFieldBoolValidation", testFieldBoolValidation),
("testUniqueFieldValidation", testUniqueFieldValidation),
// Fieldset
("testSimpleFieldset", testSimpleFieldset),
("testSimpleFieldsetGetInvalidData", testSimpleFieldsetGetInvalidData),
Expand All @@ -68,6 +70,10 @@ class VaporFormsTests: XCTestCase {
("testSampleLoginFormWithMultipart", testSampleLoginFormWithMultipart),
]
}

override func setUp(){
Database.default = Database(TestDriver())
}

func expectMatch(_ test: FieldValidationResult, _ match: Node, fail: () -> Void) {
switch test {
Expand Down Expand Up @@ -144,6 +150,10 @@ class VaporFormsTests: XCTestCase {
expectFailure(StringField(String.MaximumLengthValidator(characters: 6)).validate("maxi string")) { XCTFail() }
// Value not exact size should fail
expectFailure(StringField(String.ExactLengthValidator(characters: 6)).validate("wrong size")) { XCTFail() }
// Value in regex should succeed
expectSuccess(StringField(String.RegexValidator(regex: "^[a-z]{6}$")).validate("string")) { XCTFail() }
// Value in regex should failure
expectFailure(StringField(String.RegexValidator(regex: "^[a-z]{6}$")).validate("string 1s wr0ng")) { XCTFail() }
}

func testFieldEmailValidation() {
Expand Down Expand Up @@ -234,7 +244,14 @@ class VaporFormsTests: XCTestCase {
expectMatch(BoolField().validate("f"), Node(false)) { XCTFail() }
expectMatch(BoolField().validate(0), Node(false)) { XCTFail() }
}


func testUniqueFieldValidation() {
// Expect success because this count should return 0
expectSuccess(StringField(UniqueFieldValidator<TestUser>(column: "name")).validate("filter_applied")) { XCTFail() }
// Expect failure because this count should return 1
expectFailure(StringField(UniqueFieldValidator<TestUser>(column: "name")).validate("not_unique")) { XCTFail() }
}

// MARK: Fieldset

func testSimpleFieldset() {
Expand Down Expand Up @@ -807,3 +824,53 @@ class VaporFormsTests: XCTestCase {
} catch { XCTFail() }
}
}

// MARK: Mocks

// Mock Driver to test DB validators
class TestDriver: Driver {
var idKey: String = "id"
func query<T : Entity>(_ query: Query<T>) throws -> Node {
switch query.action {
case .count:
// If we have this specific filter consider it's not unique
guard query.filters.contains(where: {
guard case .compare(let key, let comparison, let value) = $0.method else {
return false
}
return (key == "name" && comparison == .equals && value == Node("not_unique"))
}) else {
return 0
}
return 1
default:
return 0
}
}
func schema(_ schema: Schema) throws {}
@discardableResult
public func raw(_ query: String, _ values: [Node] = []) throws -> Node {
return .null
}
}

// Mock Entity to test DB validators
struct TestUser: Entity {
var id: Node?
var name: String
init(name: String) {
self.name = name
}
init(node: Node, in context: Vapor.Context) throws {
id = try node.extract("id")
name = try node.extract("name")
}
func makeNode(context: Vapor.Context) throws -> Node {
return try Node(node: [
"id": id,
"name": name
])
}
static func prepare(_ database: Database) throws {}
static func revert(_ database: Database) throws {}
}

0 comments on commit df3ee3d

Please sign in to comment.