Validation API¶
Vapor 的 Validation API 可帮助你在使用 Content API 解码数据之前,对传入的请求进行验证。
介绍¶
Vapor 对 Swift 的类型安全的可编码
协议进行了深度集成,这意味着与动态类型的语言相比,你无需担心数据验证。但是,出于某些原因,你可能想选择使用 Validation API 进行显式验证。
语义可读错误¶
如果取得的数据无效,使用 Content API 对其解码将产生错误。但是,这些错误消息有时可能缺乏可读性。例如,采用以下字符串支持的枚举:
enum Color: String, Codable {
case red, blue, green
}
如果用户尝试将字符串 ”purple”
传递给 “Color”
类型的属性,则将收到类似于以下内容的错误:
Cannot initialize Color from invalid String value purple for key favoriteColor
尽管此错误在技术上是正确的,并且可以成功地保护端点免受无效值的影响,但它可以更好地通知用户该错误以及可用的选项。通过使用 Validation API,你可以生成类似以下的错误:
favoriteColor is not red, blue, or green
此外,一旦遇到第一个错误,Codable
将停止尝试解码。这意味着即使请求中有许多无效属性,用户也只会看到第一个错误。 Validation API 将在单个请求中抛出所有的验证失败信息。
特殊验证¶
Codable
可以很好地处理类型验证,但是有时候你还想要更多的验证方式。例如,验证字符串的内容或验证整数的大小。Validation API 具有此类验证器,可帮助验证电子邮件、字符集、整数范围等数据。
验证¶
为了验证请求,你需要生成一个 Validations
集合。最常见的做法是使现有类型继承 Validatable。
让我们看一下如何向这个简单的 POST/users
请求添加验证。本指南假定你已经熟悉 Content API。
enum Color: String, Codable {
case red, blue, green
}
struct CreateUser: Content {
var name: String
var username: String
var age: Int
var email: String
var favoriteColor: Color?
}
app.post("users") { req -> CreateUser in
let user = try req.content.decode(CreateUser.self)
// Do something with user.
return user
}
添加验证¶
第一步是在你要解码的类型(在本例中为 CreateUser
)遵循 Validatable
协议并实现 validations
静态方法,可在 extension
中完成。
extension CreateUser: Validatable {
static func validations(_ validations: inout Validations) {
// Validations go here.
}
}
验证 CreateUser
后,将调用静态方法 validations(_ :)
。你要执行的所有验证都应添加到 Validations 集合中。让我们添加一个简单的验证,以验证用户的电子邮件是否有效。
validations.add("email", as: String.self, is: .email)
第一个参数是参数值的预期键,在本例中为 email
。这应与正在验证的类型上的属性名称匹配。第二个参数 as
是预期的类型,在这种情况下为 String
。该类型通常与属性的类型匹配。最后,可以在第三个参数 is
之后添加一个或多个验证器。在这种情况下,我们添加一个验证器,以检查该值是否为电子邮件地址。
验证请求的 Content
¶
当你的数据类型继承了 Validatable
,就可以使用 validate(content:)
静态方法来验证请求的 content
。在路由处理程序中 req.content.decode(CreateUser.self)
之前添加以下行:
try CreateUser.validate(content: req)
现在,尝试发送以下包含无效电子邮件的请求:
POST /users HTTP/1.1
Content-Length: 67
Content-Type: application/json
{
"age": 4,
"email": "foo",
"favoriteColor": "green",
"name": "Foo",
"username": "foo"
}
你应该能看到返回以下错误:
email is not a valid email address
验证请求的 Query
¶
当你的数据类型继承了 Validatable
,就可以使用 validate(query:)
静态方法来验证请求的 query
。在路由处理程序中添加以下行:
try CreateUser.validate(query: req)
req.query.decode(CreateUser.self)
现在,尝试发送以下请求,该请求在查询字符串中包含无效的电子邮件。
GET /users?age=4&email=foo&favoriteColor=green&name=Foo&username=foo HTTP/1.1
你将会看到下面的错误:
email is not a valid email address
整数验证¶
现在让我们尝试添加一个针对整数年龄的验证:
validations.add("age", as: Int.self, is: .range(13...))
年龄验证要求年龄大于或等于13
。如果你尝试发送一个和上面相同的请求,现在应该会看到一个新错误:
age is less than minimum of 13, email is not a valid email address
字符串验证¶
接下来,让我们添加对 name
和 username
的验证。
validations.add("name", as: String.self, is: !.empty)
validations.add("username", as: String.self, is: .count(3...) && .alphanumeric)
名称验证使用 !
运算符将 .empty
验证反转。这要求该字符串不为空。
用户名验证使用&&
组合了两个验证器。这将要求该字符串的长度至少为3个字符,并且仅包含字母数字字符。
枚举验证¶
最后,让我们看一下更高级的验证,以检查提供的 favoriteColor
是否有效:
validations.add(
"favoriteColor", as: String.self,
is: .in("red", "blue","green"),
required: false
)
由于无法从无效值中解码 Color
,因此此验证将 String
用作基本类型。它使用 .in
验证器来验证该值是有效的选项:红色、蓝色或绿色。由于该值是可选的,因此将 required
设置为 false 表示如果请求数据中缺少此字段,则验证不会失败。
请注意,如果缺少此字段,则收藏夹颜色验证将通过,但如果提供 null
,则不会通过。 如果要支持 null
,请将验证类型更改为 String?
,并使用 .nil ||
。
validations.add(
"favoriteColor", as: String?.self,
is: .nil || .in("red", "blue", "green"),
required: false
)
自定义错误¶
你可能希望将自定义的可读错误添加到你的 Validations
或者 Validator
。为此,只需添加 customFailureDescription
参数,该参数将覆盖默认错误信息。
validations.add(
"name",
as: String.self,
is: !.empty,
customFailureDescription: "Provided name is empty!"
)
validations.add(
"username",
as: String.self,
is: .count(3...) && .alphanumeric,
customFailureDescription: "Provided username is invalid!"
)
验证器¶
以下是当前支持的验证器的列表,并简要说明了它们的作用:
验证方式 | 描述 |
---|---|
.ascii |
仅包含ASCII字符 |
.alphanumeric |
仅包含字母数字字符 |
.characterSet(_:) |
仅包含提供的 CharacterSet 中的字符 |
.count(_:) |
在提供范围内的集合计数 |
.email |
包含有效的电子邮件 |
.empty |
集合为空 |
.in(_:) |
值在提供的“集合”中 |
.nil |
值为null |
.range(_:) |
值在提供的范围内 |
.url |
包含有效的URL |
验证器也可以使用运算符组合起来以构建复杂的验证:
操作符 | 位置 | 描述 |
---|---|---|
! |
前面 | 反转验证器,要求相反 |
&& |
中间 | 组合两个验证器,需要同时满足 |
|| |
中间 | 组合两个验证器,至少满足一个 |
自定义验证器¶
创建一个自定义的验证器用于验证邮政编码,允许你通过扩展验证器框架的功能来实现。本节中,我们将引导你完成创建用于验证邮政编码的自定义验证器的步骤。
首先,创建一个新类型用于表示 ZipCode
的验证结果。这个结构体负责报告给定的字符串是否是有效的邮政编码。
extension ValidatorResults {
/// 表示验证器的结果,该验证器检查一个字符串是否为有效的邮政编码。
public struct ZipCode {
/// 指示输入是否为有效的邮政编码。
public let isValidZipCode: Bool
}
}
接下来,使新类型遵循 ValidatorResult
协议,该接口定义了自定义验证器所期望的行为。
extension ValidatorResults.ZipCode: ValidatorResult {
public var isFailure: Bool {
!self.isValidZipCode
}
public var successDescription: String? {
"is a valid zip code"
}
public var failureDescription: String? {
"is not a valid zip code"
}
}
最后,实现邮政编码的验证逻辑。使用正则表达式检查输入字符串是否与美国邮政编码的格式匹配。
private let zipCodeRegex: String = "^\\d{5}(?:[-\\s]\\d{4})?$"
extension Validator where T == String {
/// 验证一个 `String` 是否是有效的邮政编码。
public static var zipCode: Validator<T> {
.init { input in
guard let range = input.range(of: zipCodeRegex, options: [.regularExpression]),
range.lowerBound == input.startIndex && range.upperBound == input.endIndex
else {
return ValidatorResults.ZipCode(isValidZipCode: false)
}
return ValidatorResults.ZipCode(isValidZipCode: true)
}
}
}
现在你已经定义了自定义的 zipCode
验证器,你可以在应用程序中使用它来验证邮政编码。只需将以下行添加到你的验证代码中:
validations.add("zipCode", as: String.self, is: .zipCode)