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

v16.0.0 Release Notes #2037

Closed
hueniverse opened this issue Aug 10, 2019 · 0 comments
Closed

v16.0.0 Release Notes #2037

hueniverse opened this issue Aug 10, 2019 · 0 comments
Assignees
Labels
breaking changes Change that can breaking existing code release notes Major release documentation
Milestone

Comments

@hueniverse
Copy link
Contributor

hueniverse commented Aug 10, 2019

Summary

joi v16.0.0 is a massive release with many changes to every corner of the module and its extensions. The entire code base has been rewritten with many new features and core components. Instead of an internal architecture and extension framework, every core type is now using the same extension system with a full extension API available for new or expanded types. Since joi schemas are usually well-defined and well-contained, it is recommended to review them against the updated reference to ensure they still perform the expected validation.

  • Upgrade time: high - changes required to most existing schemas
  • Complexity: high - requires following a long list of changes and verifying their impact
  • Risk: medium - large number of changes but most self-validate and will error on incorrect usage
  • Dependencies: high - existing extensions will no longer work as-is

Breaking Changes

Bug fixes

Updated dependencies

  • hoek from v6.x to v8.x

New Features

Official browser support

joi now comes with a pre-built minified version for client-side development. The package size has been reduced from over 500K to around 150K (uncompressed). Future releases will allow even smaller distributions by being able to pick just the needed functionality. The browser build does not include TLD email validation, Joi.binary(), the describe/build functionality, or debug/tracing.

Manipulate referenced values in expressions (#667, #1917, #1930, #1935, #2027)

Require b to be equal to a plus one:

Joi.object({
    a: Joi.number(),
    b: Joi.expression('{a + 1}')
});

Require at least one of a, b, or c to be true:

Joi.object({
    a: Joi.boolean(),
    b: Joi.boolean(),
    c: Joi.boolean()
})
    .assert(Joi.expression('{a || b || c}'), true, 'at least one key must be true');

When annual is true, require to to be less than one year away from from:

Joi.object({
    annual: Joi.boolean().required(),
    from: Joi.date().required(),
    to: Joi.date().required()
        .when('annual', { is: true, then: Joi.date().max(Joi.expression('{number(from) + 364 * day}')) })
});

Validate domain names (#925)

Match domain names under the .com and .net TLDs:

Joi.string().domain({ tlds: { allow: ['com', 'net'] } });

Return typed validation errors (#1244)

Contributed by Aaron J. Lang.

joi will return or throw ValidationError instead of plain Error.

Support multiple languages (#1866)

const messages = {

    root: 'thing',
    'number.min': '{#label} is not big enough',

    english: {
        root: 'value',
        'number.min': '{#label} too small'
    },
    latin: {
        root: 'valorem',
        'number.min': Joi.template('{%label} angustus', { prefix: { local: '%' } })
    }
};

const schema = Joi.number().min(10);

schema.validate(1, { messages, errors: { language: 'english' } }).error;    // 'value too small'
schema.validate(1, { messages, errors: { language: 'latin' } }).error;      // 'valorem angustus'
schema.validate(1, { messages, errors: { language: 'french' } }).error;     // 'thing is not big enough'

Override rule messages (#1264, #1877)

Proof of concept provided by David Luzar.

Modify the messages of individual rules:

Joi.number()
    .min(10).message('way too small')
    .max(100).message('way too big');

Modify the messages of multiple rules:

Joi.number()
    .ruleset
        .min(10).max(100)
    .message('value must be between 10 and 100');

Modify specific languages:

const schema = Joi.number()
    .min(10)
    .message({
        english: {
            'number.min': '{#label} too small'
        },
        latin: {
            root: 'valorem',
            'number.min': Joi.x('{@label} angustus', { prefix: { local: '@' } })
        }
    });

schema.validate(1, { errors: { language: 'english' } });    // { error: 'value too small' }
schema.validate(1, { errors: { language: 'latin' } });      // { error: 'valorem angustus' }

object.rename() regex capture groups (#1403)

Rename all keys that contain only digits to start with 'num_':

Joi.object()
    .rename(/^(\d+)$/, Joi.expression('num_{#1}'));

Specify requirements for object key pattern matches (#1457, #2038)

Require an object to contain a specific number of keys starting with 'custom_' based
on the value of another key count:

Joi.object({
    count: Joi.number().required()
})
    .pattern(/^custom_\w+$/, Joi.number(), { matches: Joi.array().length(Joi.ref('count')) });

Require all keys starting with 'x' to be at least 1 and all keys ending in 'z' to be at least 10:

Joi.object()
    .pattern(/^x/, Joi.number().min(1), { fallthrough: true })
    .pattern(/z$/, Joi.number().max(10));

Require all keys starting with 'x' to be at least 1 and for all other keys ending in 'z' (but not starting with 'x') to be at least 10:

Joi.object()
    .pattern(/^x/, Joi.number().min(1))
    .pattern(/z$/, Joi.number().max(10));

Replace invalid values with a failover value (#1535)

If a number is bigger than 10, set it to 5:

Joi.number().max(10).failover(5);

Validate ISO duration string (#1612)

Contributed by Alex Petty.

Validates ISO 8601 duration strings:

Joi.string().isoDuration();     // 'P3Y6M4DT12H30M5S'

Advance references (#1569, #1650, #1695, #1702, #1807, #1865, #1979, #1993)

If an array size is 2, all the items must be 2, otherwise 7:

Joi.array()
    .when('.length', {
        is: 2,
        then: Joi.array().items(2),
        otherwise: Joi.array().items(7)
    });

Require key c to equal its uncle a:

Joi.object({
    a: Joi.any(),
    b: {
        c: Joi.ref('...a')
    }
});

Require key c to equal the absolute value a key (note that the root value changes when this schema is used as a sub-schema):

Joi.object({
    a: Joi.any(),
    b: {
        c: Joi.ref('/a')
    }
});

Reference keys with . characters:

Joi.object({
    'a.b': Joi.any(),
    b: Joi.ref('a.b', { separator: false })
});

Require the second array item to be equal or greater than the first item:

Joi.array()
    .ordered(Joi.number(), Joi.number().min(Joi.ref('0')))

Require a to be two times the value of b by manipulating the resolved value of the reference using a function:

Joi.object({
    a: Joi.ref('b', { adjust: (value) => 2 * value }),
    b: Joi.number()
});

Require a to be 10 when b is 5, and 11 when b is 6:

Joi.object({
    a: Joi.number(),
    b: Joi.ref('a', { map: [[5, 10], [6, 11]] })
});

Retain object non-enumerable properties (#1696)

const obj = { a: 100 };
Object.defineProperty(obj, 'test', { value: 42, enumerable: false });

schema.validate(obj, { nonEnumerables: true });

Validate URI domain component (#1705, #1965)

Only allow URIs with .com domains and at least 3 segments (e.g. 'www.example.com'):

Joi.string()
    .uri({ domain: { minDomainSegments: 3, tlds: { allow: ['com'] } } });

Support validation options in assert() and attempt() (#1722)

Contributed by Wes Tyler.

Return all errors in assert message:

Joi.assert(-1, Joi.number().min(2).positive(), { abortEarly: false });

Validation value and result caching (#1727)

Proof of concept contributed by Michael McLaughlin.

Cache strings to optimize complex regex lookup for repeated values:

Joi.string().pattern(/^some|complex|regex$/).cache();

Support array rules validation when items rule fails (#1730)

Return all errors, including items and unique violations:

Joi.array()
    .items(Joi.object({
        test: Joi.string(),
        hello: Joi.string()
    }))
    .unique('test')
    .preferences({abortEarly: false});

Set error language based on value in the validated object (#1737, #1907)

Return errors in the language provided within the value:

Joi.object({
    lang: Joi.string().valid('english', 'french'),
    x: Joi.number()
})
    .preferences({
        errors: {
            language: Joi.ref('/lang')
        }
    });

Validate URI-safe base64 strings (#1746)

Validate a URI-safe base64 string with optional padding:

Joi.string()
    .base64({ urlSafe: true, paddingRequired: false });

Customize dates string format in error messages (#1762, 1924)

Set date error string format to date only (no time):

Joi.date()
    .less('1-1-1970 UTC')
    .preferences({ dateFormat: 'date' });

Validate multiple email addresses on a single line (#1812)

Contributed by Dolphin.

Joi.string().email({ multiple: true });

Enahnced when() options (#1860, #2078, #2079, #2099)

Switch statement:

Joi.object({
    a: Joi.number(),
    b: Joi.number()
        .when('a', {
            switch: [
                { is: 1, then: Joi.number().min(1) },
                { is: 2, then: Joi.number().max(10) }
            ],
            otherwise: Joi.forbidden()
        })
});

Default is to truthy value validation:

Joi.object({
    a: Joi.any(),
    b: Joi.number()
        .when('a', { then: 5 });

Use not for reverse condition:

Joi.object({
    a: Joi.number(),
    b: Joi.number()
        .when('a', { not: 1, then: 5 });

Break processing conditions:

When strings begin with 'a' require them to be 'axxx', except for them they are 3 characters long which must always be 'abc':

Joi.string()
    .when('.length', { is: 3, then: 'abc', break: true })
    .when('.0', { is: 'a', then: 'axxx' });

Bidirectional describe() <-> build() (#1867, #1986, #1987)

Generates a description that is 100% representative of the schema and can be converted back into a schema:

const schema = Joi.number().min(10).required();
const desc = schema.describe();

/*
{
    type: 'number',
    flags: {
        presence: 'required'
    },
    rules: [
        {
            name: 'min',
            args: {
                limit: 10
            }
        }
    ]
}
*/

const built = Joi.build(desc);      // Same as schema

Add object.schema() type support (#1873)

Require the value to be a joi number schema:

Joi.object().schema('number');

Rules sets (#1877)

Change the error message of a group of rules:

Joi.number()
    .ruleset
        .min(10)
        .max(100)
    .message('value must be a number between 10 and 100');

Warnings (#1877, #1957, #1958)

Return a deprecation warning on unused key y:

(Note that typically, the message definition for deprecate.error will be provided globally.)

const schema = Joi.object({
    x: Joi.number(),
    y: Joi.number().
        .warning('deprecate.error', { reason: 'it is no longer supported' })
        .message({ 'deprecate.error': '{#label} is deprecated because {#reason}' });

const { value, error, warning } = schema.validate({ x: 1, y: 2 });
// value -> { x: 1, y: 2 };
// error -> undefined
// warning -> { message: 'y is deprecated because it is no longer supported', details: [...] }

Convert a set of rules to generate warnings instead of errors:

Joi.string()
    .ruleset
        .length(10)
        .pattern(/^\d+$/)
    .rule({
        warn: true,
        message: 'really wanted a string of 10 digits but oh well'
    });

Allow multiple instances of unique rules (#1877, #1881)

Keep both checks for minimum value and error based on the first rule to break:

Joi.number()
    .min(10).rule({ keep: true, message: 'value must be much bigger' })
    .min(100).message('value still not big enough');

Sort array items (#1885)

Sorts the array items in ascending order:

Joi.array()
    .items(Joi.number())
    .sort({ order: 'ascending' });

Verify (but not modify) the array of object is sorted in descending by the n object property:

Joi.array()
    .items(Joi.object({ n: Joi.number() }))
    .sort({ order: 'descending', by: 'n' })
    .strict();

Value casting (#1914, #1922, #1925, #1929)

Cast the validated number to a string:

Joi.number().cast('string');

Return the validated array as Set:

Joi.array().cast('set');

Return the validated object as Map:

Joi.object().cast('map');

Return 1 for true and 0 for false:

Joi.boolean().cast('number');

Extend schema with constructor arguments (#1932)

Create a new special type that takes a minimum limit in its type constructor:

const custom = Joi.extend({
    type: 'special',
    base: Joi.string(),
    args(schema, limit) {

        return schema.min(limit);
    }
});

const schema = custom.special(2);

Support asynchronious operations (post-validation) (#1937)

Perform a database user id lookup on valid strings:

const lookup = async (id) => {

    const user = await db.get('user', id);
    if (!user) {
        throw new Error('Invalid user id');
    }
};

const schema = Joi.string().external(lookup);
await schema.validateAsync('1234abcd');

Extract any sub-schema with implicit and explicit identifiers (#1950, #1952)

Extract a deeply nested schema inside array items definition:

const schema = Joi.array()
    .items(
        Joi.object({
            c: Joi.object({
                d: Joi.number()
            })
        })
            .id('item')
    );

schema.extract('item.c.d');

Fork schemas (#1950, #1952)

Change the deeply nested d schema to require a minimum value of 10:

const schema = Joi.object({
    a: Joi.number(),
    b: Joi.object({
        c: Joi.object({
            d: Joi.number()
        })
    })
});

const modified = schema.fork('b.c.d', (schema) => schema.min(10));

// Modified is equal to:

Joi.object({
    a: Joi.number(),
    b: Joi.object({
        c: Joi.object({
            d: Joi.number().min(10)
        })
    })
});

Schema alterations (#1954)

Feature designed by Nicolas Morel.

Define a single payload schema but tailor it to handle PUT and POST requests differently:

const payload = Joi.object({
    id: Joi.string()
        .alter({
            post: (schema) => schema.required(),
            put: (schema) => schema.forbidden()
        }),
    name: Joi.string(),
    email: Joi.string().email()
});

const postSchema = schema.tailor('post');
const putSchema = schema.tailor('put');

Schema links and recursive schemas (#1968, #1979, #1993, #2080, #2084, #2096)

Replaces Joi.lazy() by simply referencing the part of the schema to reuse, including the root.

Validate b using the same rules as a (a string or a number):

Joi.object({
    a: [Joi.string(), Joi.number()],
    b: Joi.link('a')
});

Validate a.b.c.d using the same rules as a.e.f:

Joi.object({
    a: Joi.object({
        b: Joi.object({
            c: Joi.object({
                d: Joi.link('#a.e.f')
            })
        }),
        e: Joi.object({
            f: [Joi.string(), Joi.number()]
        })
    })
});

Recursively validate persons and their friends and their friends, etc.:

const person = Joi.object({
    firstName: Joi.string().required(),
    lastName: Joi.string().required(),
    friends: Joi.array()
        .items(Joi.link('#person'))
})
  .id('person');

Validate a family where each member has recursive friends:

const person = Joi.object({
    firstName: Joi.string().required(),
    lastName: Joi.string().required(),
    friends: Joi.array()
        .items(Joi.link('#person'))
})
  .id('person');

const family = Joi.object({
    parents: Joi.array().items(person).min(1),
    siblings: Joi.array().items(person).min(1)
});

Validate using a shared schema (that is not used elsewhere in the schema chain):

const shared = Joi.number().id('shared');

const schema = Joi.object({
    a: [Joi.string(), Joi.link('#shared')],
    b: Joi.link('#type.a')
})
    .shared(shared)
    .id('type');

Validate b.c using the same rules as a (a string or a number):

Joi.object({
    a: [Joi.string(), Joi.number()],
    b: {
        c: Joi.link('...a')
    }
});

Validate recursive structure { name: 'a', keys: [{ name: 'b', keys: [{ name: 'c', keys: [{ name: 'd' }] }] }] }:

Joi.object({
    name: Joi.string().required(),
    keys: Joi.array()
        .items(Joi.link('...'))
});

Validate recursive root structure (root links are simpler but do not work when the schema is then reused inside another schema as the root reference will change to the new external schema):

Joi.object({
    name: Joi.string().required(),
    keys: Joi.array()
        .items(Joi.link('/'))
});

Custom synchronious functions (#2032)

Replace 2 with 3 and error on 4:

const method = (value, helpers) => {

    if (value === 2) {
        return 3;
    }

    if (value === 4) {
        return helpers.error('any.invalid');
    }

    return value;
};

Joi.number().custom(method, 'custom validation');

Include messages in other messages (#2033)

{
   'error.code': 'this failed because {msg("error.other")}',
   'error.other': 'some other reason'
};

Skip error rendering preference (#2035)

Return just the error code and improve performance when the text message is not needed:

Joi.number().min(10).validate(1, { errors: { render: false } }); // error -> 'number.min'

Match only one or all alternatives (#2051)

Allow numbers and strings that can be converted to numbers, but keep the original:

Joi.alternatives([
    Joi.number(),
    Joi.string()
])
    .mode('all');

Allow numbers and strings but not number strings:

Joi.alternatives([
    Joi.number(),
    Joi.string()
])
    .mode('one');

Debug log (#1959)

Return a step-by-step list of internal validation processing for debugging schemas:

const schema = Joi.object({
    a: {
        x: Joi.string()
            .lowercase()
            .pattern(/\d/)
            .max(20)
            .min(10)
    },

    b: Joi.number()
        .min(100),

    c: Joi.not('y')
});

const value = { a: { x: '12345678901234567890' }, b: 110, c: 'y' };
const { debug } = schema.validate(value, { debug: true });

//  debug -> [
//      { type: 'entry', path: [] },
//      { type: 'entry', path: ['a'] },
//      { type: 'entry', path: ['a', 'x'] },
//      { type: 'rule', name: 'case', result: 'pass', path: ['a', 'x'] },
//      { type: 'rule', name: 'pattern', result: 'pass', path: ['a', 'x'] },
//      { type: 'rule', name: 'max', result: 'pass', path: ['a', 'x'] },
//      { type: 'rule', name: 'min', result: 'pass', path: ['a', 'x'] },
//      { type: 'entry', path: ['b'] },
//      { type: 'rule', name: 'min', result: 'pass', path: ['b'] },
//      { type: 'entry', path: ['c'] },
//      { type: 'invalid', path: ['c'], value: 'y' }
//  ]

Allow / disallow lists override (#2076)

Replace the list of valid values:

const schema = Joi.valid(1, 2);

schema.valid(Joi.override, 3);  // Only allow 3

Clear valid list and only flag:

const schema = Joi.valid(1, 2);

schema.valid(Joi.override);     // All values allowed

Validate values against members of a referenced array (#2089)

Ensure defaultValue is one of allowedValues:

Joi.object({
    allowedValues: Joi.array().items(Joi.string()),
    defaultValue: Joi.in('allowedValues')
});

### Validate values against array items and object keys (#2092)

Ensure value is a member of another array:

```js
Joi.object({
    allowedValues: Joi.array(),
    defaultValue: Joi.in('allowedValues')
});

Ensure the value of b is present as a key in a object:

Joi.object({
    a: Joi.object(),
    b: Joi.in('b')
});

Migration Checklist

Due to the size of this release and the sheer amount of changes, both breaking and non-breaking, it would be impossible to generate a single migration guide that will cover every possible use case.

The list below is meant as a general guide to help with common use cases and provide clues as to how to approach upgrading to this release. It is highly recommended to review every single rule being used in your code to ensure it still does what you expect. Most problems will result in a helpful error message, but not all.

Core validation workflow

The basic validation flow has changed in a few way:

  • Type coercion (e.g. converting number to string) is only executed if the convert option is true (default value). This has been the case for built-in types but not for extensions.
  • Previously, if no valid value was found, basic type validation was still performed. This would still result in failed validation but the error might have been different due to basic type validation error.
  • When abortEarly is true and an object contains unknown keys, the first key will generate an error instead of iterating through all the unknown keys. This was the expected behavior but might result in different error returned.
  • When raw() is used in conjunction with references, the referenced value used was the raw value, not the validated value. This was changed to use the validated (and potentially coerced) value in references.
  • Mixing different versions of joi modules is no longer allowed.

Checklist:

  • Make sure any schema using raw() with references to the raw key takes into account the validated value instead of the raw value.
  • If you rely on specific error codes, make sure the changes do not result in a different error code than expected.

Errors and messages

TL;DR - If you do anything with errors other than pass the message along, you don't have to do much. However, if you rely on message format, error codes, or provide custom messages, you will need to revisit as it is unlikely to work as-is.

Error messages use a new template parser:

  • References in error messages now have access to the global context as well as the value being validated. To access errors-specific variable (e.g. label), they must be prefixed with #. For example {{label}} is now {{#label}}).
  • The {{!var}} syntax for unescaped variables has been replaced with {var}.
  • Errors paths only include the edge label. This means the #label variable will be set to the label of the failing schema instead of the full path.
  • The label prefix is not longer added by default. The !! prefix is no longer supported and will result in a literal !! included in the error message. To disable labels in message templates that include it, use the new errors.label preference.

Errors content changed throughout by applying:

  • A default date format using ISO date string. Use dateFormat preference set to string to retain the previous default.
  • References string representation changed to:
    • global context: ref:global:{path}
    • local context: ref:local:{path}
    • value: ref:{path}
    • root: ref:root:{path}

Other error changes:

  • Error objects no longer capture the call stack by default to improve performance and since validation errors are usually coming from a known local and the call stack is unused. To generate a call stack, use the errors.stack preference set to true.
  • When nested keys or array items fail validation, the new errors will use the full path to the failing schema instead of nesting a chain of errors. This removes the special nested reason local error context.
  • Change object peer dependencies (e.g. object.and()) error path to always use object root instead of including the failing key in the path. Dependency errors are about the object itself, not the individual keys.

The language preference was replaced by a new messages preference using a new structure:

{
    // Default messages

    root: 'value',
    'number.min': '{#label} too small'      // Error codes are simple strings (the comma separator is just a convention)

    // Language-specific messages

    latin: {
        root: 'valorem',
        'number.min': '{#label} angustus'
    }
}

New errors codes:

  • alternatives.all
  • alternatives.match
  • alternatives.types
  • alternatives.one
  • any.custom
  • any.failover
  • any.ref
  • array.sort
  • array.sort.mismatching
  • array.sort.unsupported
  • date.format
  • number.infinity
  • object.pattern.match
  • object.refType
  • string.domain
  • string.empty
  • string.isoDuration

Changed errors codes:

  • root - does not support templates, must be a plain string.
  • alternatives.base - changed to alternatives.any.
  • any.allowOnly - changed to any.only
  • any.empty - replaced by string.empty
  • array.excludesSingle - replaced by array.excludes
  • array.includesSingle - replaced by array.includes
  • array.ref - replaced by any.ref
  • date.ref - replaced by any.ref
  • date.isoDate - replaced by 'date.format'
  • date.timestamp.javascript - replaced by 'date.format'
  • date.timestamp.unix - replaced by 'date.format'
  • function.base - replaced by object.base
  • function.ref - replaced by any.ref
  • number.ref - replaced by any.ref
  • object.allowUnknown - replaced by object.unknown
  • object.assert - #ref changed to #subject
  • object.rename.multiple - added #pattern
  • object.rename.override - added #pattern
  • object.rename.regex.multiple - replaced by object.rename.multiple
  • object.rename.regex.override - replaced by object.rename.regex.override
  • object.schema - added #type
  • object.type - replaced by object.instance
  • string.email - added #invalids
  • string.ref - replaced by any.ref
  • string.regex.base - replaced by string.pattern.base
  • string.regex.name - replaced by string.pattern.name
  • string.regex.invert.base - replaced by string.pattern.invert.base
  • string.regex.invert.name - replaced by string.pattern.invert.name

Unsupported error codes:

  • key - no longer supported.
  • messages.wrapArrays - moved to errors.wrapArrays preference.

Schema descriptions

A key goal of joi v16 was to support a fully bidirectional describe and build format. The new format has some overlap with the previous one but the change list is far too to include here. If you use the output of describe() programmatically, please consult the source code for how schemas are described. Full documentation is upcoming.

Please note that the description manifest is not designed to be wire-friendly due to the fact that many methods support functions and symbols which do not have a JSON representation. Converting functions to strings is not practical or secure. If you plan on using the new manifest system to send text schemas across the network, make sure to only use wire-safe features which do not include functions, symbols, or other unsupported primitives.

Extensions

The entire extension system has been replaced with a new framework that is now at the core of the entire type system. Every built-in type is just an extension of the base type. Everything the built-in types can do is now available to extensions. Migrating existing extensions is outside the scope of this guide. Please consult the documentation when it becomes available. However, the best resource is the joi built-in types code base.

Array and object string coercion

Joi.object() and Joi.array() no longer support string coercion. This means that a string value of '[1,2,3]' will no longer be automatically converted to a valid array when convert is true, and similarly for objects. This could be a problem if you support object or array strings in HTTP request query strings.

Use extensions to restore this functionality, for example:

const Bourne = require('@hapi/bourne');
const Joi = require('@hapi/joi');

const custom = Joi.extend([
    {
        type: 'object',
        base: Joi.object(),
        coerce: {
            from: 'string',
            method(value, helpers) {

                if (value[0] !== '{' &&
                    !/^\s*\{/.test(value)) {

                    return;
                }

                try {
                    return { value: Bourne.parse(value) };
                }
                catch (ignoreErr) { }
            }
        }
    },
    {
        type: 'array',
        base: Joi.array(),
        coerce: {
            from: 'string',
            method(value, helpers) {

                if (typeof value !== 'string' ||
                    value[0] !== '[' && !/^\s*\[/.test(value)) {

                    return;
                }

                try {
                    return { value: Bourne.parse(value) };
                }
                catch (ignoreErr) { }
            }
        }
    }
]);

Schema compilation

When using Joi.compile() explicitly or passing non-schema keys and performing implicit compile (e.g. Joi.object({ a: 'x' })), the schema compilation step will no longer compile literals into a type matching strings, booleans, or numbers. Instead, it will return a Joi.any() type with exact literal set as valid(). This means Joi.object({ a: 5 }) will no longer match { a: '5' }. In addition, compiling an array of literal values will compile into a single any type with valid() instead of alternatives type.

When compiling literal values into valid(), the override flag will be added which will make the value override anything it is being concatenated to (e.g. when used in any.when()). To avoid the default override behavior, pass an explicit Joi.valid() schema in stead of a literal value. For example, 'x' is compiled into Joi.valid(Joi.override, 'x'), but Joi.valid('x') is left unchanged. The only exception is for values passed to the Joi.empty() function which are not set to override.

To keep the previous behavior pass the exact types you want instead of relying on implicit compiling.

API changes

Root module methods

  • Joi.allow()

    • See any.allow() changes.
  • Joi.bind()

    • Replaced with new method Joi.types().
  • Joi.concat()

    • No longer supported, use Joi.any().concat() instead (and consult its own changes).
  • Joi.compile()

    • When compiling simple types (string, number, boolean, null), Joi.any() is returned instead of the matching type. This means Joi.compile(5) will no longer match '5'. To keep the previous behavior pass explicit schemas. See Schema compilation section above.
  • Joi.createError()

    • No longer supported. Use new any.$_createError() instead.
  • Joi.createOverrideError()

    • No longer supported.
  • Joi.default()

    • No longer supported, use Joi.any().default() instead (and consult its own changes).
  • Joi.describe()

    • No longer supported, use schema.describe() directly on the compiled schema.
  • Joi.description()

    • No longer supported, use Joi.any().description() instead.
  • Joi.disallow()

    • See any.disallow() changes.
  • Joi.empty()

    • No longer supported, use Joi.any().empty() instead.
  • Joi.equal()

    • See any.equal() changes.
  • Joi.error()

    • No longer supported, use Joi.any().error() instead (and consult its own changes).
  • Joi.example()

    • No longer supported, use Joi.any().example() instead (and consult its own changes).
  • Joi.func()

    • Renamed to Joi.function().
  • Joi.invalid()

    • See any.invalid() changes.
  • Joi.label()

    • No longer supported, use Joi.any().label() instead.
  • Joi.lazy()

    • No longer supported, use new Joi.link() instead.
  • Joi.meta()

    • No longer supported, use Joi.any().meta() instead.
  • Joi.not()

    • See any.not() changes.
  • Joi.notes()

    • No longer supported, use new Joi.any().note() instead.
  • Joi.only()

    • No longer supported, use Joi.valid() instead (and consult its own changes).
  • Joi.options()

    • Renamed to Joi.preferences().
    • See any.options() changes.
  • Joi.raw()

    • No longer supported, use Joi.any().raw() instead.
  • Joi.reach()

    • No longer supported, use new schema.extract() directly on the compiled schema.
  • Joi.ref()

    • Returns a new reference implementation that is an object instead of function.
    • No longer supports Hoek.reach() options in options.
  • Joi.schemaType

    • No longer supported, use Joi.any().type instead.
  • Joi.strict()

    • No longer supported, use Joi.any().strict() instead.
  • Joi.strip()

    • No longer supported, use Joi.any().strip() instead.
  • Joi.tags()

    • No longer supported, use new Joi.any().tag() instead.
  • Joi.unit()

    • No longer supported, use Joi.any().unit() instead.
  • Joi.valid()

    • See any.valid() changes.
  • Joi.validate()

    • No longer supported. Use schema.validate() directly on the compiled schema.
    • See any.validate() changes.
  • Joi.when()

    • See any.when() changes.

any() methods (applies to all types)

  • any.allow()

    • No longer accepts array arguments. Must pass each value as a separate argument.
    • Values are checked only after type coercion (e.g. convert string to number).
  • any.applyFunctionToChildren()

    • No longer supported.
  • any.concat()

    • Unique tests (e.g. min(), max()) are no longer retained and are replaced by the source concatenated schema.
  • any.createError()

    • No longer supported. Use new any.$_createError() instead.
  • any.createOverrideError()

    • No longer supported.
  • any.default()

    • Remove description argument.
    • No longer supports undefined as value.
    • Literal function examples require the new literal option set to true.
  • any.disallow()

    • see any.invalid()
  • any.equal()

    • see any.valid()
  • any.error()

    • No longer supports options.
    • Function called without binding to this.
    • Only supports an Error instance or a function that returns a single Error instance.
    • No longer supports functions that return a string or an array or errors.
  • any.example()

    • Only supports passing a single example as the first argument.
    • Appends examples by default.
    • Use new second argument options for to override existing examples.
    • Examples are no longer validated against the schema.
  • any.invalid()

    • No longer accepts array arguments. Must pass each value as a separate argument.
    • Values are checked only after type coercion (e.g. convert string to number).
    • Throws error when all the previously valid values are no longer valid (dead end schema).
    • References that resolve into an array are no longer used as possible values to match but a single literal array value. To match against any of the array values, use the new Joi.in() method.
    • Object and array values are now compared using the Hoek.deepEqual() method instead of shallow comparison.
  • any.isJoi

    • No longer supported. Use Joi.isSchema() instead.
  • any.not()

    • see any.invalid()
  • any.notes()

    • Renamed to any.note().
    • Requires each note to be passed as a separate argument.
  • any.only()

    • No longer used as an alias of valid() and must be replaced with any.valid() (and consult its own changes).
    • New usage converts any any.allow() rules to behave as if any.valid() was used.
  • any.options()

    • Renamed to preferences().
    • See list of options changes listed under any.validate().
  • Joi.schemaType

    • No longer supported, use any.type instead.
  • any.tags()

    • Renamed to any.tag().
    • Requires each tag to be passed as a separate argument.
  • any.valid()

    • No longer accepts array arguments. Must pass each value as a separate argument.
    • Values are checked only after type coercion (e.g. convert string to number).
    • References that resolve into an array are no longer used as possible values to match but a single literal array value. To match against any of the array values, use the new Joi.in() method.
    • Object and array values are now compared using the Hoek.deepEqual() method instead of shallow comparison.
  • any.validate()

    • Remove callback support.
    • No longer returns a then-able object, only { value, error, warning }. For Promises support, use new any.asyncValidate().
    • Result error changed to undefined from null.
    • The options object is no longer validated automatically. Use Joi.checkPreferences() to manually validate once.
    • options.language no longer supported. Use new options.messages.
    • Move options.escapeHtml to options.errors.escapeHtml.
    • Move options.language.wrapArrays to errors.wrapArrays.
  • any.when()

    • No longer returns Joi.alternatives() type. The same type is retained and the condition is applied during validation time to generate a custom schema based on the combination of the multiple when() statements defined.
    • Past behavior ignored chained when() conditions once the first one matched. This is no longer the case and all the when() statements are processed. If you compare the same reference in chained when()s, keep in mind that it will match both the value and any otherwise statements which is probably not what you want. Use the new switch statement to only match a single schema.
    • Conflicting when() types (e.g. string type rules applied to number type base) will throw an error during validation so make sure to review your schema for potential conflicts.
    • is, then, otherwise literal values are compiled into override schemas ('x' is compiled into Joi.valid(Joi.override, 'x')). This means they will override any base schema the rule is applied to. To add a literal value, use the explicit Joi.valid('x') format.

alternatives() methods

  • alternatives()

    • Supports a single argument which must be an array or multiple schemas as separate arguments.
  • alternatives.try()

    • No longer accepts array arguments. Must pass each value as a separate argument.
  • alternatives.when()

    • Split off any.when() with a different implementation. Use alternatives.conditional() instead to keep existing behavior.
    • Will throw is the provided configuration will result in unreachable conditions.

array() methods

  • array()

    • No longer support string coercion.
  • array.items()

    • No longer accepts array arguments. Must pass each value as a separate argument.
    • References used in schemas point to their own schema instead of the array they are in. Prefix keys with ... to reference the array itself.
  • array.ordered()

    • No longer accepts array arguments. Must pass each value as a separate argument.
    • References used in schemas point to their own schema instead of the array they are in. Prefix keys with ... to reference the array itself.
  • array.single()

    • Applied to the value regardless of convert.
    • Only allowed on arrays with non-array items.

binary() methods

  • binary.min()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • binary.max()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • binary.length()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.

boolean() methods

  • boolean.insensitive()

    • No longer supported. Use the opposite new boolean.sensitive() instead.
  • boolean.allow()

    • String values are compared in case-sensitive manner.
  • boolean.equal()

    • String values are compared in case-sensitive manner.
  • boolean.falsy()

    • No longer accepts array arguments. Must pass each value as a separate argument.
  • boolean.invalid()

    • String values are compared in case-sensitive manner.
  • boolean.not()

    • String values are compared in case-sensitive manner.
  • boolean.truthy()

    • No longer accepts array arguments. Must pass each value as a separate argument.
  • boolean.valid()

    • String values are compared in case-sensitive manner.

date methods

  • date.min()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • date.max()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • date.greater()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • date.less()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.

function methods

  • function.arity()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • function.class()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • function.maxArity()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • function.minArity()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • function.ref()

    • Move to object.ref()

number() methods

  • number.greater()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • number.integer()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • number.less()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • number.max()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • number.min()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • number.negative()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • number.port()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • number.positive()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • number.precision()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.

object() methods

  • object()

    • No longer support string coercion.
  • object.and()

    • No longer accepts array arguments. Must pass each value as a separate argument.
  • object.assert()

    • The subject reference is no longer relative to the object keys (siblings). Instead it is relative to the object being validated itself. Just add . as a prefix to the subject reference (e.g. change 'a.b' to .a.b). No change on references resolved inside the assertion schema condition.
  • object.forbiddenKeys()

    • No longer supported. Use new any.fork() instead.
  • object.length()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • object.max()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • object.min()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • object.nand()

    • No longer accepts array arguments. Must pass each value as a separate argument.
  • object.optionalKeys()

    • No longer supported. Use new any.fork() instead.
  • object.or()

    • No longer accepts array arguments. Must pass each value as a separate argument.
  • object.pattern()

    • Stops matching patterns once a match is found. Use the new fallthrough option to keep previous behavior.
  • object.rename()

    • rename('a', 'b', { override: true }) will no longer delete existing key b if a does not exist.
  • object.requiredKeys()

    • No longer supported. Use new any.fork() instead.
  • object.schema()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • object.type()

    • Renamed to object.instance()
    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • object.xor()

    • No longer accepts array arguments. Must pass each value as a separate argument.

ref() methods

  • Joi.ref() was changed from a function to object with ref.resolve() method.
  • Joi.ref() no longer supports Hoek.reach() options.

string() methods

  • string.alphanum()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • string.base64()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • string.creditCard()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • string.dataUri()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • string.email()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • string.guid()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • string.hex()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • string.hostname()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • string.ip()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • string.isoDate()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • string.length()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • string.lowercase()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • string.max()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • string.min()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • string.normalize()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • string.regex()

    • Renamed to string.pattern().
  • string.trim()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • string.uppercase()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • string.token()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • string.uri()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
  • string.uuid()

    • Only the last invocation is kept which may result in different error message due to the order the rules are applied.
@hueniverse hueniverse added breaking changes Change that can breaking existing code release notes Major release documentation labels Aug 10, 2019
@hueniverse hueniverse added this to the 16.0.0 milestone Aug 10, 2019
@hueniverse hueniverse self-assigned this Aug 10, 2019
@hapijs hapijs locked and limited conversation to collaborators Aug 10, 2019
@hapijs hapijs unlocked this conversation Aug 12, 2019
@hapijs hapijs deleted a comment from dball Aug 30, 2019
@hapijs hapijs deleted a comment from SimonSchick Sep 8, 2019
@hapijs hapijs deleted a comment from matteocrippa Sep 17, 2019
@hapijs hapijs deleted a comment from fiws Sep 17, 2019
@hapijs hapijs locked and limited conversation to collaborators Sep 17, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
breaking changes Change that can breaking existing code release notes Major release documentation
Projects
None yet
Development

No branches or pull requests

1 participant