diff --git a/API.md b/API.md index 58fca516..f0af66ea 100755 --- a/API.md +++ b/API.md @@ -783,7 +783,14 @@ Adds an external validation rule where: return a replacement value, `undefined` to indicate no change, or throw an error, where: - `value` - a clone of the object containing the value being validated. - `helpers` - an object with the following helpers: + - `schema` - the current schema. + - `linked` - if the schema is a link, the schema it links to. + - `state` - the current validation state. - `prefs` - the current preferences. + - `original` - the original value passed into validation before any conversions. + - `error(code, [local])` - a method to generate error codes using a message code and optional local context. + - `message(messages, [local])` - a method to generate an error with an internal `'external'` error code and the provided messages object to use as override. Note that this is much slower than using the preferences `messages` option but is much simpler to write when performance is not important. + - `warn(code, [local])` - a method to add a warning using a message code and optional local context. - `description` - optional string used to document the purpose of the method. Note that external validation rules are only called after the all other validation rules for the diff --git a/lib/index.d.ts b/lib/index.d.ts index d40272c9..aec9bb5c 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -742,13 +742,20 @@ declare namespace Joi { message: (messages: LanguageMessages, local?: Context) => ErrorReport; } - type CustomValidator = (value: V, helpers: CustomHelpers) => V | ErrorReport; + type CustomValidator = (value: V, helpers: CustomHelpers) => R | ErrorReport; - interface ExternalHelpers { + interface ExternalHelpers { + schema: ExtensionBoundSchema; + linked: ExtensionBoundSchema | null; + state: State; prefs: ValidationOptions; + original: V; + warn: (code: string, local?: Context) => void; + error: (code: string, local?: Context) => ErrorReport; + message: (messages: LanguageMessages, local?: Context) => ErrorReport; } - type ExternalValidationFunction = (value: V, helpers: ExternalHelpers) => V | undefined; + type ExternalValidationFunction = (value: V, helpers: ExternalHelpers) => R | undefined; type SchemaLikeWithoutArray = string | number | boolean | null | Schema | SchemaMap; type SchemaLike = SchemaLikeWithoutArray | object; diff --git a/lib/validator.js b/lib/validator.js index cd29ed83..fa76fd87 100755 --- a/lib/validator.js +++ b/lib/validator.js @@ -67,35 +67,85 @@ exports.entryAsync = async function (value, schema, prefs) { if (mainstay.externals.length) { let root = result.value; - for (const { method, path, label } of mainstay.externals) { + const errors = []; + for (const external of mainstay.externals) { + const path = external.state.path; + const linked = external.schema.type === 'link' ? mainstay.links.get(external.schema) : null; let node = root; let key; let parent; + const ancestors = path.length ? [root] : []; + const original = path.length ? Reach(value, path) : value; + if (path.length) { key = path[path.length - 1]; - parent = Reach(root, path.slice(0, -1)); + + let current = root; + for (const segment of path.slice(0, -1)) { + current = current[segment]; + ancestors.unshift(current); + } + + parent = ancestors[0]; node = parent[key]; } try { - const output = await method(node, { prefs }); + const createError = (code, local) => (linked || external.schema).$_createError(code, node, local, external.state, settings); + const output = await external.method(node, { + schema: external.schema, + linked, + state: external.state, + prefs, + original, + error: createError, + errorsArray: internals.errorsArray, + warn: (code, local) => mainstay.warnings.push((linked || external.schema).$_createError(code, node, local, external.state, settings)), + message: (messages, local) => (linked || external.schema).$_createError('external', node, local, external.state, settings, { messages }) + }); + if (output === undefined || output === node) { continue; } + if (output instanceof Errors.Report) { + mainstay.tracer.log(external.schema, external.state, 'rule', 'external', 'error'); + errors.push(output); + + if (settings.abortEarly) { + break; + } + + continue; + } + + if (Array.isArray(output) && + output[Common.symbols.errors]) { + mainstay.tracer.log(external.schema, external.state, 'rule', 'external', 'error'); + errors.push(...output); + + if (settings.abortEarly) { + break; + } + + continue; + } + if (parent) { + mainstay.tracer.value(external.state, 'rule', node, output, 'external'); parent[key] = output; } else { + mainstay.tracer.value(external.state, 'rule', root, output, 'external'); root = output; } } catch (err) { if (settings.errors.label) { - err.message += ` (${label})`; // Change message to include path + err.message += ` (${(external.label)})`; // Change message to include path } throw err; @@ -103,6 +153,16 @@ exports.entryAsync = async function (value, schema, prefs) { } result.value = root; + + if (errors.length) { + result.error = Errors.process(errors, value, settings); + + if (mainstay.debug) { + result.error.debug = mainstay.debug; + } + + throw result.error; + } } if (!settings.warnings && @@ -514,7 +574,7 @@ internals.finalize = function (value, errors, helpers) { prefs._externals !== false) { // Disabled for matching for (const { method } of schema.$_terms.externals) { - state.mainstay.externals.push({ method, path: state.path, label: Errors.label(schema._flags, state, prefs) }); + state.mainstay.externals.push({ method, schema, state, label: Errors.label(schema._flags, state, prefs) }); } } diff --git a/test/index.ts b/test/index.ts index ae9e6483..628d21a4 100644 --- a/test/index.ts +++ b/test/index.ts @@ -19,7 +19,7 @@ const exp: RegExp = /./; const obj: object = {}; const date: Date = new Date(); const err: Error = new Error('test'); -const func: () => void = () => { }; +const func: () => void = () => {}; const symbol = Symbol('test'); const numArr: number[] = [1, 2, 3]; @@ -62,12 +62,12 @@ validOpts = { presence: 'optional' || 'required' || 'forbidden' }; validOpts = { context: obj }; validOpts = { noDefaults: bool }; validOpts = { - abortEarly: true, - messages: { - 'any.ref': str, - 'string.email': str - }, - dateFormat: 'iso' + abortEarly: true, + messages: { + 'any.ref': str, + 'string.email': str, + }, + dateFormat: 'iso', }; // Test various permutations of string, `false`, or `undefined` for both parameters: validOpts = { errors: { wrap: { label: str, array: '[]' } } }; @@ -142,7 +142,7 @@ dataUriOpts = { paddingRequired: bool }; // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- let whenOpts: Joi.WhenOptions = { - is: Joi.any() + is: Joi.any(), }; whenOpts = { is: x }; @@ -177,56 +177,56 @@ stringRegexOpts = { invert: bool }; // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- -const validErr = new Joi.ValidationError("message", [], "original"); +const validErr = new Joi.ValidationError('message', [], 'original'); let validErrItem: Joi.ValidationErrorItem; let validErrFunc: Joi.ValidationErrorFunction; validErrItem = { - message: str, - type: str, - path: [str] + message: str, + type: str, + path: [str], }; validErrItem = { - message: str, - type: str, - path: [str], - context: obj + message: str, + type: str, + path: [str], + context: obj, }; validErrItem = { - message: str, - type: str, - path: [str, num, str], - context: obj + message: str, + type: str, + path: [str, num, str], + context: obj, }; -validErrFunc = errs => errs[0]; -validErrFunc = errs => 'Some error'; -validErrFunc = errs => err; +validErrFunc = (errs) => errs[0]; +validErrFunc = (errs) => 'Some error'; +validErrFunc = (errs) => err; // error() can take function with ErrorReport argument -validErrFunc = errors => { - const path: string = errors[0].path[0]; - const code: string = errors[0].code; - const messages = errors[0].prefs.messages; +validErrFunc = (errors) => { + const path: string = errors[0].path[0]; + const code: string = errors[0].code; + const messages = errors[0].prefs.messages; - const message: string = messages ? messages[code].rendered : 'Error'; + const message: string = messages ? messages[code].rendered : 'Error'; - const validationErr = new Error(); - validationErr.message = `[${path}]: ${message}`; - return validationErr; + const validationErr = new Error(); + validationErr.message = `[${path}]: ${message}`; + return validationErr; }; Joi.any().error(validErrFunc); Joi.isError(validErr); -const maybeValidErr: any = new Joi.ValidationError("message", [], "original"); +const maybeValidErr: any = new Joi.ValidationError('message', [], 'original'); if (Joi.isError(maybeValidErr)) { - // isError is a type guard that allows accessing these properties: - maybeValidErr.isJoi; + // isError is a type guard that allows accessing these properties: + maybeValidErr.isJoi; } // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- @@ -256,105 +256,100 @@ anySchema = objSchema; let schemaMap: Joi.SchemaMap = {}; schemaMap = { - a: numSchema, - b: strSchema + a: numSchema, + b: strSchema, }; schemaMap = { - a: numSchema, - b: { - b1: strSchema, - b2: anySchema - } + a: numSchema, + b: { + b1: strSchema, + b2: anySchema, + }, }; schemaMap = { - a: numSchema, - b: [ - { b1: strSchema }, - { b2: anySchema } - ], - c: arrSchema, - d: schemaLike + a: numSchema, + b: [{ b1: strSchema }, { b2: anySchema }], + c: arrSchema, + d: schemaLike, }; schemaMap = { - a: 1, - b: { - b1: '1', - b2: 2 - }, - c: [ - { c1: true }, - { c2: null } - ] + a: 1, + b: { + b1: '1', + b2: 2, + }, + c: [{ c1: true }, { c2: null }], }; // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- anySchema = Joi.any(); -{ // common - anySchema = anySchema.allow(x); - anySchema = anySchema.allow(x, x); - anySchema = anySchema.allow(...[x, x, x]); - anySchema = anySchema.valid(x, 'x'); - anySchema = anySchema.valid(x, x); - anySchema = anySchema.valid(...[x, x, x]); - anySchema = anySchema.only(); - anySchema = anySchema.equal(x); - anySchema = anySchema.equal(x, x); - anySchema = anySchema.equal(...[x, x, x]); - anySchema = anySchema.invalid(x); - anySchema = anySchema.invalid(x, x); - anySchema = anySchema.invalid(...[x, x, x]); - anySchema = anySchema.disallow(x); - anySchema = anySchema.disallow(x, x); - anySchema = anySchema.disallow(...[x, x, x]); - anySchema = anySchema.not(x); - anySchema = anySchema.not(x, x); - anySchema = anySchema.not(...[x, x, x]); - - anySchema = Joi.object().default(); - anySchema = anySchema.default(x); - anySchema = anySchema.default("string"); - anySchema = anySchema.default(3.14); - anySchema = anySchema.default(true); - anySchema = anySchema.default({ foo: "bar" }); - anySchema = anySchema.default((parent, helpers) => { - return helpers.state; - }); - - anySchema = anySchema.required(); - anySchema = anySchema.optional(); - anySchema = anySchema.forbidden(); - anySchema = anySchema.strip(); - - anySchema = anySchema.description(str); - anySchema = anySchema.note(str); - anySchema = anySchema.note(str).note(str); - anySchema = anySchema.tag(str); - anySchema = anySchema.tag(str).tag(str); - - anySchema = anySchema.meta(obj); - anySchema = anySchema.example(obj); - anySchema = anySchema.unit(str); - - anySchema = anySchema.preferences(validOpts); - anySchema = anySchema.strict(); - anySchema = anySchema.strict(bool); - anySchema = anySchema.concat(Joi.object({ x: Joi.any() })); - - anySchema = anySchema.when(str, whenOpts); - anySchema = anySchema.when(ref, whenOpts); - anySchema = anySchema.when(schema, whenSchemaOpts); - - anySchema = anySchema.label(str); - anySchema = anySchema.raw(); - anySchema = anySchema.raw(bool); - anySchema = anySchema.empty(); - anySchema = anySchema.empty(str); - anySchema = anySchema.empty(anySchema); - - anySchema = anySchema.error(err); - anySchema = anySchema.error(validErrFunc); +{ + // common + anySchema = anySchema.allow(x); + anySchema = anySchema.allow(x, x); + anySchema = anySchema.allow(...[x, x, x]); + anySchema = anySchema.valid(x, 'x'); + anySchema = anySchema.valid(x, x); + anySchema = anySchema.valid(...[x, x, x]); + anySchema = anySchema.only(); + anySchema = anySchema.equal(x); + anySchema = anySchema.equal(x, x); + anySchema = anySchema.equal(...[x, x, x]); + anySchema = anySchema.invalid(x); + anySchema = anySchema.invalid(x, x); + anySchema = anySchema.invalid(...[x, x, x]); + anySchema = anySchema.disallow(x); + anySchema = anySchema.disallow(x, x); + anySchema = anySchema.disallow(...[x, x, x]); + anySchema = anySchema.not(x); + anySchema = anySchema.not(x, x); + anySchema = anySchema.not(...[x, x, x]); + + anySchema = Joi.object().default(); + anySchema = anySchema.default(x); + anySchema = anySchema.default('string'); + anySchema = anySchema.default(3.14); + anySchema = anySchema.default(true); + anySchema = anySchema.default({ foo: 'bar' }); + anySchema = anySchema.default((parent, helpers) => { + return helpers.state; + }); + + anySchema = anySchema.required(); + anySchema = anySchema.optional(); + anySchema = anySchema.forbidden(); + anySchema = anySchema.strip(); + + anySchema = anySchema.description(str); + anySchema = anySchema.note(str); + anySchema = anySchema.note(str).note(str); + anySchema = anySchema.tag(str); + anySchema = anySchema.tag(str).tag(str); + + anySchema = anySchema.meta(obj); + anySchema = anySchema.example(obj); + anySchema = anySchema.unit(str); + + anySchema = anySchema.preferences(validOpts); + anySchema = anySchema.strict(); + anySchema = anySchema.strict(bool); + anySchema = anySchema.concat(Joi.object({ x: Joi.any() })); + + anySchema = anySchema.when(str, whenOpts); + anySchema = anySchema.when(ref, whenOpts); + anySchema = anySchema.when(schema, whenSchemaOpts); + + anySchema = anySchema.label(str); + anySchema = anySchema.raw(); + anySchema = anySchema.raw(bool); + anySchema = anySchema.empty(); + anySchema = anySchema.empty(str); + anySchema = anySchema.empty(anySchema); + + anySchema = anySchema.error(err); + anySchema = anySchema.error(validErrFunc); } // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- @@ -372,7 +367,18 @@ arrSchema = arrSchema.sort({ by: 'n' }); arrSchema = arrSchema.sort({ by: Joi.ref('.x') }); arrSchema = arrSchema.sort(); arrSchema = arrSchema.ordered(anySchema); -arrSchema = arrSchema.ordered(anySchema, numSchema, strSchema, arrSchema, boolSchema, binSchema, dateSchema, funcSchema, objSchema, schemaLike); +arrSchema = arrSchema.ordered( + anySchema, + numSchema, + strSchema, + arrSchema, + boolSchema, + binSchema, + dateSchema, + funcSchema, + objSchema, + schemaLike +); arrSchema = arrSchema.ordered(schemaMap); expect.error(arrSchema.ordered([schemaMap, schemaMap, schemaLike])); arrSchema = arrSchema.min(num); @@ -392,51 +398,52 @@ expect.error(arrSchema.items([schemaMap, schemaMap, schemaLike])); // - - - - - - - - -{ // common copy paste - // use search & replace from any - arrSchema = arrSchema.allow(x); - arrSchema = arrSchema.allow(x, x); - arrSchema = arrSchema.allow(...[x, x, x]); - arrSchema = arrSchema.valid(x); - arrSchema = arrSchema.valid(x, x); - arrSchema = arrSchema.valid(...[x, x, x]); - arrSchema = arrSchema.only(); - arrSchema = arrSchema.equal(x); - arrSchema = arrSchema.equal(x, x); - arrSchema = arrSchema.equal(...[x, x, x]); - arrSchema = Joi.array().invalid(x); - arrSchema = arrSchema.invalid(x, x); - arrSchema = arrSchema.invalid(...[x, x, x]); - arrSchema = arrSchema.disallow(x); - arrSchema = arrSchema.disallow(x, x); - arrSchema = arrSchema.disallow(...[x, x, x]); - arrSchema = arrSchema.not(x); - arrSchema = arrSchema.not(x, x); - arrSchema = arrSchema.not(...[x, x, x]); - - arrSchema = arrSchema.default(x); - - arrSchema = arrSchema.required(); - arrSchema = arrSchema.optional(); - arrSchema = arrSchema.forbidden(); - - arrSchema = arrSchema.description(str); - arrSchema = arrSchema.note(str); - arrSchema = arrSchema.note(str).note(str); - arrSchema = arrSchema.tag(str); - arrSchema = arrSchema.tag(str).tag(str); - - arrSchema = arrSchema.meta(obj); - arrSchema = arrSchema.example(obj); - arrSchema = arrSchema.unit(str); - - arrSchema = arrSchema.preferences(validOpts); - arrSchema = arrSchema.strict(); - arrSchema = arrSchema.concat(Joi.array()); - - arrSchema = arrSchema.when(str, whenOpts); - arrSchema = arrSchema.when(ref, whenOpts); - arrSchema = arrSchema.when(schema, whenSchemaOpts); +{ + // common copy paste + // use search & replace from any + arrSchema = arrSchema.allow(x); + arrSchema = arrSchema.allow(x, x); + arrSchema = arrSchema.allow(...[x, x, x]); + arrSchema = arrSchema.valid(x); + arrSchema = arrSchema.valid(x, x); + arrSchema = arrSchema.valid(...[x, x, x]); + arrSchema = arrSchema.only(); + arrSchema = arrSchema.equal(x); + arrSchema = arrSchema.equal(x, x); + arrSchema = arrSchema.equal(...[x, x, x]); + arrSchema = Joi.array().invalid(x); + arrSchema = arrSchema.invalid(x, x); + arrSchema = arrSchema.invalid(...[x, x, x]); + arrSchema = arrSchema.disallow(x); + arrSchema = arrSchema.disallow(x, x); + arrSchema = arrSchema.disallow(...[x, x, x]); + arrSchema = arrSchema.not(x); + arrSchema = arrSchema.not(x, x); + arrSchema = arrSchema.not(...[x, x, x]); + + arrSchema = arrSchema.default(x); + + arrSchema = arrSchema.required(); + arrSchema = arrSchema.optional(); + arrSchema = arrSchema.forbidden(); + + arrSchema = arrSchema.description(str); + arrSchema = arrSchema.note(str); + arrSchema = arrSchema.note(str).note(str); + arrSchema = arrSchema.tag(str); + arrSchema = arrSchema.tag(str).tag(str); + + arrSchema = arrSchema.meta(obj); + arrSchema = arrSchema.example(obj); + arrSchema = arrSchema.unit(str); + + arrSchema = arrSchema.preferences(validOpts); + arrSchema = arrSchema.strict(); + arrSchema = arrSchema.concat(Joi.array()); + + arrSchema = arrSchema.when(str, whenOpts); + arrSchema = arrSchema.when(ref, whenOpts); + arrSchema = arrSchema.when(schema, whenSchemaOpts); } // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- @@ -444,60 +451,61 @@ expect.error(arrSchema.items([schemaMap, schemaMap, schemaLike])); boolSchema = Joi.bool(); boolSchema = Joi.boolean(); -{ // common copy paste - boolSchema = boolSchema.allow(x); - boolSchema = boolSchema.allow(x, x); - boolSchema = boolSchema.allow(...[x, x, x]); - boolSchema = boolSchema.valid(x); - boolSchema = boolSchema.valid(x, x); - boolSchema = boolSchema.valid(...[x, x, x]); - boolSchema = boolSchema.only(); - boolSchema = boolSchema.equal(x); - boolSchema = boolSchema.equal(x, x); - boolSchema = boolSchema.equal(...[x, x, x]); - boolSchema = Joi.boolean().invalid(x); - boolSchema = boolSchema.invalid(x, x); - boolSchema = boolSchema.invalid(...[x, x, x]); - boolSchema = boolSchema.disallow(x); - boolSchema = boolSchema.disallow(x, x); - boolSchema = boolSchema.disallow(...[x, x, x]); - boolSchema = boolSchema.not(x); - boolSchema = boolSchema.not(x, x); - boolSchema = boolSchema.not(...[x, x, x]); - - boolSchema = boolSchema.default(x); - - boolSchema = boolSchema.required(); - boolSchema = boolSchema.optional(); - boolSchema = boolSchema.forbidden(); - - boolSchema = boolSchema.description(str); - boolSchema = boolSchema.note(str); - boolSchema = boolSchema.note(str).note(str); - boolSchema = boolSchema.tag(str); - boolSchema = boolSchema.tag(str).tag(str); - - boolSchema = boolSchema.meta(obj); - boolSchema = boolSchema.example(obj); - boolSchema = boolSchema.unit(str); - - boolSchema = boolSchema.preferences(validOpts); - boolSchema = boolSchema.strict(); - boolSchema = boolSchema.concat(Joi.boolean()); - - boolSchema = boolSchema.truthy(str); - boolSchema = boolSchema.truthy(num); - boolSchema = boolSchema.truthy(str, str); - boolSchema = boolSchema.truthy(num, num); - boolSchema = boolSchema.falsy(str); - boolSchema = boolSchema.falsy(num); - boolSchema = boolSchema.falsy(str, str); - boolSchema = boolSchema.falsy(num, num); - boolSchema = boolSchema.sensitive(bool); - - boolSchema = boolSchema.when(str, whenOpts); - boolSchema = boolSchema.when(ref, whenOpts); - boolSchema = boolSchema.when(schema, whenSchemaOpts); +{ + // common copy paste + boolSchema = boolSchema.allow(x); + boolSchema = boolSchema.allow(x, x); + boolSchema = boolSchema.allow(...[x, x, x]); + boolSchema = boolSchema.valid(x); + boolSchema = boolSchema.valid(x, x); + boolSchema = boolSchema.valid(...[x, x, x]); + boolSchema = boolSchema.only(); + boolSchema = boolSchema.equal(x); + boolSchema = boolSchema.equal(x, x); + boolSchema = boolSchema.equal(...[x, x, x]); + boolSchema = Joi.boolean().invalid(x); + boolSchema = boolSchema.invalid(x, x); + boolSchema = boolSchema.invalid(...[x, x, x]); + boolSchema = boolSchema.disallow(x); + boolSchema = boolSchema.disallow(x, x); + boolSchema = boolSchema.disallow(...[x, x, x]); + boolSchema = boolSchema.not(x); + boolSchema = boolSchema.not(x, x); + boolSchema = boolSchema.not(...[x, x, x]); + + boolSchema = boolSchema.default(x); + + boolSchema = boolSchema.required(); + boolSchema = boolSchema.optional(); + boolSchema = boolSchema.forbidden(); + + boolSchema = boolSchema.description(str); + boolSchema = boolSchema.note(str); + boolSchema = boolSchema.note(str).note(str); + boolSchema = boolSchema.tag(str); + boolSchema = boolSchema.tag(str).tag(str); + + boolSchema = boolSchema.meta(obj); + boolSchema = boolSchema.example(obj); + boolSchema = boolSchema.unit(str); + + boolSchema = boolSchema.preferences(validOpts); + boolSchema = boolSchema.strict(); + boolSchema = boolSchema.concat(Joi.boolean()); + + boolSchema = boolSchema.truthy(str); + boolSchema = boolSchema.truthy(num); + boolSchema = boolSchema.truthy(str, str); + boolSchema = boolSchema.truthy(num, num); + boolSchema = boolSchema.falsy(str); + boolSchema = boolSchema.falsy(num); + boolSchema = boolSchema.falsy(str, str); + boolSchema = boolSchema.falsy(num, num); + boolSchema = boolSchema.sensitive(bool); + + boolSchema = boolSchema.when(str, whenOpts); + boolSchema = boolSchema.when(ref, whenOpts); + boolSchema = boolSchema.when(schema, whenSchemaOpts); } // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- @@ -509,50 +517,51 @@ binSchema = binSchema.min(num); binSchema = binSchema.max(num); binSchema = binSchema.length(num); -{ // common - binSchema = binSchema.allow(x); - binSchema = binSchema.allow(x, x); - binSchema = binSchema.allow(...[x, x, x]); - binSchema = binSchema.valid(x); - binSchema = binSchema.valid(x, x); - binSchema = binSchema.valid(...[x, x, x]); - binSchema = binSchema.only(); - binSchema = binSchema.equal(x); - binSchema = binSchema.equal(x, x); - binSchema = binSchema.equal(...[x, x, x]); - binSchema = Joi.binary().invalid(x); - binSchema = binSchema.invalid(x, x); - binSchema = binSchema.invalid(...[x, x, x]); - binSchema = binSchema.disallow(x); - binSchema = binSchema.disallow(x, x); - binSchema = binSchema.disallow(...[x, x, x]); - binSchema = binSchema.not(x); - binSchema = binSchema.not(x, x); - binSchema = binSchema.not(...[x, x, x]); - - binSchema = binSchema.default(x); - - binSchema = binSchema.required(); - binSchema = binSchema.optional(); - binSchema = binSchema.forbidden(); - - binSchema = binSchema.description(str); - binSchema = binSchema.note(str); - binSchema = binSchema.note(str).note(str); - binSchema = binSchema.tag(str); - binSchema = binSchema.tag(str).tag(str); - - binSchema = binSchema.meta(obj); - binSchema = binSchema.example(obj); - binSchema = binSchema.unit(str); - - binSchema = binSchema.preferences(validOpts); - binSchema = binSchema.strict(); - binSchema = binSchema.concat(Joi.binary()); - - binSchema = binSchema.when(str, whenOpts); - binSchema = binSchema.when(ref, whenOpts); - binSchema = binSchema.when(schema, whenSchemaOpts); +{ + // common + binSchema = binSchema.allow(x); + binSchema = binSchema.allow(x, x); + binSchema = binSchema.allow(...[x, x, x]); + binSchema = binSchema.valid(x); + binSchema = binSchema.valid(x, x); + binSchema = binSchema.valid(...[x, x, x]); + binSchema = binSchema.only(); + binSchema = binSchema.equal(x); + binSchema = binSchema.equal(x, x); + binSchema = binSchema.equal(...[x, x, x]); + binSchema = Joi.binary().invalid(x); + binSchema = binSchema.invalid(x, x); + binSchema = binSchema.invalid(...[x, x, x]); + binSchema = binSchema.disallow(x); + binSchema = binSchema.disallow(x, x); + binSchema = binSchema.disallow(...[x, x, x]); + binSchema = binSchema.not(x); + binSchema = binSchema.not(x, x); + binSchema = binSchema.not(...[x, x, x]); + + binSchema = binSchema.default(x); + + binSchema = binSchema.required(); + binSchema = binSchema.optional(); + binSchema = binSchema.forbidden(); + + binSchema = binSchema.description(str); + binSchema = binSchema.note(str); + binSchema = binSchema.note(str).note(str); + binSchema = binSchema.tag(str); + binSchema = binSchema.tag(str).tag(str); + + binSchema = binSchema.meta(obj); + binSchema = binSchema.example(obj); + binSchema = binSchema.unit(str); + + binSchema = binSchema.preferences(validOpts); + binSchema = binSchema.strict(); + binSchema = binSchema.concat(Joi.binary()); + + binSchema = binSchema.when(str, whenOpts); + binSchema = binSchema.when(ref, whenOpts); + binSchema = binSchema.when(schema, whenSchemaOpts); } // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- @@ -591,50 +600,51 @@ dateSchema = dateSchema.timestamp(); dateSchema = dateSchema.timestamp('javascript'); dateSchema = dateSchema.timestamp('unix'); -{ // common - dateSchema = dateSchema.allow(x); - dateSchema = dateSchema.allow(x, x); - dateSchema = dateSchema.allow(...[x, x, x]); - dateSchema = dateSchema.valid(x); - dateSchema = dateSchema.valid(x, x); - dateSchema = dateSchema.valid(...[x, x, x]); - dateSchema = dateSchema.only(); - dateSchema = dateSchema.equal(x); - dateSchema = dateSchema.equal(x, x); - dateSchema = dateSchema.equal(...[x, x, x]); - dateSchema = Joi.date().invalid(x); - dateSchema = dateSchema.invalid(x, x); - dateSchema = dateSchema.invalid(...[x, x, x]); - dateSchema = dateSchema.disallow(x); - dateSchema = dateSchema.disallow(x, x); - dateSchema = dateSchema.disallow(...[x, x, x]); - dateSchema = dateSchema.not(x); - dateSchema = dateSchema.not(x, x); - dateSchema = dateSchema.not(...[x, x, x]); - - dateSchema = dateSchema.default(x); - - dateSchema = dateSchema.required(); - dateSchema = dateSchema.optional(); - dateSchema = dateSchema.forbidden(); - - dateSchema = dateSchema.description(str); - dateSchema = dateSchema.note(str); - dateSchema = dateSchema.note(str).note(str); - dateSchema = dateSchema.tag(str); - dateSchema = dateSchema.tag(str).tag(str); - - dateSchema = dateSchema.meta(obj); - dateSchema = dateSchema.example(obj); - dateSchema = dateSchema.unit(str); - - dateSchema = dateSchema.preferences(validOpts); - dateSchema = dateSchema.strict(); - dateSchema = dateSchema.concat(Joi.date()); - - dateSchema = dateSchema.when(str, whenOpts); - dateSchema = dateSchema.when(ref, whenOpts); - dateSchema = dateSchema.when(schema, whenSchemaOpts); +{ + // common + dateSchema = dateSchema.allow(x); + dateSchema = dateSchema.allow(x, x); + dateSchema = dateSchema.allow(...[x, x, x]); + dateSchema = dateSchema.valid(x); + dateSchema = dateSchema.valid(x, x); + dateSchema = dateSchema.valid(...[x, x, x]); + dateSchema = dateSchema.only(); + dateSchema = dateSchema.equal(x); + dateSchema = dateSchema.equal(x, x); + dateSchema = dateSchema.equal(...[x, x, x]); + dateSchema = Joi.date().invalid(x); + dateSchema = dateSchema.invalid(x, x); + dateSchema = dateSchema.invalid(...[x, x, x]); + dateSchema = dateSchema.disallow(x); + dateSchema = dateSchema.disallow(x, x); + dateSchema = dateSchema.disallow(...[x, x, x]); + dateSchema = dateSchema.not(x); + dateSchema = dateSchema.not(x, x); + dateSchema = dateSchema.not(...[x, x, x]); + + dateSchema = dateSchema.default(x); + + dateSchema = dateSchema.required(); + dateSchema = dateSchema.optional(); + dateSchema = dateSchema.forbidden(); + + dateSchema = dateSchema.description(str); + dateSchema = dateSchema.note(str); + dateSchema = dateSchema.note(str).note(str); + dateSchema = dateSchema.tag(str); + dateSchema = dateSchema.tag(str).tag(str); + + dateSchema = dateSchema.meta(obj); + dateSchema = dateSchema.example(obj); + dateSchema = dateSchema.unit(str); + + dateSchema = dateSchema.preferences(validOpts); + dateSchema = dateSchema.strict(); + dateSchema = dateSchema.concat(Joi.date()); + + dateSchema = dateSchema.when(str, whenOpts); + dateSchema = dateSchema.when(ref, whenOpts); + dateSchema = dateSchema.when(schema, whenSchemaOpts); } // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- @@ -666,50 +676,51 @@ numSchema = numSchema.positive(); numSchema = numSchema.negative(); numSchema = numSchema.port(); -{ // common - numSchema = numSchema.allow(x); - numSchema = numSchema.allow(x, x); - numSchema = numSchema.allow(...[x, x, x]); - numSchema = numSchema.valid(x); - numSchema = numSchema.valid(x, x); - numSchema = numSchema.valid(...[x, x, x]); - numSchema = numSchema.only(); - numSchema = numSchema.equal(x); - numSchema = numSchema.equal(x, x); - numSchema = numSchema.equal(...[x, x, x]); - numSchema = Joi.number().invalid(x); - numSchema = numSchema.invalid(x, x); - numSchema = numSchema.invalid(...[x, x, x]); - numSchema = numSchema.disallow(x); - numSchema = numSchema.disallow(x, x); - numSchema = numSchema.disallow(...[x, x, x]); - numSchema = numSchema.not(x); - numSchema = numSchema.not(x, x); - numSchema = numSchema.not(...[x, x, x]); - - numSchema = numSchema.default(x); - - numSchema = numSchema.required(); - numSchema = numSchema.optional(); - numSchema = numSchema.forbidden(); - - numSchema = numSchema.description(str); - numSchema = numSchema.note(str); - numSchema = numSchema.note(str).note(str); - numSchema = numSchema.tag(str); - numSchema = numSchema.tag(str).tag(str); - - numSchema = numSchema.meta(obj); - numSchema = numSchema.example(obj); - numSchema = numSchema.unit(str); - - numSchema = numSchema.preferences(validOpts); - numSchema = numSchema.strict(); - numSchema = numSchema.concat(Joi.number()); - - numSchema = numSchema.when(str, whenOpts); - numSchema = numSchema.when(ref, whenOpts); - numSchema = numSchema.when(schema, whenSchemaOpts); +{ + // common + numSchema = numSchema.allow(x); + numSchema = numSchema.allow(x, x); + numSchema = numSchema.allow(...[x, x, x]); + numSchema = numSchema.valid(x); + numSchema = numSchema.valid(x, x); + numSchema = numSchema.valid(...[x, x, x]); + numSchema = numSchema.only(); + numSchema = numSchema.equal(x); + numSchema = numSchema.equal(x, x); + numSchema = numSchema.equal(...[x, x, x]); + numSchema = Joi.number().invalid(x); + numSchema = numSchema.invalid(x, x); + numSchema = numSchema.invalid(...[x, x, x]); + numSchema = numSchema.disallow(x); + numSchema = numSchema.disallow(x, x); + numSchema = numSchema.disallow(...[x, x, x]); + numSchema = numSchema.not(x); + numSchema = numSchema.not(x, x); + numSchema = numSchema.not(...[x, x, x]); + + numSchema = numSchema.default(x); + + numSchema = numSchema.required(); + numSchema = numSchema.optional(); + numSchema = numSchema.forbidden(); + + numSchema = numSchema.description(str); + numSchema = numSchema.note(str); + numSchema = numSchema.note(str).note(str); + numSchema = numSchema.tag(str); + numSchema = numSchema.tag(str).tag(str); + + numSchema = numSchema.meta(obj); + numSchema = numSchema.example(obj); + numSchema = numSchema.unit(str); + + numSchema = numSchema.preferences(validOpts); + numSchema = numSchema.strict(); + numSchema = numSchema.concat(Joi.number()); + + numSchema = numSchema.when(str, whenOpts); + numSchema = numSchema.when(ref, whenOpts); + numSchema = numSchema.when(schema, whenSchemaOpts); } // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- @@ -777,50 +788,51 @@ objSchema = objSchema.ref(); objSchema = objSchema.regex(); -{ // common - objSchema = objSchema.allow(x); - objSchema = objSchema.allow(x, x); - objSchema = objSchema.allow(...[x, x, x]); - objSchema = objSchema.valid(x); - objSchema = objSchema.valid(x, x); - objSchema = objSchema.valid(...[x, x, x]); - objSchema = objSchema.only(); - objSchema = objSchema.equal(x); - objSchema = objSchema.equal(x, x); - objSchema = objSchema.equal(...[x, x, x]); - objSchema = Joi.object().invalid(x); - objSchema = objSchema.invalid(x, x); - objSchema = objSchema.invalid(...[x, x, x]); - objSchema = objSchema.disallow(x); - objSchema = objSchema.disallow(x, x); - objSchema = objSchema.disallow(...[x, x, x]); - objSchema = objSchema.not(x); - objSchema = objSchema.not(x, x); - objSchema = objSchema.not(...[x, x, x]); - - objSchema = objSchema.default(x); - - objSchema = objSchema.required(); - objSchema = objSchema.optional(); - objSchema = objSchema.forbidden(); - - objSchema = objSchema.description(str); - objSchema = objSchema.note(str); - objSchema = objSchema.note(str).note(str); - objSchema = objSchema.tag(str); - objSchema = objSchema.tag(str).tag(str); - - objSchema = objSchema.meta(obj); - objSchema = objSchema.example(obj); - objSchema = objSchema.unit(str); - - objSchema = objSchema.preferences(validOpts); - objSchema = objSchema.strict(); - objSchema = objSchema.concat(Joi.object()); - - objSchema = objSchema.when(str, whenOpts); - objSchema = objSchema.when(ref, whenOpts); - objSchema = objSchema.when(schema, whenSchemaOpts); +{ + // common + objSchema = objSchema.allow(x); + objSchema = objSchema.allow(x, x); + objSchema = objSchema.allow(...[x, x, x]); + objSchema = objSchema.valid(x); + objSchema = objSchema.valid(x, x); + objSchema = objSchema.valid(...[x, x, x]); + objSchema = objSchema.only(); + objSchema = objSchema.equal(x); + objSchema = objSchema.equal(x, x); + objSchema = objSchema.equal(...[x, x, x]); + objSchema = Joi.object().invalid(x); + objSchema = objSchema.invalid(x, x); + objSchema = objSchema.invalid(...[x, x, x]); + objSchema = objSchema.disallow(x); + objSchema = objSchema.disallow(x, x); + objSchema = objSchema.disallow(...[x, x, x]); + objSchema = objSchema.not(x); + objSchema = objSchema.not(x, x); + objSchema = objSchema.not(...[x, x, x]); + + objSchema = objSchema.default(x); + + objSchema = objSchema.required(); + objSchema = objSchema.optional(); + objSchema = objSchema.forbidden(); + + objSchema = objSchema.description(str); + objSchema = objSchema.note(str); + objSchema = objSchema.note(str).note(str); + objSchema = objSchema.tag(str); + objSchema = objSchema.tag(str).tag(str); + + objSchema = objSchema.meta(obj); + objSchema = objSchema.example(obj); + objSchema = objSchema.unit(str); + + objSchema = objSchema.preferences(validOpts); + objSchema = objSchema.strict(); + objSchema = objSchema.concat(Joi.object()); + + objSchema = objSchema.when(str, whenOpts); + objSchema = objSchema.when(ref, whenOpts); + objSchema = objSchema.when(schema, whenSchemaOpts); } // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- @@ -861,12 +873,12 @@ strSchema = strSchema.uri(); strSchema = strSchema.uri(uriOpts); strSchema = strSchema.guid(); strSchema = strSchema.guid({ - version: ['uuidv1', 'uuidv2', 'uuidv3', 'uuidv4', 'uuidv5'] + version: ['uuidv1', 'uuidv2', 'uuidv3', 'uuidv4', 'uuidv5'], }); strSchema = strSchema.guid({ version: 'uuidv4' }); strSchema = strSchema.uuid(); strSchema = strSchema.uuid({ - version: ['uuidv1', 'uuidv2', 'uuidv3', 'uuidv4', 'uuidv5'] + version: ['uuidv1', 'uuidv2', 'uuidv3', 'uuidv4', 'uuidv5'], }); strSchema = strSchema.uuid({ version: 'uuidv4' }); strSchema = strSchema.hex(); @@ -885,50 +897,82 @@ strSchema = strSchema.base64(base64Opts); strSchema = strSchema.dataUri(); strSchema = strSchema.dataUri(dataUriOpts); -{ // common - strSchema = strSchema.allow(x); - strSchema = strSchema.allow(x, x); - strSchema = strSchema.allow(...[x, x, x]); - strSchema = strSchema.valid(x); - strSchema = strSchema.valid(x, x); - strSchema = strSchema.valid(...[x, x, x]); - strSchema = strSchema.only(); - strSchema = strSchema.equal(x); - strSchema = strSchema.equal(x, x); - strSchema = strSchema.equal(...[x, x, x]); - strSchema = Joi.string().invalid(x); - strSchema = strSchema.invalid(x, x); - strSchema = strSchema.invalid(...[x, x, x]); - strSchema = strSchema.disallow(x); - strSchema = strSchema.disallow(x, x); - strSchema = strSchema.disallow(...[x, x, x]); - strSchema = strSchema.not(x); - strSchema = strSchema.not(x, x); - strSchema = strSchema.not(...[x, x, x]); - - strSchema = strSchema.default(x); - - strSchema = strSchema.required(); - strSchema = strSchema.optional(); - strSchema = strSchema.forbidden(); - - strSchema = strSchema.description(str); - strSchema = strSchema.note(str); - strSchema = strSchema.note(str).note(str); - strSchema = strSchema.tag(str); - strSchema = strSchema.tag(str).tag(str); - - strSchema = strSchema.meta(obj); - strSchema = strSchema.example(obj); - strSchema = strSchema.unit(str); - - strSchema = strSchema.preferences(validOpts); - strSchema = strSchema.strict(); - strSchema = strSchema.concat(Joi.string()); - - strSchema = strSchema.when(str, whenOpts); - strSchema = strSchema.when(ref, whenOpts); - strSchema = strSchema.when(schema, whenSchemaOpts); +{ + // common + strSchema = strSchema.allow(x); + strSchema = strSchema.allow(x, x); + strSchema = strSchema.allow(...[x, x, x]); + strSchema = strSchema.valid(x); + strSchema = strSchema.valid(x, x); + strSchema = strSchema.valid(...[x, x, x]); + strSchema = strSchema.only(); + strSchema = strSchema.equal(x); + strSchema = strSchema.equal(x, x); + strSchema = strSchema.equal(...[x, x, x]); + strSchema = Joi.string().invalid(x); + strSchema = strSchema.invalid(x, x); + strSchema = strSchema.invalid(...[x, x, x]); + strSchema = strSchema.disallow(x); + strSchema = strSchema.disallow(x, x); + strSchema = strSchema.disallow(...[x, x, x]); + strSchema = strSchema.not(x); + strSchema = strSchema.not(x, x); + strSchema = strSchema.not(...[x, x, x]); + + strSchema = strSchema.default(x); + + strSchema = strSchema.required(); + strSchema = strSchema.optional(); + strSchema = strSchema.forbidden(); + + strSchema = strSchema.description(str); + strSchema = strSchema.note(str); + strSchema = strSchema.note(str).note(str); + strSchema = strSchema.tag(str); + strSchema = strSchema.tag(str).tag(str); + + strSchema = strSchema.meta(obj); + strSchema = strSchema.example(obj); + strSchema = strSchema.unit(str); + + strSchema = strSchema.preferences(validOpts); + strSchema = strSchema.strict(); + strSchema = strSchema.concat(Joi.string()); + + strSchema = strSchema.when(str, whenOpts); + strSchema = strSchema.when(ref, whenOpts); + strSchema = strSchema.when(schema, whenSchemaOpts); +} + +// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- + +{ + const custom: Joi.CustomValidator = (value, helpers) => { + expect.type(value); + expect.type(helpers.schema); + expect.type(helpers.state); + expect.type(helpers.prefs); + expect.type(helpers.original); + expect.type(helpers.warn); + expect.type(helpers.error); + expect.type(helpers.message); + return 1; + }; +} + +{ + const external: Joi.ExternalValidationFunction = (value, helpers) => { + expect.type(value); + expect.type(helpers.schema); + expect.type(helpers.linked); + expect.type(helpers.state); + expect.type(helpers.prefs); + expect.type(helpers.original); + expect.type(helpers.warn); + expect.type(helpers.error); + expect.type(helpers.message); + return 1; + }; } // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- @@ -983,49 +1027,76 @@ schema = Joi.link(str); // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- -{ // validate tests - { - let value = { username: 'example', password: 'example' }; - type TResult = { username: string; password: string }; - const schema = Joi.object().keys({ - username: Joi.string().max(255).required(), - password: Joi.string().pattern(/^[a-zA-Z0-9]{3,255}$/).required() - }); - let result: Joi.ValidationResult; - let asyncResult: Promise; - - result = schema.validate(value); - if (result.error) { - throw Error('error should not be set'); - } else { - expect.type(result.value); - } - result = schema.validate(value, validOpts); - asyncResult = schema.validateAsync(value); - asyncResult = schema.validateAsync(value, validOpts); - - asyncResult - .then(val => JSON.stringify(val, null, 2)) - .then(val => { throw new Error('one error'); }) - .catch(e => { }); - - expect.type>(schema.validateAsync(value)); - expect.type }>>(schema.validateAsync(value, { artifacts: true })); - expect.type>(schema.validateAsync(value, { warnings: true })); - expect.type; warning: Joi.ValidationWarning }>>(schema.validateAsync(value, { artifacts: true, warnings: true })); - expect.error>(schema.validateAsync(value, { artifacts: true })); - expect.error }>>(schema.validateAsync(value, { warnings: true })); - expect.error>(schema.validateAsync(value, { artifacts: true, warnings: true })); - expect.type>(schema.validateAsync(value, { artifacts: false })); - expect.type>(schema.validateAsync(value, { warnings: false })); - expect.type>(schema.validateAsync(value, { artifacts: false, warnings: false })); - - const falsyValue = { username: 'example' }; - result = schema.validate(falsyValue); - if (!result.error) { - throw Error('error should be set'); - } +{ + // validate tests + { + let value = { username: 'example', password: 'example' }; + type TResult = { username: string; password: string }; + const schema = Joi.object().keys({ + username: Joi.string().max(255).required(), + password: Joi.string() + .pattern(/^[a-zA-Z0-9]{3,255}$/) + .required(), + }); + let result: Joi.ValidationResult; + let asyncResult: Promise; + + result = schema.validate(value); + if (result.error) { + throw Error('error should not be set'); + } else { + expect.type(result.value); + } + result = schema.validate(value, validOpts); + asyncResult = schema.validateAsync(value); + asyncResult = schema.validateAsync(value, validOpts); + + asyncResult + .then((val) => JSON.stringify(val, null, 2)) + .then((val) => { + throw new Error('one error'); + }) + .catch((e) => {}); + + expect.type>(schema.validateAsync(value)); + expect.type }>>( + schema.validateAsync(value, { artifacts: true }) + ); + expect.type>( + schema.validateAsync(value, { warnings: true }) + ); + expect.type< + Promise<{ + value: TResult; + artifacts: Map; + warning: Joi.ValidationWarning; + }> + >(schema.validateAsync(value, { artifacts: true, warnings: true })); + expect.error>( + schema.validateAsync(value, { artifacts: true }) + ); + expect.error }>>( + schema.validateAsync(value, { warnings: true }) + ); + expect.error>( + schema.validateAsync(value, { artifacts: true, warnings: true }) + ); + expect.type>( + schema.validateAsync(value, { artifacts: false }) + ); + expect.type>( + schema.validateAsync(value, { warnings: false }) + ); + expect.type>( + schema.validateAsync(value, { artifacts: false, warnings: false }) + ); + + const falsyValue = { username: 'example' }; + result = schema.validate(falsyValue); + if (!result.error) { + throw Error('error should be set'); } + } } // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- @@ -1042,17 +1113,17 @@ Joi.assert(obj, schema, validOpts); expect.error(Joi.assert(obj, schemaLike)); { - let value = { username: 'example', password: 'example' }; - type TResult = { username: string; password: string }; - let typedSchema = schema as Joi.ObjectSchema; - value = Joi.attempt(obj, typedSchema); - value = Joi.attempt(obj, typedSchema, str); - value = Joi.attempt(obj, typedSchema, str, validOpts); - value = Joi.attempt(obj, typedSchema, err); - value = Joi.attempt(obj, typedSchema, err, validOpts); - value = Joi.attempt(obj, typedSchema, validOpts); - expect.type(Joi.attempt(obj, typedSchema)); - expect.error(Joi.attempt(obj, typedSchema)); + let value = { username: 'example', password: 'example' }; + type TResult = { username: string; password: string }; + let typedSchema = schema as Joi.ObjectSchema; + value = Joi.attempt(obj, typedSchema); + value = Joi.attempt(obj, typedSchema, str); + value = Joi.attempt(obj, typedSchema, str, validOpts); + value = Joi.attempt(obj, typedSchema, err); + value = Joi.attempt(obj, typedSchema, err, validOpts); + value = Joi.attempt(obj, typedSchema, validOpts); + expect.type(Joi.attempt(obj, typedSchema)); + expect.error(Joi.attempt(obj, typedSchema)); } expect.type(Joi.attempt(numArr, Joi.array())); @@ -1078,57 +1149,64 @@ description = schema.describe(); const Joi2 = Joi.extend({ type: 'test1', base: schema }); const Joi3 = Joi.extend({ - type: 'string', - base: Joi.string(), - messages: { - asd: 'must be exactly asd(f)' - }, - coerce(schema, value) { - return { value }; - }, - rules: { - asd: { - args: [ - { - name: 'allowFalse', - ref: true, - assert: Joi.boolean() - } - ], - method(allowFalse: boolean) { - return this.$_addRule({ - name: 'asd', - args: { - allowFalse - } - }); - }, - validate(value: boolean, helpers, params, options) { - if (value || params.allowFalse && !value) { - return value; - } - - return helpers.error('asd', { v: value }, options); - } + type: 'string', + base: Joi.string(), + messages: { + asd: 'must be exactly asd(f)', + }, + coerce(schema, value) { + return { value }; + }, + rules: { + asd: { + args: [ + { + name: 'allowFalse', + ref: true, + assert: Joi.boolean(), + }, + ], + method(allowFalse: boolean) { + return this.$_addRule({ + name: 'asd', + args: { + allowFalse, + }, + }); + }, + validate(value: boolean, helpers, params, options) { + if (value || (params.allowFalse && !value)) { + return value; } - } + + return helpers.error('asd', { v: value }, options); + }, + }, + }, }); -const Joi4 = Joi.extend({ type: 'test4', base: schema }, { type: 'test4a', base: schema }); +const Joi4 = Joi.extend( + { type: 'test4', base: schema }, + { type: 'test4a', base: schema } +); -const Joi5 = Joi.extend({ type: 'test5', base: schema }, { type: 'test5a', base: schema }, { type: 'test5b', base: schema }); +const Joi5 = Joi.extend( + { type: 'test5', base: schema }, + { type: 'test5a', base: schema }, + { type: 'test5b', base: schema } +); // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- const defaultsJoi = Joi.defaults((schema) => { - switch (schema.type) { - case 'string': - return schema.allow(''); - case 'object': - return (schema as Joi.ObjectSchema).min(1); - default: - return schema; - } + switch (schema.type) { + case 'string': + return schema.allow(''); + case 'object': + return (schema as Joi.ObjectSchema).min(1); + default: + return schema; + } }); schema = Joi.allow(x, x); @@ -1192,14 +1270,14 @@ ref = Joi.in(str, refOpts); schema = Joi.symbol(); schema = Joi.symbol().map(new Map()); schema = Joi.symbol().map({ - key: Symbol('asd') + key: Symbol('asd'), }); // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- const rule = Joi.string().case('upper').$_getRule('case'); if (rule && rule.args) { - const direction = rule.args.direction; + const direction = rule.args.direction; } // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- @@ -1212,103 +1290,112 @@ const terms = schema.$_terms; // should be able to append any new properties let anyObject = Joi.object({ - name: Joi.string().required(), - family: Joi.string() + name: Joi.string().required(), + family: Joi.string(), }); -anyObject = anyObject.append({ - age: Joi.number() -}).append({ - height: Joi.number() -}); +anyObject = anyObject + .append({ + age: Joi.number(), + }) + .append({ + height: Joi.number(), + }); anyObject = anyObject.keys({ - length: Joi.string() + length: Joi.string(), }); // test with keys -Joi.object().keys({ +Joi.object() + .keys({ name: Joi.string().required(), - family: Joi.string() -}).append({ - age: Joi.number() -}).append({ - height: Joi.number() -}).keys({ - length: Joi.string() -}); + family: Joi.string(), + }) + .append({ + age: Joi.number(), + }) + .append({ + height: Joi.number(), + }) + .keys({ + length: Joi.string(), + }); // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- // Test generic types interface User { - name: string; - family?: string; - age: number; + name: string; + family?: string; + age: number; } const userSchemaObject = Joi.object({ - name: Joi.string().required(), - family: Joi.string() + name: Joi.string().required(), + family: Joi.string(), }); - let userSchema = Joi.object().keys({ - name: Joi.string().required(), - family: Joi.string() + name: Joi.string().required(), + family: Joi.string(), }); userSchema = userSchema.append({ - age: Joi.number() + age: Joi.number(), }); userSchema.append({ height: Joi.number() }); - -const userSchema2 = Joi.object().keys({ - name: Joi.string().required() -}).keys({ - family: Joi.string() -}); +const userSchema2 = Joi.object() + .keys({ + name: Joi.string().required(), + }) + .keys({ + family: Joi.string(), + }); interface Comment { - text: string; - user: User; - isNew: boolean; + text: string; + user: User; + isNew: boolean; } const commentSchemaObject = Joi.object({ - text: Joi.string().required(), - user: userSchemaObject, - isNew: Joi.boolean().required(), + text: Joi.string().required(), + user: userSchemaObject, + isNew: Joi.boolean().required(), }); interface Comment2 { - text: string; - user?: { - name: string; - } + text: string; + user?: { + name: string; + }; } const commentSchemaObject2 = Joi.object({ - text: Joi.string().required(), - user: { - name: Joi.string().required(), - } -}) + text: Joi.string().required(), + user: { + name: Joi.string().required(), + }, +}); interface CommentWithAlternatives { - text: string; - user: string | User; - type: 'topLevel' | 'pingback' | 'reply'; - reported: boolean | number; + text: string; + user: string | User; + type: 'topLevel' | 'pingback' | 'reply'; + reported: boolean | number; } -const commentWithAlternativesSchemaObject = Joi.object({ - text: Joi.string().required(), - user: Joi.alternatives(Joi.string(), userSchemaObject), - type: Joi.string().required().valid('topLevel', 'pingback', 'reply'), - reported: Joi.alternatives(Joi.boolean(), Joi.number()), +const commentWithAlternativesSchemaObject = Joi.object< + CommentWithAlternatives, + true +>({ + text: Joi.string().required(), + user: Joi.alternatives(Joi.string(), userSchemaObject), + type: Joi.string().required().valid('topLevel', 'pingback', 'reply'), + reported: Joi.alternatives(Joi.boolean(), Joi.number()), }); expect.error(userSchema2.keys({ height: Joi.number() })); diff --git a/test/validator.js b/test/validator.js index f4bc2326..969fb437 100755 --- a/test/validator.js +++ b/test/validator.js @@ -286,6 +286,13 @@ describe('Validator', () => { expect(await schema.validateAsync(['skip'])).to.equal(['skip!']); }); + it('executes externals on array', async () => { + + const schema = Joi.array().items(Joi.string()).external((value) => [...value, 'extra']); + + expect(await schema.validateAsync(['valid'])).to.equal(['valid', 'extra']); + }); + it('skips externals when prefs is false', async () => { const check = (id) => { @@ -302,6 +309,22 @@ describe('Validator', () => { expect(() => schema.validate({ id: 'valid' })).to.throw('Schema with external rules must use validateAsync()'); }); + it('skips externals when validation failed', async () => { + + let called = false; + const check = (id) => { + + called = true; + }; + + const schema = Joi.object({ + id: Joi.string().min(10).external(check) + }); + + await expect(schema.validateAsync({ id: 'valid' })).to.reject('"id" length must be at least 10 characters long'); + expect(called).to.be.false(); + }); + it('supports describe', () => { const append = async (id) => { @@ -390,22 +413,69 @@ describe('Validator', () => { expect(input).to.equal([[1]]); }); - it('has access to prefs', async () => { + it('has access to helpers', async () => { const context = { foo: 'bar' }; - const tag = (value, { prefs }) => { - - return prefs.context.foo; + const tag = (value, helpers) => { + + expect(value).to.equal('my stringmy string'); + expect(helpers).to.be.an.object(); + expect(Joi.isSchema(helpers.schema)).to.be.true(); + expect(helpers.schema.type).to.equal('string'); + expect(helpers.state).to.be.an.object(); + expect(helpers.state.ancestors).to.equal([]); + expect(helpers.state.path).to.equal([]); + expect(helpers.prefs.context).to.equal({ foo: 'bar' }); + expect(helpers.original).to.equal('my string'); + expect(helpers.warn).to.be.a.function(); + expect(helpers.message).to.be.a.function(); + return helpers.prefs.context.foo; }; - const schema = Joi.string().external(tag); + const schema = Joi.string().custom((v) => v + v).external(tag); const input = 'my string'; const result = await schema.validateAsync(input, { context }); expect(result).to.equal('bar'); }); + it('has access to helpers on nested objects', async () => { + + const context = { foo: 'bar' }; + + const ext = (value, helpers) => { + + expect(value).to.equal('my stringmy string'); + expect(helpers).to.be.an.object(); + expect(Joi.isSchema(helpers.schema)).to.be.true(); + expect(helpers.schema.type).to.equal('string'); + expect(helpers.state).to.be.an.object(); + expect(helpers.state.ancestors).to.equal([ + { parent: 'my stringmy string' }, + { + grandparent: { parent: 'my stringmy string' } + } + ]); + expect(helpers.state.path).to.equal(['grandparent', 'parent']); + expect(helpers.prefs.context).to.equal({ foo: 'bar' }); + expect(helpers.original).to.equal('my string'); + expect(helpers.warn).to.be.a.function(); + expect(helpers.message).to.be.a.function(); + return helpers.prefs.context.foo; + }; + + const schema = Joi.object({ + grandparent: Joi.object({ + parent: Joi.string().custom((v) => v + v).external(ext) + }) + }); + const input = { grandparent: { parent: 'my string' } }; + + const result = await schema.validateAsync(input, { context }); + expect(result).to.equal({ grandparent: { parent: 'bar' } }); + }); + it('changes the message depending on label\'s value', async () => { const context = { foo: 'bar' }; @@ -421,6 +491,315 @@ describe('Validator', () => { await expect(schema.validateAsync(input, { context })).to.reject('Oops (value)'); await expect(schema.validateAsync(input, { context, errors: { label: false } })).to.reject('Oops'); }); + + it('should add warnings when warn helper is used', async () => { + + const ext = (value, { warn }) => { + + const newValue = value + value; + + warn('string.max', { limit: 10, value: newValue }); + warn('string.min', { limit: 1, value: newValue }); + + return newValue; + }; + + const schema = Joi.string().external(ext); + const input = 'my string'; + + const result = await schema.validateAsync(input, { warnings: true }); + expect(result.value).to.equal('my stringmy string'); + expect(result.warning).to.equal({ + message: '"value" length must be less than or equal to 10 characters long. "value" length must be at least 1 characters long', + details: [ + { + message: '"value" length must be less than or equal to 10 characters long', + path: [], + type: 'string.max', + context: { + label: 'value', + limit: 10, + value: 'my stringmy string' + } + }, + { + message: '"value" length must be at least 1 characters long', + path: [], + type: 'string.min', + context: { + label: 'value', + limit: 1, + value: 'my stringmy string' + } + } + ] + }); + }); + + it('should add errors when error helper is used', async () => { + + const ext = (value, { error }) => { + + const newValue = value + value; + + return error('string.max', { limit: 10, value: newValue }); + }; + + const schema = Joi.string().external(ext); + const input = 'my string'; + + const error = await expect(schema.validateAsync(input)).to.reject('"value" length must be less than or equal to 10 characters long'); + expect(error.details).to.equal([{ + message: '"value" length must be less than or equal to 10 characters long', + path: [], + type: 'string.max', + context: { + label: 'value', + limit: 10, + value: 'my stringmy string' + } + }]); + }); + + it('should add multiple errors when errorsArray helper is used', async () => { + + const ext = (value, { error, errorsArray }) => { + + const newValue = value + value; + + const errors = errorsArray(); + errors.push( + error('string.max', { limit: 10, value: newValue }), + error('string.min', { limit: 1, value: newValue }) + ); + return errors; + }; + + const schema = Joi.string().external(ext); + const input = 'my string'; + + const error = await expect(schema.validateAsync(input)).to.reject('"value" length must be less than or equal to 10 characters long. "value" length must be at least 1 characters long'); + expect(error.details).to.equal([{ + message: '"value" length must be less than or equal to 10 characters long', + path: [], + type: 'string.max', + context: { + label: 'value', + limit: 10, + value: 'my stringmy string' + } + }, { + message: '"value" length must be at least 1 characters long', + path: [], + type: 'string.min', + context: { + label: 'value', + limit: 1, + value: 'my stringmy string' + } + }]); + }); + + it('should add a custom error when message helper is used', async () => { + + const ext = (value, { message }) => { + + return message('{{#label}} has an invalid value {{#value}} ({{#custom}})', { custom: 'denied' }); + }; + + const schema = Joi.string().external(ext); + const input = 'my string'; + + const error = await expect(schema.validateAsync(input)).to.reject('"value" has an invalid value my string (denied)'); + expect(error.details).to.equal([{ + message: '"value" has an invalid value my string (denied)', + path: [], + type: 'external', + context: { + label: 'value', + value: 'my string', + custom: 'denied' + } + }]); + }); + + it('should add warnings when warn helper is used on a link', async () => { + + const schema = Joi.object({ + a: Joi.number(), + b: Joi.link('a').external((value, helpers) => { + + expect(helpers.schema.type).to.equal('link'); + expect(helpers.linked.type).to.equal('number'); + expect(helpers.state.schemas).to.have.length(2); + expect(helpers.state.schemas[0].schema.type).to.equal('link'); + expect(helpers.state.schemas[1].schema.type).to.equal('object'); + helpers.warn('number.max', { limit: 1, value }); + return value * 2; + }) + }); + + const result = await schema.validateAsync({ a: 1, b: 4 }, { warnings: true }); + expect(result.value).to.equal({ a: 1, b: 8 }); + expect(result.warning).to.equal({ + message: '"b" must be less than or equal to 1', + details: [{ + context: { + key: 'b', + label: 'b', + limit: 1, + value: 4 + }, + message: '"b" must be less than or equal to 1', + path: ['b'], + type: 'number.max' + }] + }); + }); + + it('should add errors when error helper is used on a link', async () => { + + const schema = Joi.object({ + a: Joi.number(), + b: Joi.link('a').external((value, helpers) => { + + expect(helpers.schema.type).to.equal('link'); + expect(helpers.linked.type).to.equal('number'); + expect(helpers.state.schemas).to.have.length(2); + expect(helpers.state.schemas[0].schema.type).to.equal('link'); + expect(helpers.state.schemas[1].schema.type).to.equal('object'); + return helpers.error('number.max', { limit: 1, value }); + }) + }); + + const error = await expect(schema.validateAsync({ a: 1, b: 4 }, { warnings: true })).to.reject('"b" must be less than or equal to 1'); + expect(error.details).to.equal([{ + context: { + key: 'b', + label: 'b', + limit: 1, + value: 4 + }, + message: '"b" must be less than or equal to 1', + path: ['b'], + type: 'number.max' + }]); + }); + + it('should add a custom error when message helper is used on a link', async () => { + + const schema = Joi.object({ + a: Joi.number(), + b: Joi.link('a').external((value, helpers) => { + + return helpers.message('{{#label}} should be {{#sign}} {{#value}}', { sign: '>' }); + }) + }); + + const error = await expect(schema.validateAsync({ a: 1, b: 4 })).to.reject('"b" should be > 4'); + expect(error.details).to.equal([{ + message: '"b" should be > 4', + path: ['b'], + type: 'external', + context: { + key: 'b', + label: 'b', + value: 4, + sign: '>' + } + }]); + }); + + it('should call multiple externals when abortEarly is false and error helper is used', async () => { + + const ext1 = (value, { error }) => { + + const newValue = value + value; + + return error('string.max', { limit: 10, value: newValue }); + }; + + const ext2 = (value, { error, errorsArray }) => { + + const errors = errorsArray(); + errors.push( + error('string.min', { limit: 8, value }), + error('string.max', { limit: 15, value }) + ); + return errors; + }; + + const schema = Joi.string().external(ext1).external(ext2); + const input = 'my string'; + + const error = await expect(schema.validateAsync(input, { abortEarly: false })).to.reject('"value" length must be less than or equal to 10 characters long. "value" length must be at least 8 characters long. "value" length must be less than or equal to 15 characters long'); + expect(error.details).to.equal([ + { + message: '"value" length must be less than or equal to 10 characters long', + path: [], + type: 'string.max', + context: { + label: 'value', + limit: 10, + value: 'my stringmy string' + } + }, + { + message: '"value" length must be at least 8 characters long', + path: [], + type: 'string.min', + context: { + label: 'value', + limit: 8, + value: 'my string' + } + }, + { + message: '"value" length must be less than or equal to 15 characters long', + path: [], + type: 'string.max', + context: { + label: 'value', + limit: 15, + value: 'my string' + } + } + ]); + }); + + it('should add debug information', async () => { + + const ext1 = (value, { error }) => { + + const newValue = value + value; + + return newValue; + }; + + const ext2 = (value, { error, errorsArray }) => { + + const errors = errorsArray(); + errors.push( + error('string.min', { limit: 8, value }), + error('string.max', { limit: 15, value }) + ); + return errors; + }; + + const schema = Joi.string().external(ext1).external(ext2); + const input = 'my string'; + + const error = await expect(schema.validateAsync(input, { abortEarly: false, debug: true })).to.reject(); + expect(error.debug).to.equal( [ + { type: 'entry', path: [] }, + { type: 'value', by: 'rule', path: [], from: 'my string', name: 'external', to: 'my stringmy string' }, + { type: 'rule', name: 'external', result: 'error', path: [] }, + { type: 'resolve', ref: 'ref:local:label', to: 'value', path: [] }, + { type: 'resolve', ref: 'ref:local:limit', to: 8, path: [] }, + { type: 'resolve', ref: 'ref:local:label', to: 'value', path: [] }, + { type: 'resolve', ref: 'ref:local:limit', to: 15, path: [] } + ]); + }); }); describe('finalize()', () => {