joi 17.4.0 → 17.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/errors.js CHANGED
@@ -131,10 +131,19 @@ exports.template = function (value, messages, code, state, prefs) {
131
131
  }
132
132
 
133
133
  if (lang &&
134
- messages[lang] &&
135
- messages[lang][code] !== undefined) {
134
+ messages[lang]) {
136
135
 
137
- return messages[lang][code];
136
+ if (messages[lang][code] !== undefined) {
137
+ return messages[lang][code];
138
+ }
139
+
140
+ if (messages[lang]['*'] !== undefined) {
141
+ return messages[lang]['*'];
142
+ }
143
+ }
144
+
145
+ if (!messages[code]) {
146
+ return messages['*'];
138
147
  }
139
148
 
140
149
  return messages[code];
package/lib/index.d.ts CHANGED
@@ -77,11 +77,18 @@ declare namespace Joi {
77
77
  label?: string | false,
78
78
 
79
79
  /**
80
- * the characters used around array avlues. Defaults to `'[]'`
80
+ * the characters used around array values. Defaults to `'[]'`
81
81
  *
82
82
  * @default '[]'
83
83
  */
84
84
  array?: string | false
85
+
86
+ /**
87
+ * the characters used around array string values. Defaults to no wrapping.
88
+ *
89
+ * @default false
90
+ */
91
+ string?: string | false
85
92
  };
86
93
  }
87
94
 
@@ -250,6 +257,12 @@ declare namespace Joi {
250
257
  }
251
258
 
252
259
  interface EmailOptions {
260
+ /**
261
+ * if `true`, domains ending with a `.` character are permitted
262
+ *
263
+ * @default false
264
+ */
265
+ allowFullyQualified?: boolean;
253
266
  /**
254
267
  * If `true`, Unicode characters are permitted
255
268
  *
@@ -289,6 +302,12 @@ declare namespace Joi {
289
302
  }
290
303
 
291
304
  interface DomainOptions {
305
+ /**
306
+ * if `true`, domains ending with a `.` character are permitted
307
+ *
308
+ * @default false
309
+ */
310
+ allowFullyQualified?: boolean;
292
311
  /**
293
312
  * If `true`, Unicode characters are permitted
294
313
  *
@@ -642,10 +661,14 @@ declare namespace Joi {
642
661
 
643
662
  type ValidationErrorFunction = (errors: ErrorReport[]) => string | ValidationErrorItem | Error;
644
663
 
645
- interface ValidationResult {
646
- error?: ValidationError;
664
+ type ValidationResult<TSchema = any> = {
665
+ error: undefined;
647
666
  warning?: ValidationError;
648
- value: any;
667
+ value: TSchema;
668
+ } | {
669
+ error: ValidationError;
670
+ warning?: ValidationError;
671
+ value: undefined;
649
672
  }
650
673
 
651
674
  interface CreateErrorOptions {
@@ -681,15 +704,44 @@ declare namespace Joi {
681
704
 
682
705
  type CustomValidator<V = any> = (value: V, helpers: CustomHelpers) => V | ErrorReport;
683
706
 
684
- type ExternalValidationFunction = (value: any) => any;
707
+ interface ExternalHelpers {
708
+ prefs: ValidationOptions;
709
+ }
710
+
711
+ type ExternalValidationFunction<V = any> = (value: V, helpers: ExternalHelpers) => V | undefined;
685
712
 
686
713
  type SchemaLikeWithoutArray = string | number | boolean | null | Schema | SchemaMap;
687
714
  type SchemaLike = SchemaLikeWithoutArray | object;
688
715
 
689
- type SchemaMap<TSchema = any> = {
716
+ type NullableType<T> = undefined | null | T
717
+
718
+ type ObjectPropertiesSchema<T = any> =
719
+ T extends NullableType<string>
720
+ ? Joi.StringSchema
721
+ : T extends NullableType<number>
722
+ ? Joi.NumberSchema
723
+ : T extends NullableType<bigint>
724
+ ? Joi.NumberSchema
725
+ : T extends NullableType<boolean>
726
+ ? Joi.BooleanSchema
727
+ : T extends NullableType<Date>
728
+ ? Joi.DateSchema
729
+ : T extends NullableType<Array<any>>
730
+ ? Joi.ArraySchema
731
+ : T extends NullableType<object>
732
+ ? ObjectSchema<StrictSchemaMap<T>>
733
+ : never
734
+
735
+ type PartialSchemaMap<TSchema = any> = {
690
736
  [key in keyof TSchema]?: SchemaLike | SchemaLike[];
737
+ }
738
+
739
+ type StrictSchemaMap<TSchema = any> = {
740
+ [key in keyof TSchema]-?: ObjectPropertiesSchema<TSchema[key]>
691
741
  };
692
742
 
743
+ type SchemaMap<TSchema = any, isStrict = false> = isStrict extends true ? StrictSchemaMap<TSchema> : PartialSchemaMap<TSchema>
744
+
693
745
  type Schema<P = any> =
694
746
  | AnySchema
695
747
  | ArraySchema
@@ -805,7 +857,7 @@ declare namespace Joi {
805
857
  $_validate(value: any, state: State, prefs: ValidationOptions): ValidationResult;
806
858
  }
807
859
 
808
- interface AnySchema extends SchemaInternals {
860
+ interface AnySchema<TSchema = any> extends SchemaInternals {
809
861
  /**
810
862
  * Flags of current schema.
811
863
  */
@@ -1128,7 +1180,7 @@ declare namespace Joi {
1128
1180
  /**
1129
1181
  * Validates a value using the schema and options.
1130
1182
  */
1131
- validate(value: any, options?: ValidationOptions): ValidationResult;
1183
+ validate(value: any, options?: ValidationOptions): ValidationResult<TSchema>;
1132
1184
 
1133
1185
  /**
1134
1186
  * Validates a value using the schema and options.
@@ -1548,7 +1600,7 @@ declare namespace Joi {
1548
1600
  matches: SchemaLike | Reference;
1549
1601
  }
1550
1602
 
1551
- interface ObjectSchema<TSchema = any> extends AnySchema {
1603
+ interface ObjectSchema<TSchema = any> extends AnySchema<TSchema> {
1552
1604
  /**
1553
1605
  * Defines an all-or-nothing relationship between keys where if one of the peers is present, all of them are required as well.
1554
1606
  *
@@ -1764,7 +1816,7 @@ declare namespace Joi {
1764
1816
  /**
1765
1817
  * Adds a conditional alternative schema type, either based on another key value, or a schema peeking into the current value.
1766
1818
  */
1767
- conditional(ref: string | Reference, options: WhenOptions): this;
1819
+ conditional(ref: string | Reference, options: WhenOptions | WhenOptions[]): this;
1768
1820
  conditional(ref: Schema, options: WhenSchemaOptions): this;
1769
1821
 
1770
1822
  /**
@@ -1972,7 +2024,7 @@ declare namespace Joi {
1972
2024
  * Generates a schema object that matches an object data type (as well as JSON strings that have been parsed into objects).
1973
2025
  */
1974
2026
  // tslint:disable-next-line:no-unnecessary-generics
1975
- object<TSchema = any, T = TSchema>(schema?: SchemaMap<T>): ObjectSchema<TSchema>;
2027
+ object<TSchema = any, isStrict = false, T = TSchema>(schema?: SchemaMap<T, isStrict>): ObjectSchema<TSchema>;
1976
2028
 
1977
2029
  /**
1978
2030
  * Generates a schema object that matches a string data type. Note that empty strings are not allowed by default and must be enabled with allow('').
@@ -2090,7 +2142,7 @@ declare namespace Joi {
2090
2142
  /**
2091
2143
  * Checks whether or not the provided argument is a joi schema.
2092
2144
  */
2093
- isSchema(schema: any, options?: CompileOptions): boolean;
2145
+ isSchema(schema: any, options?: CompileOptions): schema is AnySchema;
2094
2146
 
2095
2147
  /**
2096
2148
  * A special value used with `any.allow()`, `any.invalid()`, and `any.valid()` as the first value to reset any previously set values.
package/lib/messages.js CHANGED
@@ -81,7 +81,7 @@ exports.decompile = function (messages) {
81
81
  const message = messages[code];
82
82
 
83
83
  if (code === 'root') {
84
- target[code] = message;
84
+ target.root = message;
85
85
  continue;
86
86
  }
87
87
 
@@ -99,7 +99,7 @@ exports.decompile = function (messages) {
99
99
  const localized = message[code];
100
100
 
101
101
  if (code === 'root') {
102
- target[language][code] = localized;
102
+ target[language].root = localized;
103
103
  continue;
104
104
  }
105
105
 
package/lib/schemas.js CHANGED
@@ -34,7 +34,8 @@ exports.preferences = Joi.object({
34
34
  stack: Joi.boolean(),
35
35
  wrap: {
36
36
  label: internals.wrap,
37
- array: internals.wrap
37
+ array: internals.wrap,
38
+ string: internals.wrap
38
39
  }
39
40
  },
40
41
  externals: Joi.boolean(),
package/lib/template.js CHANGED
@@ -307,9 +307,10 @@ internals.wrap = function (value, ends) {
307
307
  };
308
308
 
309
309
 
310
- internals.stringify = function (value, original, state, prefs, local, options) {
310
+ internals.stringify = function (value, original, state, prefs, local, options = {}) {
311
311
 
312
312
  const type = typeof value;
313
+ const wrap = prefs && prefs.errors && prefs.errors.wrap || {};
313
314
 
314
315
  let skipWrap = false;
315
316
  if (Ref.isRef(value) &&
@@ -324,7 +325,7 @@ internals.stringify = function (value, original, state, prefs, local, options) {
324
325
  }
325
326
 
326
327
  if (type === 'string') {
327
- return value;
328
+ return internals.wrap(value, options.arrayItems && wrap.string);
328
329
  }
329
330
 
330
331
  if (type === 'number' ||
@@ -355,16 +356,12 @@ internals.stringify = function (value, original, state, prefs, local, options) {
355
356
  return value.toString();
356
357
  }
357
358
 
358
- let partial = '';
359
+ const values = [];
359
360
  for (const item of value) {
360
- partial = partial + (partial.length ? ', ' : '') + internals.stringify(item, original, state, prefs, local, options);
361
- }
362
-
363
- if (skipWrap) {
364
- return partial;
361
+ values.push(internals.stringify(item, original, state, prefs, local, { arrayItems: true, ...options }));
365
362
  }
366
363
 
367
- return internals.wrap(partial, prefs.errors.wrap.array);
364
+ return internals.wrap(values.join(', '), !skipWrap && wrap.array);
368
365
  };
369
366
 
370
367
 
@@ -46,6 +46,7 @@ module.exports = Any.extend({
46
46
 
47
47
  if (schema._flags.match) {
48
48
  const matched = [];
49
+ const failed = [];
49
50
 
50
51
  for (let i = 0; i < schema.$_terms.matches.length; ++i) {
51
52
  const item = schema.$_terms.matches[i];
@@ -57,24 +58,45 @@ module.exports = Any.extend({
57
58
  matched.push(result.value);
58
59
  }
59
60
  else {
61
+ failed.push(result.errors);
60
62
  localState.restore();
61
63
  }
62
64
  }
63
65
 
64
66
  if (matched.length === 0) {
65
- return { errors: error('alternatives.any') };
67
+ const context = {
68
+ details: failed.map((f) => Errors.details(f, { override: false }))
69
+ };
70
+
71
+ return { errors: error('alternatives.any', context) };
66
72
  }
67
73
 
74
+ // Match one
75
+
68
76
  if (schema._flags.match === 'one') {
69
77
  return matched.length === 1 ? { value: matched[0] } : { errors: error('alternatives.one') };
70
78
  }
71
79
 
80
+ // Match all
81
+
72
82
  if (matched.length !== schema.$_terms.matches.length) {
73
- return { errors: error('alternatives.all') };
83
+ const context = {
84
+ details: failed.map((f) => Errors.details(f, { override: false }))
85
+ };
86
+
87
+ return { errors: error('alternatives.all', context) };
74
88
  }
75
89
 
76
- const allobj = schema.$_terms.matches.reduce((acc, v) => acc && v.schema.type === 'object', true);
77
- return allobj ? { value: matched.reduce((acc, v) => Merge(acc, v, { mergeArrays: false })) } : { value: matched[matched.length - 1] };
90
+ const isAnyObj = (alternative) => {
91
+
92
+ return alternative.$_terms.matches.some((v) => {
93
+
94
+ return v.schema.type === 'object' ||
95
+ (v.schema.type === 'alternatives' && isAnyObj(v.schema));
96
+ });
97
+ };
98
+
99
+ return isAnyObj(schema) ? { value: matched.reduce((acc, v) => Merge(acc, v, { mergeArrays: false })) } : { value: matched[matched.length - 1] };
78
100
  }
79
101
 
80
102
  // Match any
@@ -319,7 +319,7 @@ module.exports = Any.extend({
319
319
  continue;
320
320
  }
321
321
 
322
- if (schema.$_terms._inclusions.length &&
322
+ if ((schema.$_terms._inclusions.length || schema.$_terms._requireds.length) &&
323
323
  !isValid) {
324
324
 
325
325
  if (stripUnknown) {
@@ -169,7 +169,7 @@ module.exports = Any.extend({
169
169
  },
170
170
  validate(value, helpers, { base }, options) {
171
171
 
172
- if (value % base === 0) {
172
+ if (value * (1 / base) % 1 === 0) {
173
173
  return value;
174
174
  }
175
175
 
@@ -28,7 +28,7 @@ const internals = {
28
28
  },
29
29
  dataUriRegex: /^data:[\w+.-]+\/[\w+.-]+;((charset=[\w-]+|base64),)?(.*)$/,
30
30
  hexRegex: /^[a-f0-9]+$/i,
31
- ipRegex: Ip.regex().regex,
31
+ ipRegex: Ip.regex({ cidr: 'forbidden' }).regex,
32
32
  isoDurationRegex: /^P(?!$)(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+S)?)?$/,
33
33
 
34
34
  guidBrackets: {
@@ -123,13 +123,20 @@ module.exports = Any.extend({
123
123
  }
124
124
  },
125
125
 
126
- validate(value, { error }) {
126
+ validate(value, { schema, error }) {
127
127
 
128
128
  if (typeof value !== 'string') {
129
129
  return { value, errors: error('string.base') };
130
130
  }
131
131
 
132
132
  if (value === '') {
133
+ const min = schema.$_getRule('min');
134
+ if (min &&
135
+ min.args.limit === 0) {
136
+
137
+ return;
138
+ }
139
+
133
140
  return { value, errors: error('string.empty') };
134
141
  }
135
142
  },
@@ -257,7 +264,7 @@ module.exports = Any.extend({
257
264
  method(options) {
258
265
 
259
266
  if (options) {
260
- Common.assertOptions(options, ['allowUnicode', 'maxDomainSegments', 'minDomainSegments', 'tlds']);
267
+ Common.assertOptions(options, ['allowFullyQualified', 'allowUnicode', 'maxDomainSegments', 'minDomainSegments', 'tlds']);
261
268
  }
262
269
 
263
270
  const address = internals.addressOptions(options);
@@ -276,7 +283,7 @@ module.exports = Any.extend({
276
283
  email: {
277
284
  method(options = {}) {
278
285
 
279
- Common.assertOptions(options, ['allowUnicode', 'ignoreLength', 'maxDomainSegments', 'minDomainSegments', 'multiple', 'separator', 'tlds']);
286
+ Common.assertOptions(options, ['allowFullyQualified', 'allowUnicode', 'ignoreLength', 'maxDomainSegments', 'minDomainSegments', 'multiple', 'separator', 'tlds']);
280
287
  Assert(options.multiple === undefined || typeof options.multiple === 'boolean', 'multiple option must be an boolean');
281
288
 
282
289
  const address = internals.addressOptions(options);
@@ -632,7 +639,7 @@ module.exports = Any.extend({
632
639
  Common.assertOptions(options, ['allowRelative', 'allowQuerySquareBrackets', 'domain', 'relativeOnly', 'scheme']);
633
640
 
634
641
  if (options.domain) {
635
- Common.assertOptions(options.domain, ['allowUnicode', 'maxDomainSegments', 'minDomainSegments', 'tlds']);
642
+ Common.assertOptions(options.domain, ['allowFullyQualified', 'allowUnicode', 'maxDomainSegments', 'minDomainSegments', 'tlds']);
636
643
  }
637
644
 
638
645
  const { regex, scheme } = Uri.regex(options);
package/lib/validator.js CHANGED
@@ -94,7 +94,10 @@ exports.entryAsync = async function (value, schema, prefs) {
94
94
  }
95
95
  }
96
96
  catch (err) {
97
- err.message += ` (${label})`; // Change message to include path
97
+ if (settings.errors.label) {
98
+ err.message += ` (${label})`; // Change message to include path
99
+ }
100
+
98
101
  throw err;
99
102
  }
100
103
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "joi",
3
3
  "description": "Object schema validation",
4
- "version": "17.4.0",
4
+ "version": "17.5.0",
5
5
  "repository": "git://github.com/sideway/joi",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
@@ -17,7 +17,7 @@
17
17
  "dependencies": {
18
18
  "@hapi/hoek": "^9.0.0",
19
19
  "@hapi/topo": "^5.0.0",
20
- "@sideway/address": "^4.1.0",
20
+ "@sideway/address": "^4.1.3",
21
21
  "@sideway/formula": "^3.0.0",
22
22
  "@sideway/pinpoint": "^2.0.0"
23
23
  },
@@ -26,7 +26,7 @@
26
26
  "@hapi/code": "8.x.x",
27
27
  "@hapi/joi-legacy-test": "npm:@hapi/joi@15.x.x",
28
28
  "@hapi/lab": "24.x.x",
29
- "typescript": "4.0.x"
29
+ "typescript": "4.3.x"
30
30
  },
31
31
  "scripts": {
32
32
  "prepublishOnly": "cd browser && npm install && npm run build",