Skip does_not_contain on an absent value - #400
Open
youdie006 wants to merge 1 commit into
Open
Conversation
11cc0e9 generalised ValidateContains to a blanket Option<T> impl that returns true for None, so contains skips absent values the way length, email and url do. It did not touch does_not_contain.rs, which is still a plain negation of validate_contains, so negating that skip turns it into a failure. The derive usually hides this because number_options matches the literal spellings of Option in OPTIONS_TYPE and wraps the call, but a type alias defeats that inference and the exported ValidateDoesNotContain trait is wrong for Option either way. Add a defaulted contains_is_absent to ValidateContains, override it on Option, and let does_not_contain skip on it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
11cc0e9("Use generics and macros", #274) replaced the concreteimpl ValidateContains for Option<String>with a blanketimpl<T: ValidateContains> ValidateContains for Option<T>that returnstrueforNone-- socontainsskips absent values, the waylength,emailandurlalready do.git show 11cc0e9 --stattouches nine files and does not includedoes_not_contain.rs, which is still:Negating a skip turns it into a failure. So
does_not_containis the only validator in the crate that rejectsNone:Who actually hits it
The derive usually papers over this.
number_options(validator_derive/src/types.rs:153) matches only the literal spellings inOPTIONS_TYPE--"Option|","std|option|Option|","core|option|Option|"-- and wraps the call inif let Some(..). A type alias defeats that inference and the raw trait call is emitted:The same struct with
containsorlengthreturnsOk. AndValidateDoesNotContainis publicly exported, so the trait is wrong forOptionregardless of the derive.validator_derive_tests/tests/complex.rs:190(test_works_with_none_values) andtests/range.rs:262both assert thatNonepasses.contains/does_not_containsimply had noNonerow.The fix
ValidateContainsgains a defaultedcontains_is_absent()returningfalse;Option<T>overrides it;does_not_containskips on it.I kept the blanket
impl<T: ValidateContains> ValidateDoesNotContain for T. The alternative -- deleting it and mirroringcontains.rs's six concrete impls -- would break any downstream type that implementsValidateContainsand relies on gettingValidateDoesNotContainfor free, so I did not take it.Behaviour change
does_not_containon an absent value now passes instead of failing. No existing test row asserts the old behaviour, so nothing had to change.Verification
cargo fmt --checkclean.cargo clippy -p validator --all-targets -- -D warnings-- 0 errors.cargo test -p validator --lib62/62;does_not_contain6/6,contains4/4,complex5/5,nested18/18,range17/17.is_none_oris fine against the workspacerust-version = "1.88".I mutation-checked each site on its own:
does_not_containto the plain negationOption::contains_is_absent->falseOption::contains_is_absent->true, soSomeis skipped toois_none_orwith an equivalentmatchThe last is deliberate: it is the same predicate, and its passing shows the tests pin "an absent value is skipped" rather than the particular helper I reached for.
One thing I want to mention rather than leave buried. My first draft also forwarded
contains_is_absentthrough the&TandCowimpls. Mutating either of those survived, and the reason is that neither is reachable with anOption:Cow's bound (for<'a> &'a T: ValidateContains) admits onlystr/String, and(&opt).validate_does_not_contain(..)autoderefs to theOption<T>impl. Rather than ship two lines no test could pin, I removed them.Pre-existing failures I did not touch
cargo test -p validator_derive_tests --test compile_test(the trybuilduitest) fails identically on unmodifiedmaster-- I confirmed by stashing. Workspace-wide clippy also reports errors atmasterhere, which I believe is my local clippy 1.97 against your pinned 1.88 rather than anything real. Neither is related to these lines; I mention them so the failures are not read as mine.Disclosure: AI-assisted. I found and prepared this with an AI assistant, and I ran and verified everything above myself.