Skip to content

Commit e60376b

Browse files
authored
Merge pull request #1035 from WesTyler/master
Implement String.regex "invalidate" configuration
2 parents e657a5a + 80f06eb commit e60376b

File tree

4 files changed

+102
-12
lines changed

4 files changed

+102
-12
lines changed

API.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@
109109
- [`string.truncate([enabled])`](#stringtruncateenabled)
110110
- [`string.creditCard()`](#stringcreditcard)
111111
- [`string.length(limit, [encoding])`](#stringlengthlimit-encoding)
112-
- [`string.regex(pattern, [name])`](#stringregexpattern-name)
112+
- [`string.regex(pattern, [name | options])`](#stringregexpattern-name--options)
113113
- [`string.replace(pattern, replacement)`](#stringreplacepattern-replacement)
114114
- [`string.alphanum()`](#stringalphanum)
115115
- [`string.token()`](#stringtoken)
@@ -1542,14 +1542,29 @@ const schema = Joi.object({
15421542
});
15431543
```
15441544
1545-
#### `string.regex(pattern, [name])`
1545+
#### `string.regex(pattern, [name | options])`
15461546
15471547
Defines a regular expression rule where:
15481548
- `pattern` - a regular expression object the string value must match against.
1549-
- `name` - optional name for patterns (useful with multiple patterns). Defaults to 'required'.
1549+
- `name` - optional name for patterns (useful with multiple patterns).
1550+
- `options` - an optional configuration object with the following supported properties:
1551+
- `name` - optional pattern name.
1552+
- `invert` - optional boolean flag. Defaults to `false` behavior. If specified as `true`, the provided pattern will be disallowed instead of required.
15501553
15511554
```js
15521555
const schema = Joi.string().regex(/^[abc]+$/);
1556+
1557+
const inlineNamedSchema = Joi.string().regex(/[0-9]/, 'numbers');
1558+
inlineNamedSchema.validate('alpha'); // ValidationError: "value" with value "alpha" fails to match the numbers pattern
1559+
1560+
const namedSchema = Joi.string().regex(/[0-9]/, { name: 'numbers'});
1561+
namedSchema.validate('alpha'); // ValidationError: "value" with value "alpha" fails to match the numbers pattern
1562+
1563+
const invertedSchema = Joi.string().regex(/[a-z]/, { invert: true });
1564+
invertedSchema.validate('lowercase'); // ValidationError: "value" with value "lowercase" matches the inverted pattern: [a-z]
1565+
1566+
const invertedNamedSchema = Joi.string().regex(/[a-z]/, { name: 'alpha', invert: true });
1567+
invertedNamedSchema.validate('lowercase'); // ValidationError: "value" with value "lowercase" matches the inverted alpha pattern
15531568
```
15541569
15551570
#### `string.replace(pattern, replacement)`

lib/language.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,9 @@ exports.errors = {
122122
token: 'must only contain alpha-numeric and underscore characters',
123123
regex: {
124124
base: 'with value "{{!value}}" fails to match the required pattern: {{pattern}}',
125-
name: 'with value "{{!value}}" fails to match the {{name}} pattern'
125+
inverted: 'with value "{{!value}}" matches the inverted pattern: {{pattern}}',
126+
name: 'with value "{{!value}}" fails to match the {{name}} pattern',
127+
invertedName: 'with value "{{!value}}" matches the inverted {{name}} pattern'
126128
},
127129
email: 'must be a valid email',
128130
uri: 'must be a valid uri',

lib/string.js

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,19 +91,39 @@ internals.String = class extends Any {
9191
});
9292
}
9393

94-
regex(pattern, name) {
94+
regex(pattern, patternOptions) {
9595

9696
Hoek.assert(pattern instanceof RegExp, 'pattern must be a RegExp');
9797

98-
pattern = new RegExp(pattern.source, pattern.ignoreCase ? 'i' : undefined); // Future version should break this and forbid unsupported regex flags
98+
const patternObject = {
99+
pattern: new RegExp(pattern.source, pattern.ignoreCase ? 'i' : undefined) // Future version should break this and forbid unsupported regex flags
100+
};
101+
let patternIsInverted = false;
102+
103+
if (typeof patternOptions === 'string') {
104+
patternObject.name = patternOptions;
105+
}
106+
else if (typeof patternOptions === 'object') {
107+
patternIsInverted = !!patternOptions.invert;
108+
patternObject.invert = patternIsInverted;
109+
110+
if (patternOptions.name) {
111+
patternObject.name = patternOptions.name;
112+
}
113+
}
114+
115+
const baseRegex = patternIsInverted ? 'string.regex.inverted' : 'string.regex.base';
116+
const nameRegex = patternIsInverted ? 'string.regex.invertedName' : 'string.regex.name';
117+
118+
return this._test('regex', patternObject, function (value, state, options) {
99119

100-
return this._test('regex', pattern, function (value, state, options) {
120+
const patternMatch = patternObject.pattern.test(value);
101121

102-
if (pattern.test(value)) {
122+
if (patternMatch ^ patternIsInverted) {
103123
return value;
104124
}
105125

106-
return this.createError((name ? 'string.regex.name' : 'string.regex.base'), { name, pattern, value }, state, options);
126+
return this.createError((patternObject.name ? nameRegex : baseRegex), { name: patternObject.name, pattern, value }, state, options);
107127
});
108128
}
109129

test/string.js

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ describe('string', () => {
8181

8282
describe('invalid()', () => {
8383

84-
it('invalidates case sensitive values', (done) => {
84+
it('inverts case sensitive values', (done) => {
8585

8686
Helper.validate(Joi.string().invalid('a', 'b'), [
8787
['a', false, null, '"value" contains an invalid value'],
@@ -91,7 +91,7 @@ describe('string', () => {
9191
], done);
9292
});
9393

94-
it('invalidates case insensitive values', (done) => {
94+
it('inverts case insensitive values', (done) => {
9595

9696
Helper.validate(Joi.string().invalid('a', 'b').insensitive(), [
9797
['a', false, null, '"value" contains an invalid value'],
@@ -875,6 +875,37 @@ describe('string', () => {
875875
done();
876876
});
877877
});
878+
879+
it('should include a pattern name in options object', (done) => {
880+
881+
const schema = Joi.string().regex(/[a-z]+/, { name: 'letters' }).regex(/[0-9]+/, { name: 'numbers' });
882+
schema.validate('abcd', (err, value) => {
883+
884+
expect(err.message).to.contain('numbers pattern');
885+
done();
886+
});
887+
});
888+
889+
it('should "invert" regex pattern if specified in options object', (done) => {
890+
891+
const schema = Joi.string().regex(/[a-z]/, { invert: true });
892+
Helper.validate(schema, [
893+
['0123456789', true],
894+
['abcdefg', false, null, '"value" with value "abcdefg" matches the inverted pattern: /[a-z]/']
895+
], done);
896+
});
897+
898+
it('should include inverted pattern name if specified', (done) => {
899+
900+
const schema = Joi.string().regex(/[a-z]/, {
901+
name : 'lowercase',
902+
invert: true
903+
});
904+
Helper.validate(schema, [
905+
['0123456789', true],
906+
['abcdefg', false, null, '"value" with value "abcdefg" matches the inverted lowercase pattern']
907+
], done);
908+
});
878909
});
879910

880911
describe('ip()', () => {
@@ -2085,7 +2116,7 @@ describe('string', () => {
20852116
], done);
20862117
});
20872118

2088-
it('should invalidate invalid values', (done) => {
2119+
it('should invert invalid values', (done) => {
20892120

20902121
const schema = Joi.string().valid('a', 'b', 'c');
20912122
Helper.validate(schema, [
@@ -3628,5 +3659,27 @@ describe('string', () => {
36283659
});
36293660
done();
36303661
});
3662+
3663+
it('describes invert regex pattern', (done) => {
3664+
3665+
const schema = Joi.string().regex(/[a-z]/, {
3666+
invert: true
3667+
});
3668+
const description = schema.describe();
3669+
expect(description).to.equal({
3670+
type: 'string',
3671+
invalids: [''],
3672+
rules: [
3673+
{
3674+
name: 'regex',
3675+
arg: {
3676+
pattern: /[a-z]/,
3677+
invert: true
3678+
}
3679+
}
3680+
]
3681+
});
3682+
done();
3683+
});
36313684
});
36323685
});

0 commit comments

Comments
 (0)