Skip to content
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

Upgrade to joi 4.x #1641

Merged
merged 3 commits into from
May 18, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ infrastructure. The framework supports a powerful plugin architecture for pain-f

For the latest updates and release information follow [@hapijs](https://twitter.com/hapijs) on twitter.

Current version: **4.0.x**
Current version: **5.0.x**

[![Build Status](https://secure.travis-ci.org/spumko/hapi.png)](http://travis-ci.org/spumko/hapi)

Expand Down
4 changes: 3 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
## Current Documentation
[v4.0.x](https://github.com/spumko/hapi/blob/master/docs/Reference.md)
[v5.0.x](https://github.com/spumko/hapi/blob/master/docs/Reference.md)

## Previous Documentation
[v4.1.x](https://github.com/spumko/hapi/blob/v4.1.0/docs/Reference.md)
[v4.0.x](https://github.com/spumko/hapi/blob/v4.0.0/docs/Reference.md)
[v3.1.x](https://github.com/spumko/hapi/blob/v3.1.0/docs/Reference.md)
[v3.0.x](https://github.com/spumko/hapi/blob/v3.0.0/docs/Reference.md)
[v2.6.x](https://github.com/spumko/hapi/blob/v2.6.0/docs/Reference.md)
Expand Down
45 changes: 23 additions & 22 deletions docs/Reference.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 4.0.x API Reference
# 5.0.x API Reference

- [`Hapi.Server`](#hapiserver)
- [`new Server([host], [port], [options])`](#new-serverhost-port-options)
Expand Down Expand Up @@ -183,7 +183,7 @@ When creating a server instance, the following options configure the server's be
- `credentials` - if `true`, allows user credentials to be sent ('Access-Control-Allow-Credentials'). Defaults to `false`.

- `security` - sets some common security related headers. All headers are disabled by default. To enable set `security` to `true` or to an object with
the following options:
the following options:
- `hsts` - controls the 'Strict-Transport-Security' header. If set to `true` the header will be set to `max-age=15768000`, if specified as a number
the maxAge parameter will be set to that number. Defaults to `true`. You may also specify an object with the following fields:
- `maxAge` - the max-age portion of the header, as a number. Default is `15768000`.
Expand Down Expand Up @@ -262,8 +262,8 @@ the following options:
- `maxSockets` - sets the number of sockets available per outgoing proxy host connection. `false` means use node.js default value.
Does not affect non-proxy outgoing client connections. Defaults to `Infinity`.

- `validation` - options to pass to [Joi](http://github.com/spumko/joi). Useful to set global options such as `stripUnknown` or `abortEarly` (the complete list is available [here](https://github.com/spumko/joi#validatevalue-schema-options)). Defaults to `{ modify: true }` which will cast data to the specified
types.
- `validation` - options to pass to [Joi](http://github.com/spumko/joi). Useful to set global options such as `stripUnknown` or `abortEarly`
(the complete list is available [here](https://github.com/spumko/joi#validatevalue-schema-options)). Defaults to no options.

- <a name="server.config.views"></a>`views` - enables support for view rendering (using templates to generate responses). Disabled by default.
To enable, set to an object with the following options:
Expand Down Expand Up @@ -484,7 +484,7 @@ The following options are available when adding a route:
- a validation function using the signature `function(value, options, next)` where:
- `value` - the object containing the query parameters.
- `options` - the server validation options.
- `next(err)` - the callback function called when validation is completed.
- `next(err, value)` - the callback function called when validation is completed.

- `payload` - validation rules for an incoming request payload (request body). Values allowed:
- `true` - any payload allowed (no validation performed). This is the default.
Expand All @@ -493,17 +493,17 @@ The following options are available when adding a route:
- a validation function using the signature `function(value, options, next)` where:
- `value` - the object containing the payload object.
- `options` - the server validation options.
- `next(err)` - the callback function called when validation is completed.
- `next(err, value)` - the callback function called when validation is completed.

- `path` - validation rules for incoming request path parameters, after matching the path against the route and extracting any
- `params` - validation rules for incoming request path parameters, after matching the path against the route and extracting any
parameters then stored in `request.params`. Values allowed:
- `true` - any path parameters allowed (no validation performed). This is the default.
- `false` - no path variables allowed.
- a [Joi](http://github.com/spumko/joi) validation object.
- a validation function using the signature `function(value, options, next)` where:
- `value` - the object containing the path parameters.
- `options` - the server validation options.
- `next(err)` - the callback function called when validation is completed.
- `next(err, value)` - the callback function called when validation is completed.

- `errorFields` - an optional object with error fields copied into every validation error response.
- `failAction` - determines how to handle invalid requests. Allowed values are:
Expand Down Expand Up @@ -550,20 +550,19 @@ The following options are available when adding a route:
- `'log'` - report the error but continue processing the request.
- `'ignore'` - take no action and continue processing the request.

- `response` - validation rules for the outgoing response payload (response body). Can only validate [object](#obj) response. Values allowed:
- `true` - any payload allowed (no validation performed). This is the default.
- `false` - no payload allowed.
- an object with the following options:
- `schema` - the response object validation rules expressed as one of:
- a [Joi](http://github.com/spumko/joi) validation object.
- a validation function using the signature `function(value, options, next)` where:
- `value` - the object containing the response object.
- `options` - the server validation options.
- `next(err)` - the callback function called when validation is completed.
- `sample` - the percent of responses validated (0 - 100). Set to `0` to disable all validation. Defaults to `100` (all responses).
- `failAction` - defines what to do when a response fails validation. Options are:
- `error` - return an Internal Server Error (500) error response. This is the default value.
- `log` - log the error but send the response.
- `response` - validation rules for the outgoing response payload (response body). Can only validate [object](#obj) response:
- `schema` - the response object validation rules expressed as one of:
- `true` - any payload allowed (no validation performed). This is the default.
- `false` - no payload allowed.
- a [Joi](http://github.com/spumko/joi) validation object.
- a validation function using the signature `function(value, options, next)` where:
- `value` - the object containing the response object.
- `options` - the server validation options.
- `next(err)` - the callback function called when validation is completed.
- `sample` - the percent of responses validated (0 - 100). Set to `0` to disable all validation. Defaults to `100` (all responses).
- `failAction` - defines what to do when a response fails validation. Options are:
- `error` - return an Internal Server Error (500) error response. This is the default value.
- `log` - log the error but send the response.

- `cache` - if the route method is 'GET', the route can be configured to include caching directives in the response using the following options:
- `privacy` - determines the privacy flag included in client-side caching using the 'Cache-Control' header. Values are:
Expand Down Expand Up @@ -1377,6 +1376,8 @@ Each request object has the following properties:
- `host` - content of the HTTP 'Host' header.
- `method` - the request method in lower case (e.g. `'get'`, `'post'`).
- `mime` - the parsed content-type header. Only available when payload parsing enabled and no payload error occurred.
- `orig` - an object containing the values of `params`, `query`, and `payload` before any validation modifications made. Only set when
input validation is performed.
- `params` - an object where each key is a path parameter name with matching value as described in [Path parameters](#path-parameters).
- `path` - the request URI's path component.
- `payload` - the request payload based on the route `payload.output` and `payload.parse` settings.
Expand Down
27 changes: 10 additions & 17 deletions examples/validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,6 @@ var Joi = require('joi');
var internals = {};


// Type shortcuts

var S = Joi.string;
var N = Joi.number;
var A = Joi.array;


internals.get = function (request, reply) {

reply('Success!\n');
Expand All @@ -33,19 +26,19 @@ internals.main = function () {
var server = new Hapi.Server(8000);

server.route([
{ method: 'GET', path: '/', config: { handler: internals.get, validate: { query: { username: S() } } } },
{ method: 'GET', path: '/admin', config: { handler: internals.get, validate: { query: { username: S().required().with('password'), password: S() } } } },
{ method: 'GET', path: '/users', config: { handler: internals.get, validate: { query: { email: S().email().required().min(18) } } } },
{ method: 'GET', path: '/config', config: { handler: internals.get, validate: { query: { choices: A().required() } } } },
{ method: 'GET', path: '/test', config: { handler: internals.get, validate: { query: { num: N().min(0) } } } },
{ method: 'GET', path: '/test2', config: { handler: internals.get, validate: { query: { p1: S().required().rename('itemId') } } } },
{ method: 'GET', path: '/simple', config: { handler: internals.get, validate: { query: { input: S().min(3) } } } }
{ method: 'GET', path: '/', config: { handler: internals.get, validate: { query: { username: Joi.string() } } } },
{ method: 'GET', path: '/admin', config: { handler: internals.get, validate: { query: Joi.object({ username: Joi.string().required(), password: Joi.string() }).and('username', 'password') } } },
{ method: 'GET', path: '/users', config: { handler: internals.get, validate: { query: { email: Joi.string().email().required().min(18) } } } },
{ method: 'GET', path: '/config', config: { handler: internals.get, validate: { query: { choices: Joi.array().required() } } } },
{ method: 'GET', path: '/test', config: { handler: internals.get, validate: { query: { num: Joi.number().min(0) } } } },
{ method: 'GET', path: '/test2', config: { handler: internals.get, validate: { query: Joi.object({ p1: Joi.string().required() }).rename('p1', 'itemId') } } },
{ method: 'GET', path: '/simple', config: { handler: internals.get, validate: { query: { input: Joi.string().min(3) } } } }
]);

var schema = {
title: S(),
status: S().valid('open', 'pending', 'close'),
participants: A().includes(S(), N())
title: Joi.string(),
status: Joi.string().valid('open', 'pending', 'close'),
participants: Joi.array().includes(Joi.string(), Joi.number())
};

server.route({ method: 'POST', path: '/users/{id}', config: { handler: internals.payload, validate: { query: {}, payload: schema } } });
Expand Down
4 changes: 1 addition & 3 deletions lib/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@ exports.server = {

// Validation

validation: { // Joi validation options
modify: true
},
validation: {}, // Joi validation options

// JSON

Expand Down
3 changes: 2 additions & 1 deletion lib/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ exports = module.exports = internals.Request = function (server, req, res, optio
this.auth.credentials = options.credentials;
}

// Defined elsewhere:
// Assigned elsewhere:

this.orig = {};
this.params = {};
this.payload = null;
this.state = null;
Expand Down
79 changes: 39 additions & 40 deletions lib/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,22 +49,39 @@ exports = module.exports = internals.Route = function (options, server, env) {
// Validation

this.settings.validate = this.settings.validate || {};
var payloadSupported = (this.method !== 'get' && this.method !== 'head');

var validation = this.settings.validate;
['payload', 'query', 'path'].forEach(function (type) {
['payload', 'query', 'params'].forEach(function (type) {

if (validation[type] &&
typeof validation[type] !== 'function') {
// null, undefined, true - anything allowed
// false - nothing allowed
// {...} - ... allowed

validation[type] = Joi.compile(validation[type]);
}
var rule = validation[type];
validation[type] = (rule === false ? Joi.object({})
: typeof rule === 'function' ? rule
: !rule || rule === true ? null // false tested earlier
: Joi.compile(rule));
});

if (this.settings.response) {
var rule = this.settings.response.schema;
if (rule === true ||
this.settings.response.sample === 0) {

this.settings.response = null;
}
else {
this.settings.response.schema = (rule === false ? Joi.object({})
: typeof rule === 'function' ? rule
: Joi.compile(rule));
}
}

// Payload parsing

Hoek.assert(!this.settings.payload || payloadSupported, 'Cannot set payload settings on HEAD or GET request:', options.path);
if (payloadSupported) {
if (this.method !== 'get' &&
this.method !== 'head') {

this.settings.payload = this.settings.payload || {};
var isProxy = (typeof this.settings.handler === 'object' && !!this.settings.handler.proxy);
this.settings.payload.output = this.settings.payload.output || (isProxy ? 'stream' : 'data');
Expand All @@ -76,8 +93,11 @@ exports = module.exports = internals.Route = function (options, server, env) {
this.settings.payload.allow = [].concat(this.settings.payload.allow);
}
}
else {
Hoek.assert(!this.settings.payload, 'Cannot set payload settings on HEAD or GET request:', options.path);
Hoek.assert(!this.settings.validate.payload, 'Cannot validate HEAD or GET requests:', options.path);
}

Hoek.assert(!this.settings.validate.payload || payloadSupported, 'Cannot validate HEAD or GET requests:', options.path);
Hoek.assert(!this.settings.validate.payload || this.settings.payload.parse, 'Route payload must be set to \'parse\' when payload validation enabled:', options.path);
Hoek.assert(!this.settings.jsonp || typeof this.settings.jsonp === 'string', 'Bad route JSONP parameter name:', options.path);

Expand Down Expand Up @@ -105,27 +125,8 @@ exports = module.exports = internals.Route = function (options, server, env) {

internals.Route.prototype.lifecycle = function () {

var self = this;

var cycle = [];

// Lifecycle flags

var parsePayload = (this.method !== 'get' && this.method !== 'head');
var authenticate = (this.settings.auth !== false); // Anything other than 'false' can still require authentication

var validate = function (type, root) {

// null, undefined, true - anything allowed
// false, {} - nothing allowed
// {...} - ... allowed

root = root || self.settings.validate;
return root[type] !== null &&
root[type] !== undefined &&
root[type] !== true;
};

// 'onRequest'

if (this.server.settings.state.cookies.parse) {
Expand All @@ -134,11 +135,14 @@ internals.Route.prototype.lifecycle = function () {

cycle.push('onPreAuth');

var authenticate = (this.settings.auth !== false); // Anything other than 'false' can still require authentication
if (authenticate) {
cycle.push(Auth.authenticate);
}

if (parsePayload) {
if (this.method !== 'get' &&
this.method !== 'head') {

cycle.push(Payload.read);
if (authenticate) {
cycle.push(Auth.payload);
Expand All @@ -147,32 +151,27 @@ internals.Route.prototype.lifecycle = function () {

cycle.push('onPostAuth');

if (validate('path')) {
cycle.push(Validation.path);
if (this.settings.validate.params) {
cycle.push(Validation.params);
}

if (this.settings.jsonp) {
cycle.push(Request.parseJSONP);
}

if (validate('query')) {
if (this.settings.validate.query) {
cycle.push(Validation.query);
}

if (parsePayload &&
validate('payload')) {

if (this.settings.validate.payload) {
cycle.push(Validation.payload);
}

cycle.push('onPreHandler');
cycle.push(Handler.execute); // Must not call next() with an Error
cycle.push('onPostHandler'); // An error from here on will override any result set in handler()

if (validate('response', this.settings) &&
this.settings.response.sample !== 0 &&
this.settings.response.sample !== false) {

if (this.settings.response) {
cycle.push(Validation.response);
}

Expand Down
Loading