Repository: vapor/vapor
File: Tests/VaporTests/ValidationTests.swift (Swift)

Code before:
        // validate name is at least 5 characters and alphanumeric
        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
        // validate the email is valid or is nil
        v.add("email", as: String?.self, is: .nil || .email)
        v.add("email", as: String?.self, is: .email || .nil) // test other way
        // validate that the lucky number is nil or is 5 or 7
        v.add("luckyNumber", as: Int?.self, is: .nil || .in(5, 7))
        // validate that the profile picture is nil or a valid URL
        v.add("profilePictureURL", as: String?.self, is: .url || .nil)
        v.add("preferredColors", as: [String].self, is: !.empty)
        // pet validations
        v.add("pet") { pet in
            pet.add("name", as: String.self,
                    is: .count(5...) && .characterSet(.alphanumerics + .whitespaces))
            pet.add("age", as: Int.self, is: .range(3...))
        }
        v.add("isAdmin", as: Bool.self)
        // validate arrays of hobbies
        v.add("hobbies", as: [Hobby].self, is: !.empty)
        v.addForEach("hobbies") { hobby in
            hobby.add("title", as: String.self,
                    is: .count(5...) && .characterSet(.alphanumerics + .whitespaces))
        }
    }
}


Review comment (bug):
This is going to decode `[Hobby]` which will fail with a Codable error if any of the data is malformed. I thinking putting this _after_ the `v.add(forEach:)` will resolve that issue, but we should have a test for it. 

Code after:
        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")
    }

    func testCustomResponseMiddleware() throws {
        let app = Application(.testing)
        defer { app.shutdown() }

        // Converts validation errors to a custom response.
        final class ValidationErrorMiddleware: Middleware {
            // Defines the format of the custom error response.
            struct ErrorResponse: Content {
                var errors: [String]
            }

            func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
                next.respond(to: request).flatMapErrorThrowing { error in
                    // Check to see if this is a validation error. 
                    if let validationError = error as? ValidationsError {
                        // Convert each failed ValidatorResults to a String
                        // for the sake of this example.
                        let errorMessages = validationError.failures.map { failure -> String in 
                            let reason: String
                            // The failure result will be one of the ValidatorResults subtypes.
                            //
                            // Each validator extends ValidatorResults with a nested type.
                            // For example, the .email validator's result type is:
                            //
                            //      struct ValidatorResults.Email {
                            //          let isValidEmail: Bool
                            //      }
                            //
                            // You can handle as many or as few of these types as you want.
                            // Vapor and third party packages may add additional types.
                            // This switch is only handling two cases as an example.
                            //
                            // If you want to localize your validation failures, this is a
                            // good place to do it.
                            switch failure.result {
                            case is ValidatorResults.Missing:
                                reason = "is required"
                            case let error as ValidatorResults.TypeMismatch:
                                reason = "is not \(error.type)"
                            default:
                                reason = "unknown"