-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
383 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
--- | ||
lang: en | ||
title: 'Validation in ORM Layer' | ||
keywords: LoopBack 4.0, LoopBack 4 | ||
sidebar: lb4_sidebar | ||
permalink: /doc/en/lb4/Validation-ORM-layer.html | ||
--- | ||
|
||
The validation in the ORM layer is to make sure the data being added or updated | ||
to the database is valid. | ||
|
||
Schema constraints are enforced by specific databases, such as unique index. For | ||
pre-processing against CRUD operations, see details: | ||
https://loopback.io/doc/en/lb4/migration-models-operation-hooks.html. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
--- | ||
lang: en | ||
title: 'Validation in REST Layer' | ||
keywords: LoopBack 4.0, LoopBack 4 | ||
sidebar: lb4_sidebar | ||
permalink: /doc/en/lb4/Validation-REST-layer.html | ||
--- | ||
|
||
At the REST layer, request body is being validated against the OpenAPI schema | ||
specification. | ||
|
||
## Type Validation | ||
|
||
Type validation comes out-of-the-box in LoopBack. | ||
|
||
> Validation is applied on the parameters and the request body data. It also | ||
> uses OpenAPI specification as the reference to infer the validation rules. | ||
Take the `capacity` property in the [`CoffeeShop` model](Validation.md) as an | ||
example; it is a number. When creating a `CoffeeShop` by calling `/POST`, if a | ||
string is specified for the `capacity` property as below: | ||
|
||
```json | ||
{ | ||
"city": "Toronto", | ||
"phoneNum": "416-111-1111", | ||
"capacity": "100" | ||
} | ||
``` | ||
|
||
a "request body is invalid" error is expected: | ||
|
||
```json | ||
{ | ||
"error": { | ||
"statusCode": 422, | ||
"name": "UnprocessableEntityError", | ||
"message": "The request body is invalid. See error object `details` property for more info.", | ||
"code": "VALIDATION_FAILED", | ||
"details": [ | ||
{ | ||
"path": ".capacity", | ||
"code": "type", | ||
"message": "should be number", | ||
"info": { | ||
"type": "number" | ||
} | ||
} | ||
] | ||
} | ||
} | ||
``` | ||
|
||
## Validation against OpenAPI Schema Specification | ||
|
||
For validation against an OpenAPI schema specification, the | ||
[AJV module](https://github.com/epoberezkin/ajv) is used to validate data with a | ||
JSON schema generated from the OpenAPI schema specification. More details can be | ||
found about | ||
[validation keywords](https://github.com/epoberezkin/ajv#validation-keywords) | ||
and | ||
[annotation keywords](https://github.com/epoberezkin/ajv#annotation-keywords) | ||
available in AJV. AJV can also be extended with custom keywords and formats, see | ||
[AJV defining custom keywords page](https://ajv.js.org/custom.html). | ||
|
||
Besides AJV, other third-party validation libraries, such as | ||
[@hapi/joi](https://github.com/hapijs/joi) and | ||
[class-validator](https://github.com/typestack/class-validator), can be used. | ||
|
||
Below are a few examples of using AJV for validation. The source code of the | ||
snippets can be found in the | ||
[coffee-shop.model.ts in the example app](https://github.com/strongloop/loopback-next/blob/master/examples/validation-app/src/models/coffee-shop.model.ts). | ||
|
||
{% include note.html content="The `jsonSchema` property expects [JSON Schema Draft-07](http://json-schema.org/draft/2019-09/json-schema-validation.html), which is then transformed into the [OAS 3](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md) variant." %} | ||
|
||
### Example 1: Length limit | ||
|
||
A typical validation example is to have a length limit on a string using the | ||
keywords `maxLength` and `minLength`. For example: | ||
|
||
{% include code-caption.html content="/src/models/coffee-shop.model.ts" %} | ||
|
||
```ts | ||
@property({ | ||
type: 'string', | ||
required: true, | ||
// --- add jsonSchema ----- | ||
jsonSchema: { | ||
maxLength: 10, | ||
minLength: 1, | ||
}, | ||
// ------------------------ | ||
}) | ||
city: string; | ||
``` | ||
|
||
If the `city` property in the request body does not satisfy the requirement as | ||
follows: | ||
|
||
```json | ||
{ | ||
"city": "a long city name 123123123", | ||
"phoneNum": "416-111-1111", | ||
"capacity": 10 | ||
} | ||
``` | ||
|
||
an error will occur with details on what has been violated: | ||
|
||
```json | ||
{ | ||
"error": { | ||
"statusCode": 422, | ||
"name": "UnprocessableEntityError", | ||
"message": "The request body is invalid. See error object `details` property for more info.", | ||
"code": "VALIDATION_FAILED", | ||
"details": [ | ||
{ | ||
"path": ".city", | ||
"code": "maxLength", | ||
"message": "should NOT be longer than 10 characters", | ||
"info": { | ||
"limit": 10 | ||
} | ||
} | ||
] | ||
} | ||
} | ||
``` | ||
|
||
### Example 2: Value range for a number | ||
|
||
For numbers, the validation rules are used to specify the range of the value. | ||
For example, any coffee shop would not be able to have more than 100 people, it | ||
can be specified as follows: | ||
|
||
{% include code-caption.html content="/src/models/coffee-shop.model.ts" %} | ||
|
||
```ts | ||
@property({ | ||
type: 'number', | ||
required: true, | ||
// --- add jsonSchema ----- | ||
jsonSchema: { | ||
maximum: 100, | ||
minimum: 1, | ||
}, | ||
// ------------------------ | ||
}) | ||
capacity: number; | ||
``` | ||
|
||
### Example 3: Pattern in a string | ||
|
||
Model properties, such as phone number and postal/zip code, usually have certain | ||
patterns. In this case, the `pattern` keyword is used to specify the | ||
restrictions. | ||
|
||
Below shows an example of the expected pattern of phone numbers, i.e. a sequence | ||
of 10 digits separated by `-` after the 3rd and 6th digits. | ||
|
||
{% include code-caption.html content="/src/models/coffee-shop.model.ts" %} | ||
|
||
```ts | ||
@property({ | ||
type: 'string', | ||
required: true, | ||
// --- add jsonSchema ----- | ||
jsonSchema: { | ||
pattern: '\\d{3}-\\d{3}-\\d{4}', | ||
}, | ||
// ------------------------ | ||
}) | ||
phoneNum: string; | ||
``` | ||
|
||
{% include tip.html content="RegExp can be converted into a string with `.source` to avoid escaping backslashes" %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
--- | ||
lang: en | ||
title: 'Validation in the Controller, Repository and Service Layer' | ||
keywords: LoopBack 4.0, LoopBack 4 | ||
sidebar: lb4_sidebar | ||
permalink: /doc/en/lb4/Validation-controller-repo-service-layer.html | ||
--- | ||
|
||
In the case where validation rules are not static, validation cannot be | ||
specified at the model level. Hence, validation can be added in the controller | ||
layer. | ||
|
||
Take an example of a promo code in an order, it is usually a defined value that | ||
is only valid for a certain period of time. And in the CoffeeShop example, the | ||
area code of a phone number usually depends on the geolocation. | ||
|
||
## Add validation function in the Controller method | ||
|
||
The simplest way is to apply the validation function in the controller method. | ||
For example: | ||
|
||
{% include code-caption.html content="/src/controllers/coffee-shop.controller.ts" %} | ||
|
||
```ts | ||
// create a validatePhoneNum function and call it here | ||
if (!this.validatePhoneNum(coffeeShop.phoneNum, coffeeShop.city)) | ||
throw new Error('Area code in phone number and city do not match.'); | ||
return this.coffeeShopRepository.create(coffeeShop); | ||
``` | ||
|
||
## Add interceptor for validation | ||
|
||
Another way is to use [interceptors](Interceptors.md). | ||
|
||
Interceptors are reusable functions to provide aspect-oriented logic around | ||
method invocations. | ||
|
||
Interceptors have access to the invocation context, including parameter values | ||
for the method call. It can perform more specific validation, for example, | ||
calling a service to check if an address is valid. There are three types of | ||
interceptors for different scopes: global, class-level and method-level | ||
interceptors. | ||
|
||
Interceptors can be created using the | ||
[interceptor generator](https://loopback.io/doc/en/lb4/Interceptor-generator.html) | ||
`lb4 interceptor` command. In the CoffeeShop example, the `phoneNum` in the | ||
`CoffeeShop` request body will be validated for the `POST` and `PUT` calls | ||
whether the area code in the phone number matches the specified city. Since this | ||
is only applicable to the CoffeeShop endpoints, a non-global interceptor is | ||
created, i.e. specify `No` in the `Is it a global interceptor` prompt. | ||
|
||
```sh | ||
$ lb4 interceptor | ||
? Interceptor name: validatePhoneNum | ||
? Is it a global interceptor? No | ||
create src/interceptors/validate-phone-num.interceptor.ts | ||
update src/interceptors/index.ts | ||
|
||
Interceptor ValidatePhoneNum was created in src/interceptors/ | ||
``` | ||
|
||
In the newly created interceptor `ValidatePhoneNumInterceptor`, the function | ||
`intercept` is the place where the pre-invocation and post-invocation logic can | ||
be added. | ||
|
||
{% include code-caption.html content="/src/interceptors/validate-phone-num-interceptor.ts" %} | ||
|
||
```ts | ||
async intercept( | ||
invocationCtx: InvocationContext, | ||
next: () => ValueOrPromise<InvocationResult>, | ||
) { | ||
// Add pre-invocation logic here | ||
// ------ VALIDATE PHONE NUMBER ---------- | ||
let coffeeShop: CoffeeShop | undefined; | ||
if (invocationCtx.methodName === 'create') | ||
coffeeShop = invocationCtx.args[0]; | ||
else if (invocationCtx.methodName === 'updateById') | ||
coffeeShop = invocationCtx.args[1]; | ||
|
||
if ( | ||
coffeeShop && | ||
!this.isAreaCodeValid(coffeeShop.phoneNum, coffeeShop.city) | ||
) { | ||
const err: ValidationError = new ValidationError( | ||
'Area code and city do not match', | ||
); | ||
err.statusCode = 422; | ||
throw err; | ||
} | ||
// ---------------------------------------- | ||
|
||
const result = await next(); | ||
// Add post-invocation logic here | ||
return result; | ||
} catch (err) { | ||
// Add error handling logic here | ||
throw err; | ||
} | ||
} | ||
|
||
isAreaCodeValid(phoneNum: string, city: string): Boolean { | ||
// add some dummy logic | ||
const areaCode: string = phoneNum.slice(0, 3); | ||
if ( | ||
!( | ||
city.toLowerCase() === 'toronto' && | ||
(areaCode === '416' || areaCode === '647') | ||
) | ||
) | ||
return false; | ||
|
||
// otherwise it always return true | ||
return true; | ||
} | ||
``` | ||
|
||
Now that the interceptor is created, we are going to apply this to the | ||
controller with the `CoffeeShop` endpoints, `CoffeeShopController`. | ||
|
||
{% include code-caption.html content="/src/controllers/coffee-shop.controller.ts" %} | ||
|
||
```ts | ||
// Add these imports for interceptors | ||
import {inject, intercept} from '@loopback/core'; | ||
import {ValidatePhoneNumInterceptor} from '../interceptors'; | ||
|
||
// Add this line to apply interceptor to this class | ||
@intercept(ValidatePhoneNumInterceptor.BINDING_KEY) | ||
export class CoffeeShopController { | ||
// .... | ||
} | ||
``` | ||
|
||
## Reference | ||
|
||
To find out more about interceptors, check out the blog posts below: | ||
|
||
- [Learning LoopBack 4 Interceptors (Part 1) - Global Interceptors](https://strongloop.com/strongblog/loopback4-interceptors-part1/) | ||
- [Learning LoopBack 4 Interceptors (Part 2) - Method Level and Class Level Interceptors](https://strongloop.com/strongblog/loopback4-interceptors-part2/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
--- | ||
lang: en | ||
title: 'Validation' | ||
keywords: LoopBack 4.0, LoopBack 4 | ||
sidebar: lb4_sidebar | ||
permalink: /doc/en/lb4/Validation.html | ||
--- | ||
|
||
Within a LoopBack application, validation can be added in various places | ||
depending on the usage. Some types of validations come out-of-the-box in | ||
LoopBack, such as type validation in the REST layer, whereas some require | ||
additional configuration or code. | ||
|
||
There are various types of validations such as: | ||
|
||
- validation of input/output for method invocations | ||
- validation of model instance properties, for example, age < 0 | ||
- validation of model collections, for example, uniqueness | ||
|
||
Let's take a closer look at how validation can be added in the following layers: | ||
|
||
- [REST layer](Validation-REST-layer.md) | ||
- [Controller, Repository and Service Layer](Validation-controller-layer.md) | ||
- [ORM layer](Validation-ORM-layer.md) | ||
|
||
The | ||
[validation-app example application](https://github.com/strongloop/loopback-next/blob/master/examples/validation-app) | ||
is used in the following documentation pages for demonstration. In the example, | ||
a `CoffeeShop` model is being used. It has the following properties. | ||
|
||
| Property name | Type | Description | | ||
| ------------- | :----: | ------------------------------------- | | ||
| shopId | string | ID of the coffee shop | | ||
| city | string | City where the coffee shop is located | | ||
| phoneNum | string | Phone number of the coffee shop | | ||
| capacity | number | Capacity of the coffee shop | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters