Skip to content

Commit

Permalink
Refactor string regex implementation per review on #1035
Browse files Browse the repository at this point in the history
  • Loading branch information
WesTyler committed Nov 14, 2016
1 parent c5f95af commit ea40559
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 35 deletions.
14 changes: 7 additions & 7 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -1546,22 +1546,22 @@ const schema = Joi.object({
Defines a regular expression rule where:
- `pattern` - a regular expression object the string value must match against.
- `name` - optional name for patterns (useful with multiple patterns). Defaults to 'required'.
- `name` - optional name for patterns (useful with multiple patterns).
- `config` - an optional configuration object with the following supported properties:
- `name` - optional pattern name. Defaults to `required`.
- `invalidate` - optional boolean flag. Defaults to `false` behavior. If specified as `true`, the provided pattern will be disallowed instead of required.
- `name` - optional pattern name.
- `invert` - optional boolean flag. Defaults to `false` behavior. If specified as `true`, the provided pattern will be disallowed instead of required.
```js
const schema = Joi.string().regex(/^[abc]+$/);

const namedSchema = Joi.string().regex(/[0-9]/, { name: 'numbers'});
namedSchema.validate('alpha'); // ValidationError: "value" with value "alpha" fails to match the numbers pattern

const invalidatedSchema = Joi.string().regex(/[a-z]/, { invalidate: true });
invalidatedSchema.validate('lowercase'); // ValidationError: "value" with value "lowercase" matches the invalidated pattern: [a-z]
const invertedSchema = Joi.string().regex(/[a-z]/, { invert: true });
invertedSchema.validate('lowercase'); // ValidationError: "value" with value "lowercase" matches the inverted pattern: [a-z]

const invalidatedNamedSchema = Joi.string().regex(/[a-z]/, { name: 'alpha', invalidate: true });
invalidatedNamedSchema.validate('lowercase'); // ValidationError: "value" with value "lowercase" matches the invalidated alpha pattern
const invertedNamedSchema = Joi.string().regex(/[a-z]/, { name: 'alpha', invert: true });
invertedNamedSchema.validate('lowercase'); // ValidationError: "value" with value "lowercase" matches the inverted alpha pattern
```
#### `string.replace(pattern, replacement)`
Expand Down
4 changes: 2 additions & 2 deletions lib/language.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@ exports.errors = {
token: 'must only contain alpha-numeric and underscore characters',
regex: {
base: 'with value "{{!value}}" fails to match the required pattern: {{pattern}}',
invalidated: 'with value "{{!value}}" matches the invalidated pattern: {{pattern}}',
inverted: 'with value "{{!value}}" matches the inverted pattern: {{pattern}}',
name: 'with value "{{!value}}" fails to match the {{name}} pattern',
invalidatedName: 'with value "{{!value}}" matches the invalidated {{name}} pattern'
invertedName: 'with value "{{!value}}" matches the inverted {{name}} pattern'
},
email: 'must be a valid email',
uri: 'must be a valid uri',
Expand Down
28 changes: 16 additions & 12 deletions lib/string.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,28 +91,32 @@ internals.String = class extends Any {
});
}

regex(pattern, config) {
regex(pattern, patternOptions) {

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

pattern = new RegExp(pattern.source, pattern.ignoreCase ? 'i' : undefined); // Future version should break this and forbid unsupported regex flags
const patternObject = {
pattern: new RegExp(pattern.source, pattern.ignoreCase ? 'i' : undefined) // Future version should break this and forbid unsupported regex flags
};

const patternIsInvalidated = (typeof config === 'object' && Hoek.reach(config, 'invalidate'));
const testName = patternIsInvalidated ? 'invalidatedRegex' : 'regex';
const patternIsInverted = (typeof patternOptions === 'object' && patternOptions.invert);
if (patternIsInverted) {
patternObject.inverted = true;
}

return this._test(testName, pattern, function (value, state, options) {
return this._test('regex', patternObject, function (value, state, options) {

const patternMatch = pattern.test(value);
const patternMatch = patternObject.pattern.test(value);

if ((patternMatch && !patternIsInvalidated) || (!patternMatch && patternIsInvalidated)) {
if (patternMatch ^ patternIsInverted) {
return value;
}

const name = typeof config === 'string' ?
config :
Hoek.reach(config, 'name');
const baseRegex = patternIsInvalidated ? 'string.regex.invalidated' : 'string.regex.base';
const nameRegex = patternIsInvalidated ? 'string.regex.invalidatedName' : 'string.regex.name';
const name = typeof patternOptions === 'string' ?
patternOptions :
Hoek.reach(patternOptions, 'name');
const baseRegex = patternIsInverted ? 'string.regex.inverted' : 'string.regex.base';
const nameRegex = patternIsInverted ? 'string.regex.invertedName' : 'string.regex.name';

return this.createError((name ? nameRegex : baseRegex), { name, pattern, value }, state, options);
});
Expand Down
31 changes: 17 additions & 14 deletions test/string.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ describe('string', () => {

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

it('invalidates case sensitive values', (done) => {
it('inverts case sensitive values', (done) => {

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

it('invalidates case insensitive values', (done) => {
it('inverts case insensitive values', (done) => {

Helper.validate(Joi.string().invalid('a', 'b').insensitive(), [
['a', false, null, '"value" contains an invalid value'],
Expand Down Expand Up @@ -886,24 +886,24 @@ describe('string', () => {
});
});

it('should "invalidate" regex pattern if specified in options object', (done) => {
it('should "invert" regex pattern if specified in options object', (done) => {

const schema = Joi.string().regex(/[a-z]/, { invalidate: true });
const schema = Joi.string().regex(/[a-z]/, { invert: true });
Helper.validate(schema, [
['0123456789', true],
['abcdefg', false, null, '"value" with value "abcdefg" matches the invalidated pattern: /[a-z]/']
['abcdefg', false, null, '"value" with value "abcdefg" matches the inverted pattern: /[a-z]/']
], done);
});

it('should include invalidated pattern name if specified', (done) => {
it('should include inverted pattern name if specified', (done) => {

const schema = Joi.string().regex(/[a-z]/, {
name : 'lowercase',
invalidate: true
name : 'lowercase',
invert: true
});
Helper.validate(schema, [
['0123456789', true],
['abcdefg', false, null, '"value" with value "abcdefg" matches the invalidated lowercase pattern']
['abcdefg', false, null, '"value" with value "abcdefg" matches the inverted lowercase pattern']
], done);
});
});
Expand Down Expand Up @@ -2116,7 +2116,7 @@ describe('string', () => {
], done);
});

it('should invalidate invalid values', (done) => {
it('should invert invalid values', (done) => {

const schema = Joi.string().valid('a', 'b', 'c');
Helper.validate(schema, [
Expand Down Expand Up @@ -3660,19 +3660,22 @@ describe('string', () => {
done();
});

it('describes invalidate regex pattern', (done) => {
it('describes invert regex pattern', (done) => {

const schema = Joi.string().regex(/[a-z]/, {
invalidate: true
invert: true
});
const description = schema.describe();
expect(description).to.equal({
type: 'string',
invalids: [''],
rules: [
{
name: 'invalidatedRegex',
arg: /[a-z]/
name: 'regex',
arg: {
pattern: /[a-z]/,
inverted: true
}
}
]
});
Expand Down

0 comments on commit ea40559

Please sign in to comment.