Repository navigation
fix(validation): consider mapper convertors in IsValid (#337) - #345
Merged
Merged
Conversation
alirezanet
force-pushed
the
fix/337-isvalid-convertors
branch
from
September 17, 2026 10:30
7441878 to
9c79e2c
Compare
alirezanet
force-pushed
the
fix/337-isvalid-convertors
branch
2 times, most recently
from
September 17, 2026 11:04
fc4edac to
06cdb4e
Compare
IsValid validated the raw filter value against the mapped property type and ignored the map's convertor, while BaseQueryBuilder runs the convertor first and only then converts the type. A filter the query builder executes fine was therefore reported as invalid, for example a "d-2" relative date on a DateTime property behind a custom datetime parser. The convertor now runs before the type check. A string result goes through the existing type check, the only case the builder converts. Any other result is used as the value of the condition, so the question is whether the builder can use it, and CanBeUsedAsValue answers that by calling the builder's own value machinery rather than reproducing its rules: - Expression.Constant(value, propertyType) for plain LINQ, which needs the exact type and rejects null for a non-nullable property - GridifyTypeBuilder.CreateNewObject for the Entity Framework compatibility layer, which assigns through reflection and so widens some types Two earlier revisions of this change tried to reproduce those rules instead and both got them wrong, reporting filters as valid that throw when the query is built: first every non-string result through a ToString round trip, then every non-string result under the EF layer, on the mistaken premise that reflection accepts anything. Reflection widens int into long and int into an enum, and takes null for a non-nullable property, but rejects long into int, double into decimal, int into decimal, DateTime into int and Guid into int. Running the real code path removes the guesswork. A composite map is ORed over all of its expressions, so the value has to work for every one of them, not just the first that IGMap.To exposes. A convertor that throws is a validation error, the builder does not catch it either. Validation messages keep quoting the value the caller sent rather than what the convertor produced, since those messages are the payload an API returns. IsValid_WithConvertor_AgreesWithWhetherTheQueryCanBeBuilt pins the verdict to whether ApplyFiltering can build the query across thirty convertor-result and property-type combinations, on both value paths. Docs: the IsValid guide gains a section on convertor maps, including the note that IsValid now invokes the convertor.
alirezanet
force-pushed
the
fix/337-isvalid-convertors
branch
from
September 17, 2026 11:27
06cdb4e to
98e6733
Compare
This was referenced Sep 18, 2026
This was referenced Sep 28, 2026
This was referenced Oct 22, 2026
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.
Fixes #337
The bug
IsValidcompared the raw filter value against the mapped property's CLR type and never looked at the map's convertor, whileBaseQueryBuilder.BuildQueryruns the convertor first and only then converts the type.So validation and execution disagreed: a filter the query builder happily executes was reported invalid.
The fix
The convertor runs before the type check. A string result goes through the existing type check - the only case
BuildQueryconverts. Any other result becomes the value of the condition directly, so the question is simply can the builder use it, andCanBeUsedAsValueanswers that by calling the builder's own value machinery instead of reproducing its rules:Expression.Constant(value, propertyType)- needs the exact type, rejectsnullfor a non-nullable propertyEntityFrameworkCompatibilityLayerGridifyTypeBuilder.CreateNewObject(...)- assigns through reflection, so it widens some typesA convertor that throws is a validation error; the builder does not catch it either. A
CompositeGMapis ORed over all of its expressions, so the value must work for every one, not just the first thatIGMap.Toexposes.Why it calls the real code instead of checking types itself
Two earlier revisions tried to model the builder's rules and both got them wrong, reporting filters as valid that throw
ArgumentExceptionwhen the query is built:converted.ToString()round-trip;That premise is false.
PropertyInfo.SetValuewidensint→longandint→enum and takesnullfor a non-nullable property, but rejectslong→int,double→decimal,int→decimal,DateTime→intandGuid→int. Running the real path removes the guesswork, and it is less code than the rules it replaces.Verified agreement
IsValid_WithConvertor_AgreesWithWhetherTheQueryCanBeBuiltasserts the expected verdict and that the verdict equals whetherApplyFilteringcan actually build the query, over 30 combinations spanning both value paths:long→long,int→int,enum→enum,DateTime→DateTimeint→long,int→enumnull→int?,null→stringnull→int,null→DateTimelong→int,double→decimal,int→decimal,DateTime→int,Guid→intintintBehaviour changes (for the release note)
trueand now reportsfalse. Those filters threw at execution anyway.falseinstead oftrue.IsValidnow invokes the map's convertor. A convertor with side effects, or an expensive one, runs during validation as well as during filtering.IsValid_WithConvertor_InvokesItOncePerValuepins the count to whatApplyFilteringdoes.GridifyTypeBuilder's type cache, which the builder was going to populate anyway.if (gMap.Convertor != null).Tests
Full solution: 675 passing (
Gridify.Tests410,Gridify.Elasticsearch.Tests213, EF integration 11 / 12 / 29). Builds for all six TFMs includingnetstandard2.0.Known limitations, deliberately not addressed here
Each of these is a disagreement between
IsValidand execution that exists onmastertoo, is not about convertors, and would change verdicts for maps without a convertor. Worth separate issues.Like/NotLikeon a non-string property validatestrueand always throws -IsValid("Int=*5")on anintproperty istrueonmasterand here. Considering convertors widens the reach of this hole to three more inputs (a convertor map on a non-string property with=*/!*), which previously happened to be rejected because the raw value failed the type check. Fixing it properly means validating operator/property-type compatibility for every map.ExtractPropertyTypereturnsnullfor aSelect(...)or indexer body, so the loop skips the value entirely.AllowNullSearchis read from the wrong place:IsValidreadsGridifyGlobalConfiguration.AllowNullSearchwhileBuildQueryreadsmapper.Configuration.AllowNullSearch, so a per-mapper setting validates under the wrong rule - in both directions.ElasticsearchQueryBuilderuses the convertor's result only for type dispatch and emits the raw token for most operators, so a convertor map can throwFormatExceptionor silently query the unconverted value.master's stricterIsValidhappened to reject some of those filters. The honest fix is in that builder; its 213 tests pass unchanged here.