-
Notifications
You must be signed in to change notification settings - Fork 1.1k
docs: add parsing request #1519
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,204 @@ | ||
| --- | ||
| lang: en | ||
| title: 'Parsing requests' | ||
| keywords: LoopBack 4.0, LoopBack 4 | ||
| tags: | ||
| sidebar: lb4_sidebar | ||
| permalink: /doc/en/lb4/Parsing-requests.html | ||
| summary: | ||
| --- | ||
|
|
||
| ## Parsing Requests | ||
|
|
||
| This is an action in the default HTTP sequence, it parses arguments from an | ||
| incoming request and uses them as inputs to invoke the corresponding controller | ||
| method. | ||
|
|
||
| This action contains 3 steps: | ||
|
|
||
| - Parses arguments from request query, body, path and header according to the | ||
| operation's OpenAPI specification. | ||
| - Coerces parameters from string to its corresponding JavaScript run-time type. | ||
| - Performs validation on the parameters and body data. | ||
|
|
||
| ### Parsing Raw Data | ||
|
|
||
| The code below defines a typical endpoint by decorating a controller method with | ||
| rest decorators. | ||
|
|
||
| ```ts | ||
| class TodoController { | ||
| constructor(@repository(TodoRepository) protected todoRepo: TodoRepository) {} | ||
|
|
||
| @put('/todos/{id}') | ||
| async replaceTodo( | ||
| @param.path.number('id') id: number, | ||
| @requestBody() todo: Todo, | ||
| ): Promise<boolean> { | ||
| return await this.todoRepo.replaceById(id, todo); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| An OpenAPI operation specification will be generated in-memory to describe it, | ||
| and raw data is parsed from request according to the specification. In the | ||
| example above, the first parameter is from source `path`, so its value will be | ||
| parsed from a request's path. | ||
|
|
||
| {% include note.html title="Controller documentation" content="See [controller](Controller.md) for more details of defining an endpoint." %} | ||
|
|
||
| {% include note.html title="OpenAPI operation object" content="See | ||
| [OpenAPI operation object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#operationObject) | ||
| to know more about its structure." %} | ||
|
|
||
| ### Coercion | ||
|
|
||
| The parameters parsed from path, header, and query of a http request are always | ||
| in the string format when using the `http` module in Node.js to handle requests. | ||
| Therefore when invoking a controller function, a parameter need to be converted | ||
| to its corresponding JavaScript runtime type, which is inferred from its | ||
| parameter specification. | ||
|
|
||
| For example, the operation `replaceTodo` in section | ||
| [parsing raw data](#parsing-raw-data) takes in a number `id` as the first input. | ||
| Without coercion,`id` would have to be manually cast into the number type before | ||
| it can be used as seen below: | ||
|
|
||
| ```ts | ||
| @put('/todos/{id}') | ||
| async replaceTodo( | ||
| @param.path.number('id') id: number, | ||
| @requestBody() todo: Todo, | ||
| ): Promise<boolean> { | ||
| // NO need to do the "string to number" convertion now, | ||
| // coercion automatically handles it for you. | ||
| id = +id; | ||
| return await this.todoRepo.replaceById(id, todo); | ||
| } | ||
| ``` | ||
|
|
||
| ### Validation | ||
|
|
||
| Validations are applied on the parameters and the request body data. They also | ||
| use OpenAPI specification as the reference to infer the validation rules. | ||
|
|
||
| #### Parameters | ||
|
|
||
| We have the data type safety check for the parameters parsed from header, path, | ||
| and query. For example, if a parameter should be an integer, then a number with | ||
| decimal like "1.23" would be rejected. | ||
|
|
||
| You can specify a parameter's type by calling shortcut decorators of `@param` | ||
| like `@param.query.integer()`. A list of available shortcuts can be found in the | ||
| [API Docs](https://apidocs.strongloop.com/@loopback%2fdocs/openapi-v3.html#param). | ||
| Check out the section on | ||
| [parameter decorators](Decorators.md#parameter-decorator) for instructions on | ||
| how to decorate the controller parameter. | ||
|
|
||
| Here are our default validation rules for each type: | ||
|
|
||
| - number: validated by `isNaN(Number(data))`. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick: I suggest to either remove
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bajtos hmm...I added it for each line....which line did you find not inconsistent? |
||
| - integer: validated by `Number.isInteger(data)`. | ||
| - long: validated by `Number.isSafeInteger(data)`. | ||
| - date-time: should be a valid date-time defined in | ||
| [RFC3339](https://xml2rfc.tools.ietf.org/public/rfc/html/rfc3339.html#anchor14). | ||
| - date: should be a valid full-date defined in | ||
| [RFC3339](https://xml2rfc.tools.ietf.org/public/rfc/html/rfc3339.html#anchor14). | ||
| - boolean: after converted to all upper case, should be one of the following | ||
| values: `TRUE`, `1`, `FALSE` or `0`. | ||
|
|
||
| #### Request Body | ||
|
|
||
| The data from request body is validated against its OpenAPI schema | ||
| specification. We use [AJV](https://github.com/epoberezkin/ajv) module to | ||
| perform the validation, which validates data with a JSON schema generated from | ||
| the OpenAPI schema specification. | ||
|
|
||
| Take again the operation replaceTodo for instance: | ||
|
|
||
| ```ts | ||
| import {Todo} from './models'; | ||
|
|
||
| // class definition | ||
| ... | ||
| @put('/todos/{id}') | ||
| async replaceTodo( | ||
| @param.path.number('id') id: number, | ||
| @requestBody() todo: Todo, | ||
| ): Promise<boolean> { | ||
| return await this.todoRepo.replaceById(id, todo); | ||
| } | ||
| ... | ||
| ``` | ||
|
|
||
| The request body specification is defined by applying `@requestBody()` to | ||
| argument `todo`, and the schema specification inside it is inferred from its | ||
| type `Todo`. The type is exported from a `Todo` model. | ||
|
|
||
| {% include note.html title="Model documentation" content="See [model](Model.md) to know more details about how to decorate a model class" %} | ||
|
|
||
| When the `PUT` method on the `/todo/{id}` gets called, the `todo` instance from | ||
| the request body will be validated with a well defined specification. | ||
|
|
||
| Validation of model objects is heavily dependent on its OpenAPI Schema defined | ||
| in/by the `@requestBody` decorator. Please refer to the documentation on | ||
| [@requestBody decorator](Decorators.md#requestbody-decorator) to get a | ||
| comprehensive idea of defining custom validation rules for your models. | ||
|
|
||
| A few tips worth mentioning: | ||
|
|
||
| - If a model property's type refers to another model, make sure it is also | ||
| decorated with `@model` decorator. | ||
|
|
||
| - If you're using API first development approach, you can also provide the | ||
| request body specification in decorators like `route()` and | ||
| [`api()`](Decorators.md#api-decorator), this requires you to provide a | ||
| completed request body specification. | ||
|
|
||
| #### Localizing errors | ||
|
|
||
| A body data may break multiple validation rules, like missing required fields, | ||
| data in a wrong type, data that exceeds the maximum length, etc...The validation | ||
| errors are returned in batch mode, and user can find all of them in | ||
| `error.details`, which describes errors in a machine-readable way. | ||
|
|
||
| Each element in the `error.details` array reports one error. It contains 4 | ||
| attributes: | ||
|
|
||
| - `path`: The path to the invalid field. | ||
| - `code`: A single word code represents the error's type. | ||
| - `message`: A human readable description of the error. | ||
| - `info`: Some additional details that the 3 attributes above don't cover. | ||
|
|
||
| In most cases `path` shows which field in the body data is invalid. For example, | ||
| if an object schema's `id` field should be a string, while the data in body has | ||
| it as a number: `{id: 1, name: 'Foo'}`. Then the error entry is: | ||
|
|
||
| ```ts | ||
| { | ||
| path: '.id', | ||
| code: 'type', | ||
| message: 'should be string', | ||
| info: {type: 'boolean'}, | ||
| } | ||
| ``` | ||
|
|
||
| And in this case the error code is `type`. A reference of all the possible code | ||
| could be found in | ||
| [ajv validation error keywords(codes)](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md). | ||
|
|
||
| In some exception scenarios, like a required field is missing, the `path` is | ||
| empty, but the field location is easy to find in `message` and `info`. For | ||
| example, `id` is a required field while it's missing in a request body: | ||
| `{name: 'Foo'}`, the error entry will be: | ||
|
|
||
| ```ts | ||
| { | ||
| // `path` is empty | ||
| path: '', | ||
| code: 'required', | ||
| message: "should have required property 'id'", | ||
| // you can parse the missing field from `info.missingProperty` | ||
| info: {missingProperty: 'id'}, | ||
| }, | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -229,9 +229,8 @@ How to get query string param values. | |
|
|
||
| ### Parsing Requests | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we're going to have a subpage for this section, we should get rid of its mention entirely here.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. important catch! thx |
||
|
|
||
| {% include content/tbd.html %} | ||
|
|
||
| Parsing and validating arguments from the request url, headers, and body. | ||
| Parsing and validating arguments from the request url, headers, and body. See | ||
| page [Parsing requests](Parsing-requests.md) | ||
|
|
||
| ### Invoking controller methods | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you specify what the key that the action is bound to is (PARSE_PARAMS)? It might be good to see it in code as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure what's the point of introducing the binding key here...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The coercion/validation is done by
parser.ts'sparseOperationArgs, which is bound by default to the keyRestBindings.SequenceActions.PARSE_PARAMS. Without this action being triggered int he sequence, no validation/coercion would be done.The section is also a child page of
Sequence.md, so might as wellUh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see...my doc only covers the default behaviour of this action, and I think those default actions would be bound automatically after extending
Applicationwith the rest mixin :)I would like to add a section on
sequencefor replacing/customizing the actions, and list the binding key for each action under it, if such a section doesn't exist. Otherwise we need to repeat these things in every action: in which situations they come out of the box, in which situations it's on the users to bind them manually, and how to replace them with a custom action.thought?