-
Notifications
You must be signed in to change notification settings - Fork 25
Handlers
Handlers are a special type of service used for implementing Express routes. They are automatically loaded from the handlers directory of the application during server startup.
Handlers can either use the Express app directory in order to set up routes, or they can be configured through Swagger.
Like a service, a handler is a module with an init method. Other services are dependency-injected as parameters of the init
function, including the express app via the app
parameter. An optional callback can be used for asynchronous initialization.
function(app, callback) {
...
}
Below is an example handler module that registers endpoints on the Express app.
exports.init = function(app) {
app.get('/foo', function(req, res) {
res.status(200).send('/foo Endpoint');
});
};
Swagger is a tool used to describe and document RESTful APIs. Within BlueOak Server, Swagger can be used to automatically configure Express routes so that it's not necessary to hard-code route paths within handler code.
Additionally, Swagger can enable validation of request payloads and reduce the amount of code needed.
Swagger documents in either JSON or YAML format should be placed in the swagger directory within the project.
When the Swagger doc is loaded, the server will look for a file within the handlers directory with the same base name as the Swagger doc. E.g., if your projects contains swagger/endpoints.json, the server will look for a corresponding handler file in handlers/endpoint.js.
For each path defined in the Swagger doc, the server will look for an operationId. The operationId determines the name of the function within the handler that handles requests to the given path.
BlueOak Server will validate incoming requests against Swagger endpoints and respond with 422
and a body providing details of the validation error(s) to the client, e.g.:
{
"message": "Error validating request body",
"status": 422,
"type": "ValidationError",
"source": {
"type": "body"
},
"validation_errors": [
{
"message": "Missing required property: id",
"schemaPath": "/required/0",
"code": 302,
"field": "/id",
"in": "body"
}
]
}
Another example:
{
"message": "Multiple validation errors for this request",
"status": 422,
"type": "ValidationError",
"source":
{
"type": "request"
},
"validation_errors":
[
{
"message": "Missing effectiveDate query parameter",
"schemaPath": "",
"code": 10404,
"field": "effectiveDate",
"in": "query"
},
{
"message": "Invalid type: number (expected string)",
"schemaPath": "/properties/zip/type",
"code": 0,
"field": "/zip",
"in": "body"
},
{
"message": "Missing required property: longitude",
"schemaPath": "/properties/location/required/1",
"code": 302,
"field": "/location/longitude",
"in": "body"
}
]
}
In addition to the request body, BlueOak Server will all check for any missing or invalid header, form, path, or query parameters.
By default it validates all aspects the request before issuing a validation error response. If, however, you want it to stop after the first validation error, set rejectRequestAfterFirstValidationError
in the swagger
stanza of your config, e.g.:
"swagger": {
"rejectRequestAfterFirstValidationError": true
}
The Swagger spec supports inheritance/polymorphism using the discriminator property.
If the discriminator property is used, BlueOak Server will validate against the actual provided model. For example if the response requires model 'Animal', and the provided model is 'Cat' (a subtype of 'Animal'), then it will validate against the 'Cat' model.
You can disable polymorphic validation (usually you wouldn't want to do this) in your config, e.g.:
"swagger": {
"polymorphicValidation": "off"
}
Alternately, you can set it to only print warnings in the console, e.g.:
"swagger": {
"polymorphicValidation": "warn"
}
(This is meant to help teams migrating from BOS < 2.4.0 that had made use of the discriminator
property before the instance would be validated against the specific match sub-model.)
It can also be configured to validate the responses you send. (This is very valuable during development and testing, but probably something you'd never want to have on in production.)
Turn on the response model validation feature by setting the swagger.validateResponseModels
property in your config to either "warn"
, "error"
or "fail"
. (If unset, or set to anything else, response model validation will be off.)
- if set to
"warn"
, any validation problems are printed usinglogger.warn
- if set to
"error"
, any validation problems are included in a_response_validation_errors
property added to the response body, as well as printed usinglogger.error
- if set to
"fail"
, any validation problems cause a completely different response:- the failing response body is returned in the the
invalidResponse.body
parameter - the status code of the response is changed to
522
- (see the related test case for an example response)
- the failing response body is returned in the the
- if response validation is enabled in any mode, the validated body is attached to the Express response object in a field named
sentBody
for use by handlers and/or middleware that are configured to execute after the response is sent
example config:
"swagger": {
"validateResponseModels": "warn"
}
The purpose of the swagger ref compiler is allow you write all your models (the definitions
, parameters
, and responses
sections of the swagger spec), as individual files, and have them included into your specification without having to manage all the $ref
statements yourself.
- Include "refCompiler" configuration in the
swagger
config stanza - For each swagger API that you are serving, specify the name of the base spec file and the corresponding reference directories. Each reference directory can contain
/definitions
,/parameters
, and/responses
directories. - For .yaml specs, ref compiler looks for the comment
### ref-compiler: BEGIN
when deciding where to overwrite the existing spec. Make sure to add this comment the first time ref compiler runs if you have have any existing definitions, parameters or responses that you want overwritten.
Sample config (in config/default.json
, for example):
"swagger": {
"refCompiler": {
"petstore": {
"baseSpecFile": "petstore.json",
"refDirs": [
"public",
"v1-assets"
]
},
"api-v1": {
"baseSpecFile": "api-v1.yaml",
"refDirs": [
"public"
]
}
}
}
Related directory structure for that sample config:
config/
handlers/
index.js
middleware/
nodemon.json
swagger/
api-v1.yaml
petstore.json
public/
definitions/
Activities.yaml
Activity.yaml
FoodProduct.yaml
Label.yaml
PriceEstimate.yaml
Product.yaml
Profile.yaml
ToyLabel.yaml
ToyProduct.yaml
parameters/
QueryName.yaml
responses/
ProductSearchResults.yaml
paths.yaml
v1-assets/
definitions/
Error.yaml
parameters/
PathId.yaml
responses/
NotFound.yaml
Unauthorized.yaml
test/
Also, take a look at the Swagger example.
By default the swagger specs are served up through :/swagger/. There's no extension on the filename, meaning that if you have a petstore.json or a petstore.yaml, the file will be served through :/swagger/petstore. By serving the spec, it's possible to import it into a tool like swagger ui and easily make calls to swagger endpoints.
To disable serving of spec files, set `serve' to false in the swagger config, e.g.
"swagger": {
"serve": false,
}
The default context root for serving swagger specs is /swagger, but this can be changed through the context
setting in the swagger config.
Since the swagger specs are most often used during development, the host field in the spec is automatically changed to localhost:. This makes it easier to test the API in a local swagger ui. This behavior can be changed through the useLocalhost
setting in the swagger config. When set to false, the host value from the spec will remain unchanged.
"swagger": {
"context": "/specs",
"serve": true,
"useLocalhost": false
},
NOTE: As of version 2.8.0 the BlueOak Server recommends the use of "x-bos-" prefixed extensions. The previous "x-" prefixed extensions still work, but new code should use "x-bos-handler" and "x-bos-middleware".
This is a path-level custom field that can be used to change the handler file that defines the callback functions.
Normally the callback function will be loaded from a handler file with the same name as the swagger document, e.g. petstore.json will look for functions in the handlers/petstore.js. If you wish to load a function from a different handler file, specify the name of the handler in "x-bos-handler"
"paths": {
"/pets/{id}": {
"x-bos-handler": "foo", //use callbacks from handlers/foo.js
...
}
}
Deprecated as of v2.8.0. See the x-bos-handler above.
Use to add additional middleware callbacks to a swagger-defined endpoint. These are registered after any authentication callback and before the main handler callback.
This is a method-specific field. It can either be a single string, or an array of strings in case more than one callback is needed.
"paths": {
"/pets": {
"get": {
"x-bos-middleware": ["callback1", "callback2"]
...
By default it will look for the callback function in the default handler for the given endpoint. If, however, you wish to load a callback from a different handler, specify the callbacks using the form handler.function. For example, using "foo.bar" would register the bar function from handlers/foo.js.
Deprecated as of v2.8.0. See the x-bos-middleware above.
File uploads can be handled as form data. See the fileupload example for details.
Data types in Swagger can have both a type (string, number, etc) and format property. The spec defines a small number formats. Any format that isn't included in the spec can be customized by the user. For example, an "email" data type could have a type of "string" and format of "email".
In order to validate custom types, the swagger service allows additional formats to be specified.
swagger.addFormat('uppercase', function(data, schema) {
//check that a tag is uppercase
if (data !== data.toUpperCase()) {
return 'String must be all uppercase';
}
return null;
});
The parameters for addFormat:
- format - A string corresponding to the "format" value in schemas.
- validationFunction - A function that either returns a string describing the validation error, or null if no error has occurred.
The Swagger validator creates JS Errors if it hits a validation problem. The errors are passed off via a call to next(error)
in order for the error-handling middleware to deal with it however it wants. The errors will have a name field that you can check the type of error. In the case of the swagger validation, it returns an error with name ValidationError.
The default error handler will attempt to create an appropriate JSON response from the error and return a 422 status code. To handle them differently, see the custom middleware in examples/swagger.
Some errors are generated by our swagger validator, such as missing required query params or headers. In those cases the Error contains a simple message such as "Missing required parameter X".
However, in all other cases, or in the case of validating a body, the validation failures are generated by the tv4 library. BlueOak server creates a single ValidatorError, and embeds all the tv4-generated errors under the subErrors
field.