diff --git a/couscous.yml b/couscous.yml index 65993cb3..1cea19f7 100644 --- a/couscous.yml +++ b/couscous.yml @@ -50,6 +50,9 @@ menu: guide-views: text: Views relativeUrl: guide/views.html + guide-validation: + text: Validation + relativeUrl: guide/validation.html http: name: HTTP items: diff --git a/guide/config.md b/guide/config.md index 236c55cf..63d59169 100644 --- a/guide/config.md +++ b/guide/config.md @@ -27,7 +27,7 @@ And an example of how this might look: } ``` -What that's saying, is that our application should start a single server named 'default' serving port `8080` on host `0.0.0.0`. This represents the following url: `http://localhost:8080`. +What that's saying, is that our application should start a single server named 'http' serving port `8080` on host `0.0.0.0`. This represents the following url: `http://localhost:8080`. ### Custom Keys @@ -45,7 +45,7 @@ Let's add a custom key to the `servers.json` file: This can be accessed from your application's config using the following. -```Swift +```swift let customValue = app.config["server", "http", "custom-key"].string ``` @@ -70,13 +70,13 @@ You can access your config directory with the following syntax. `app.config[<#fi We can access this file by making sure the first argument in our subscript is keys. To get the first name in our list: -```Swift +```swift let name = app.config["keys", "test-names", 0].string ``` Or our mongo url: -```Swift +```swift let mongoUrl = app.config["keys", "mongo", "url"].string ``` @@ -145,7 +145,7 @@ Let's start with the following JSON files. Please notice that servers.json, and production/servers.json both declare the same keys. host, and port. In our application, we'll call: -```Swift +```swift // will load 0.0.0.0 or 127.0.0.1 based on above config let host = app.config["servers", "http", "host"].string // will load 9000, or environment variable port. @@ -166,7 +166,7 @@ Arguments set through the command line can be accessed through config's cli file would be accessible within your application by using the following: -```Swift +```swift let mongoPassword = app.config["cli", "mongo-password"].string ``` @@ -180,6 +180,6 @@ If you want command line arguments set to a file besides "cli", you can use this would be accessible within your application by using the following: -```Swift +```swift let analyticsKey = app.config["keys", "analytics"].string ``` diff --git a/guide/validation.md b/guide/validation.md new file mode 100644 index 00000000..e65f09e9 --- /dev/null +++ b/guide/validation.md @@ -0,0 +1,106 @@ +--- +currentMenu: guide-validation +--- + +# Validation + +Vapor provides a few different ways to validate data coming into your application. Let's start by looking at the most common. + +## Common Usage + +Several useful convenience validators are included by default. You can use these to validate data coming into your application, or combine them and create your own. + +Let's look at the most common way to validate data. + +```swift +class Employee { + var email: Valid + var name: Valid + + init(request: Request) throws { + name = try request.data["name"].validated() + email = try request.data["email"].validated() + } +} +``` + +Here we have a typical Employee model with an `email` and `name` property. By declaring both of these properties as `Valid<>`, you are ensuring that these properties can only ever contain valid data. The Swift type checking system will prevent anything that does not pass validation from being stored. + +To store something in a `Valid<>` property, you must use the `.validated()` method. This is available for any data returned by `request.data`. + +`Email` is a real `validator` included with Vapor, but `Name` is not. Let's take a look at how you can create a Validator. + +```swift +Valid +Valid +Valid> +Valid> +Valid> +Valid> +Valid> +``` + +## Validators vs. ValidationSuites + +Validators, like `Count` or `Contains` can have multiple configurations. For example: + +```swift +let name: Valid> = try "Vapor".validated(by: Count.max(5)) +``` + +Here we are validating that the `String` is at most 5 characters long. The type of `Valid` tells us that the string has been validated to be a certain count, but it does not tell us exactly what that count was. The string could have been validated to be less than three characters or more than one million. + +Because of this, `Validators` themselves are not as type safe as some applications might desire. `ValidationSuites` fix this. They combine multiple `Validators` and/or `ValidationSuites` together to represent exactly what type of data should be considered valid + +## Custom Validator + +Here is how to create a custom `ValidationSuite`. + +```swift +class Name: ValidationSuite { + static func validate(input value: String) throws { + let evaluation = OnlyAlphanumeric.self + && Count.min(5) + && Count.max(20) + + try evaluation.validate(input: value) + } +} +``` + +You only have to implement one method. In this method, use any other validators or logic to create your custom validator. Here we are defining a Name as only accepting alphanumeric Strings that are between 5 and 20 characters. + +Now we can be sure that anything of type Valid follows these rules. + +## Combining Validators + +In the `Name` validator, you can see that `&&` is being used to combine validators. You can use `&&` as well as `||` to combine any validator as you would boolean values with an `if` statement. + +You can also use `!` to invert the validator. + +```swift +let symbols = input.validated(by: !OnlyAlphanumeric.self) +``` + +## Testing Validity + +While `validated() throw` is the most common method for validating, there are two others. + +```swift +let passed = input.passes(Count.min(5)) +let valid = try input.tested(Count.min(5)) +``` + +`passes()` returns a boolean indicating whether or not the test passed. `tested()` will throw if the validation does not pass. But unlike `validated()` which returns a `Valid<>` type, `tested()` returns the original type of the item it was called on. + +## Validation Failures + +Vapor will automatically catch validation failures in the `ValidationMiddleware`. But you can catch them on your own, or customize responses for certain types of failures. + +```swift +do { + //validation here +} catch let error as ValidationErrorProtocol { + print(error.message) +} +```