Skip to content

5.0.0 Release Notes #1645

@hueniverse

Description

@hueniverse

Summary

hapi v5.0 contains a new version of the joi validation module (v4.0). This version contains significant changes in how joi operates as well as some powerful new features.

  • Upgrade time: low - a few hours for most users
  • Complexity: low - all the changes are easy to locate and modify
  • Risk: low to moderate - use of response validation in prior versions may rely on an existing side-effect bug

Breaking Changes

  • The full list of joi breaking changes included is listed here.
  • Missing optional ('{param?} or {param*}) path parameters (request.params) are no longer assigned the empty string.
  • Route validation config config.validate.path is renamed config.validate.params.
  • Response validation no longer modifies the response when type casting is performed (e.g. converting a string to number). Validation will still pass but the data will remain unchanged.
  • Response validation config config.response no longer accepts true or false.
  • Server config validation no longer supports the modify flag (was true by default).

New Features

  • Pre-validation values (before any defaults are applied or type casting) are now available under request.orig for modified values.
  • Request headers validation rules can be specified using config.validate.headers.
  • Cross input validation (e.g. validating query parameters based on the value of path parameters).

New feature highlights in joi v4.0:

  • References - rules can use the value of other keys:
var rule = {
    a: Joi.ref('b'),  // a must be equal to b
    b: Joi.number(),
    c: Joi.default(Joi.ref('b')),  // c defaults to b if not present
    d: Joi.valid(Joi.ref('b'), Joi.ref('c')),  // d must be equal to b or c
    e: {
        f: Joi.string()
    },
    g: Joi.ref('e.f')  // g references a child of e
};
  • Conditions - write rules where the value of one variable is tested based on the value of another:
var rule = {
    a: Joi.any(),
    // If a is a number, b must be 'x' - otherwise 'y'
    b: Joi.when('a', {
        is: Joi.number(),
        then: 'x',
        otherwise: 'y'
    })
};
  • Context - hapi now sets a validation context object with the values of the other inputs (when headers are being validated, the values of params, query, and payload are exposed). The context values can be used in references to create rules that cross input sources:
var server = new Hapi.Server();
server.route({
    method: 'GET',
    path: '/{user?}',
    handler: function (request, reply) { reply('ok'); },
    config: {
        validate: {
            query: {
                // When the request path includes a user, enable the verbose query
                // parameter, otherwise forbid it.
                verbose: Joi.boolean()
                    .when('$params.user', {
                        is: Joi.exist(),
                        otherwise: Joi.forbidden()
                    })
            }
        }
    }
});
  • Custom Types - joi v3.0 added support for immutable object which means that adding a rule to an existing joi object returns a new object and leaves the source unchanged. This allows defining types and reusing them (before, adding a rule changes all other references to the original object). In v4.0, joi adds support for object.keys() and alternatives.try() which allow adding additional keys or alternatives.
// Defining new custom types:
var fullName = Joi.string().min(3).max(255).regex(/\w+ \w+/);
var requiredFullName = fullName.required();

// Adding keys to object:
var name = Joi.object({
    first: Joi.string().require(),
    last: Joi.string()
});

var extName = name.keys({
    middle: Joi.string()
});

Migration Checklist

  • Look for usage of optional path parameters (search for ?} and *} in route paths) and confirm your code can handle request.params[name] to be null instead of ''.
  • Look for any route with config.validate.path and rename path to params.
  • Look for any route using response validation via config.response:
    • if config.response is set to true or false, change it to { schema: true } or { schema: false }.
    • if config.response.schema is set to a joi rule, ensure that you are not relying on any type conversions (e.g. the handler is setting { a: '2' } and the validation changes a to 2). If you rely in this behavior, remove the validation rule and instead apply it using the 'onPreResponse' extension point.
  • Look for server configuration key validation and if you override validation.modify to false in your code, change your handlers and extensions to use request.orig instead (e.g. request.orig.params).

joi changes:

  • The modify option is no longer available. The original values are not modified but instead a modified copy is returned. Note that the copy is not a full deep clone and may share nodes with the original value to minimize impact on performance. Within hapi, the original values are now available under request.orig. When using joi directly, the modified value is return as the second callback argument.
  • rename()
    • moved to object.rename() - key can no longer rename itself, only the object can rename its children.
    • the move option is now true by default (old name is removed). To keep the old name alongside the new name, set alias to true.
  • with(), without(), xor(), andor()` can no longer be set on individual keys, only on the parent object:
    • { a: Joi.any().with('b') } is now Joi.object({ a: Joi.any() }).with('a', 'b') }) where the first argument to with() is the subject.
    • { a: Joi.any().without('b') } is now Joi.object({ a: Joi.any() }).without('a', 'b') }) where the first argument to with() is the subject.
    • { a: Joi.any().xor('b') } is now Joi.object({ a: Joi.any() }).xor('a', 'b') }) where xor() argument order does not matter.
    • { a: Joi.any().or('b') } is now Joi.object({ a: Joi.any() }).or('a', 'b') }) where or() argument order does not matter.
  • Validation error object no longer support the simple() method. The error.message is automatically set to the output of simple() and does not change. The method annotated() is replaced by annotate() which does not modify the error and returns the annotated error string.
  • Alternatives using the [] notation now allow undefined by default (defaulted to required before). To preserve the previous behavior, replace [Joi.string(), Joi.number()] with Joi.alternatives([Joi.string(), Joi.number()]).required().
  • The language file templates have changed significantly and any custom language files must be reworked using the new formats.

Metadata

Metadata

Assignees

Labels

breaking changesChange that can breaking existing coderelease notesMajor release documentation

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions