Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement String.regex "invalidate" configuration #1035

Merged
merged 6 commits into from
Nov 14, 2016

Conversation

WesTyler
Copy link
Contributor

Per discussions in #1033 and #1020, refactored string().regex(pattern, [name]) to support string().regex(pattern, [name | config]) as follows:

  1. If name is a string it behaves as currently implemented. No change.
  2. If config is an object, it supports the following properties:
  • name: {string}, behaves as currently implemented.
  • invalidate: {boolean}, defaults to false behavior; if true, disallows the provided regex pattern.

Usage:

const schema = Joi.string().regex(/[a-z]/, {
    name      : 'lowercase',
    invalidate: true
});

schema.validate('bad', (err, value) => {
    if (err) {
        // ValidationError: "value" with value "bad" matches the invalidated lowercase pattern
    }
});
schema.validate('BAD', (err, value) => {
    if (err) {} // err === null
});

schema.describe();
/*
{
  type: 'string',
  invalids: [ '' ],
  rules: [ { name: 'invalidatedRegex', arg: /[a-z]/ } ] 
}
*/ 

@WesTyler
Copy link
Contributor Author

(Realized I missed the API doc update. Pushing now)


const patternMatch = pattern.test(value);

if ((patternMatch && !patternIsInvalidated) || (!patternMatch && patternIsInvalidated)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clearer with patternMatch ^ patternIsInvalidated.


if (pattern.test(value)) {
return this._test(testName, pattern, function (value, state, options) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pattern should now become an object with every details in it (name, pattern, invalidated), I wouldn't change the name of the test depending on the options.

Copy link
Contributor Author

@WesTyler WesTyler Nov 14, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the test name to distinguish between a normal regex test and an inverted regex test in the describe'd value. Using the revision you requested, should the name/pattern/inverted flag just become part of the arg property on the describe?

{
                type: 'string',
                invalids: [''],
                rules: [
                    {
                        name: 'regex',
                        arg: {
                            pattern: /[a-z]/,
                            inverted: true
                        }
                    }
                ]
            }

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will automatically if you do.

Copy link
Contributor Author

@WesTyler WesTyler Nov 14, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pattern.pattern gets a bit weird. Especially at this.createError (end up with a pattern: pattern.pattern). How about patternObject with a pattern property?

(Running low on time on my lunch break. Going to move forward with patternObject, but willing to change if you disagree)

@@ -91,19 +91,30 @@ internals.String = class extends Any {
});
}

regex(pattern, name) {
regex(pattern, config) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the term options, in readme too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going to use "options", but then I have a variable name clash with the test callback defined on line 103.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, didn't see that one. patternOptions then ?


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'.
- `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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a big fan of invalidate, it makes me think of caching. What about invert ?


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'.
- `config` - an optional configuration object with the following supported properties:
- `name` - optional pattern name. Defaults to `required`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the original, I have absolutely no idea where that Defaults to required came from, it isn't :)


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

return this._test('regex', pattern, function (value, state, options) {
const patternIsInvalidated = (typeof config === 'object' && Hoek.reach(config, 'invalidate'));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use hoek to access it since it's a direct property and you just checked it's an object ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Habit. Updating along with other changes. :)

@Marsup Marsup added the feature New functionality or improvement label Nov 14, 2016
@Marsup Marsup self-assigned this Nov 14, 2016
Copy link
Collaborator

@Marsup Marsup left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're close !

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

const patternIsInverted = (typeof patternOptions === 'object' && patternOptions.invert);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (typeof patternOptions === 'string') {
    patternOjbect.name = patternOptions;
}
else if (typeof patternOptions === 'object') {
    patternOjbect.name = patternOptions.name;
    patternObject.invert = !!patternOptions.invert;
}

Copy link
Contributor Author

@WesTyler WesTyler Nov 14, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For patternObject.name = patternOptions.name; it looks like I need to have either a conditional check or a fallback value for describe when name isn't provided:

 "name": "[undefined]"

Preference here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are you getting this ? It's not supposed to be stringified.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was actually just taking a second look at that. The stringified piece is just because I copied the Lab test output. But I am seeing this:

const schema = Joi.string().regex(/[a-z]/, {
    invert: true
});
const description = schema.describe();
console.log(description.rules[0]);
/*
{ name: 'regex',
  arg: { pattern: /[a-z]/, name: undefined, invert: true } }
*/

If having name: undefined is okay, then no problem. I just wouldn't expect any property at all in this case.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a if then, no ternary.

return value;
}

return this.createError((name ? 'string.regex.name' : 'string.regex.base'), { name, pattern, value }, state, options);
const name = typeof patternOptions === 'string' ?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This becomes useless with upper snippet.

const name = typeof patternOptions === 'string' ?
patternOptions :
Hoek.reach(patternOptions, 'name');
const baseRegex = patternIsInverted ? 'string.regex.inverted' : 'string.regex.base';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error code can be precalculated outside of the test, we should do that.


```js
const schema = Joi.string().regex(/^[abc]+$/);

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing the simpler inlined string.

@@ -1542,14 +1542,26 @@ const schema = Joi.object({
});
```

#### `string.regex(pattern, [name])`
#### `string.regex(pattern, [name | config])`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

options

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops XD

@Marsup Marsup added this to the 10.0.0 milestone Nov 14, 2016
@Marsup
Copy link
Collaborator

Marsup commented Nov 14, 2016

Enough work for you, I have other ideas but I'll try them out locally, thanks a lot !

@Marsup Marsup merged commit e60376b into hapijs:master Nov 14, 2016
@WesTyler
Copy link
Contributor Author

Awesome, thanks for the feedback and guidance!

Marsup added a commit that referenced this pull request Nov 14, 2016
@Marsup
Copy link
Collaborator

Marsup commented Nov 14, 2016

Just pushed a small fix, just so you know what I was aiming for.

@WesTyler
Copy link
Contributor Author

Ah, I see. Cool 👍

@lock
Copy link

lock bot commented Jan 9, 2020

This thread has been automatically locked due to inactivity. Please open a new issue for related bugs or questions following the new issue template instructions.

@lock lock bot locked as resolved and limited conversation to collaborators Jan 9, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
feature New functionality or improvement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants