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

Add support for truthy/falsy boolean values #998

Merged
merged 8 commits into from Oct 22, 2016
Merged

Conversation

WesTyler
Copy link
Contributor

@WesTyler WesTyler commented Oct 5, 2016

This commit resolves #991 by adding the following support:

  • Implements Joi.boolean().truthy() and Joi.boolean().falsy()
    • Both methods accept either a string, or an array of strings.
    • Both methods utilize Hoek.assert to ensure that the values passed are strings.
    • Both methods have 100% test coverage.
    • Both methods are documented in API.md.

@WesTyler WesTyler closed this Oct 5, 2016
@WesTyler WesTyler reopened this Oct 5, 2016
@@ -808,6 +810,24 @@ const boolean = Joi.boolean();
boolean.validate(true, (err, value) => { });
```

#### `boolean.truthy(value)`

Allows for additional strings to be considered valid boolean values in addition to default 'truthy' strings: ['true', 'yes', '1', 'on']. Accepts a string or an array of strings.
Copy link
Contributor

@DavidTPate DavidTPate Oct 5, 2016

Choose a reason for hiding this comment

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

Personally, I don't think this should be additional as I want to be able to be explicit about it. Instead I think this should override the valid truthy values instead of just add new ones.

The same goes for the falsey one below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I get that. It is currently (in this PR) implemented as additive, so that I didn't nuke the strings that are already type-coerced by the "vanilla" boolean schema type. Here's where the "additive" piece is in the code: https://github.com/hapijs/joi/pull/998/files#diff-3b86748616af947ebd7969eedec0062aR76

I'm totally down for overriding the default type-coerced strings. I was just trying to play nice with what was already in place. Just let me know what would be preferred and I will update accordingly.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I figured that was the case @WesTyler. It's definitely just my personal preference here not sure that one method is more correct than the other.

Not sure of the opinion that @Marsup has on this, but I definitely defer to him. Just wanted to state my preference.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Like I said in the issue, I don't mind it being a breaking change, so default true/false and additive calls to stay consistent with valid/invalid makes the most sense to me.


```js
const boolean = Joi.boolean().truthy('Y');
boolean.validate('Y', (err, value) => { });
Copy link
Contributor

Choose a reason for hiding this comment

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

Doing boolean.validate('Y', (err, value) => { }); // valid or something else might help to show that this works.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure thing. On it, thanks.

@AdriVanHoudt
Copy link
Contributor

Why only allow string values?

return value.toLowerCase();
});
}
else {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not just cast the value to an array if it isn't one currently? That way you don't have the code repeated. So before line 62 something like: truthyValue = Array.isArray(truthyValue) ? truthyValue : [];

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call. I'll do that along with the API.md update above. Thanks

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is actually inconsistent with how we do it everywhere else, see https://github.com/hapijs/joi/blob/master/lib/any.js#L235.

@WesTyler
Copy link
Contributor Author

WesTyler commented Oct 5, 2016

@AdriVanHoudt Simply because that was what was discussed in the feature request issue. And I thought a simple initial implementation would be best for a new feature PR. It can always be extended in the future if the need for non-string values arises.

@AdriVanHoudt
Copy link
Contributor

@WesTyler I see your point, although feature wise it would seem weird to limit it (if you are not involved with the original issue)

@Marsup
Copy link
Collaborator

Marsup commented Oct 5, 2016

Agreed with @AdriVanHoudt here, I thought my pov was clear in the original issue, sorry for that.

@WesTyler
Copy link
Contributor Author

WesTyler commented Oct 6, 2016

@Marsup Ah, gotcha. I misunderstood what you meant in the issue about it being a breaking change. Updating right now per your 3 comments above. Thanks for the input!

@WesTyler
Copy link
Contributor Author

WesTyler commented Oct 6, 2016

Now that I just pushed that though, I'm wondering if the ._inner.truthySet (and falsy) should utilize the Set class defined in the Any internals object? That way it can take advantage of the getter/setter and has, rather than relying on push and indexOf.

Thoughts?

@Marsup Marsup added feature New functionality or improvement breaking changes Change that can breaking existing code labels Oct 12, 2016
@Marsup Marsup self-assigned this Oct 12, 2016
@Marsup
Copy link
Collaborator

Marsup commented Oct 12, 2016

@WesTyler yes, this might be a good idea. Careful with cloning though, you'll have to implement a slice equivalent (https://github.com/hapijs/joi/blob/master/lib/any.js#L110) because _inner properties are meant to be arrays.

@Marsup
Copy link
Collaborator

Marsup commented Oct 12, 2016

This might also be a challenge for concat() and describe() which are currently lacking in the PR. Joi is a complex beast :)

@WesTyler
Copy link
Contributor Author

Alright, I extracted the Set class out of any.js into its own file, and implemented it for the boolean inners.

I also implemented Set.concat and Set.slice and added corresponding tests.

I added boolean-specific tests around schema.concat() and schema.describe() as well.


const Ref = require('./ref');

const Set = class {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you make this exports.Set ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

@WesTyler oh my, sorry, I was thinking module.exports = class, just bypassing the local constant.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Haha, my bad. Yeah I'll do it that way.

return result;
}

_truthy() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why did you split both truthy and falsy ?

Copy link
Contributor Author

@WesTyler WesTyler Oct 13, 2016

Choose a reason for hiding this comment

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

Just following the pattern set with _allow and allow in Any. I can un-split them if you prefer though; going to leave it as-is unless you specifically want them combined @Marsup :)


result.value = (value === 1 ? true
: (value === 0 ? false : value));
if (this._inner._truthySet.values().length) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

values() is already a copy, so I guess description.truthyValues = this._inner._truthySet.values() is enough.

slice() {

const newSet = new Set();
newSet.merge(this);
Copy link
Collaborator

Choose a reason for hiding this comment

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

merge will be slower, a simple newSet._set = this._set.slice() would be better.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alright cool. Wasn't sure if it was kosher to access the private _set from outside the class. Definitely agree with you though. I'm on it, along with other requested revisions.

concat(source) {

const newSet = new Set();
newSet.merge(this);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same here, probably newSet._set = this._set.concat(source._set).

@WesTyler
Copy link
Contributor Author

I thought I had already left this comment, but I don't see it now. So here goes again.

The Set class has been refactored per change requests above. I left boolean.truthy/falsy as-is for now. Not a big deal for me to combine _truthy/truthy and _falsy/falsy, I just was trying to mirror _allow/allow as closely as possible.

@Marsup
Copy link
Collaborator

Marsup commented Oct 13, 2016

Can't remember why _allow is this way but I see no reason to do that.

@WesTyler
Copy link
Contributor Author

Alright, I'll update truthy/falsy then. Would you like me to go ahead and re-combine _allow/allow in Any while I'm at it?

@Marsup
Copy link
Collaborator

Marsup commented Oct 13, 2016

Nah let's keep this PR focused, I'll do it someday :)

@WesTyler
Copy link
Contributor Author

Alright, pushed the removal of Boolean's _truthy/_falsy pattern.

@WesTyler
Copy link
Contributor Author

@Marsup Is there anything else you're waiting on me to fix/update/refactor?

Not trying to rush you, just making sure you're not waiting on me :)

@Marsup
Copy link
Collaborator

Marsup commented Oct 22, 2016

Almost perfect, I'll deal with the bits I don't like myself. Thanks a lot !
Know that this won't be published right away though, I'd like to tackle the other pending breaking change on the same release.

@Marsup Marsup merged commit 2eed6a8 into hapijs:master Oct 22, 2016
Marsup added a commit that referenced this pull request Oct 22, 2016
@Marsup
Copy link
Collaborator

Marsup commented Oct 22, 2016

LMK if you see anything shocking to you in my cleanup.

@WesTyler
Copy link
Contributor Author

Awesome! Thanks for the patience and guidance on this! :D
And yeah, I figured you would want to bundle several breaking changes in together before publishing. 👍

Cleanup makes sense to me.

@Marsup Marsup added this to the 10.0.0 milestone Nov 6, 2016
@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
breaking changes Change that can breaking existing code feature New functionality or improvement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feature Request - Boolean type cast
4 participants