From 249e46c09bf9092280062bb5a0cafd613c9bc7a5 Mon Sep 17 00:00:00 2001 From: Paul DeVito Date: Thu, 1 Oct 2026 17:05:10 +0300 Subject: [PATCH] fix(config)!: apply a property max depth only to the property and its paths HasMaxDepth matched every path that starts with the property name, so HasMaxDepth on Address also applied to AddressBackup.State. A path could skip the global MaxPropertyDepth if its name started with a property that has a looser limit. Match the property name or the name followed by a dot. BREAKING CHANGE: HasMaxDepth on a property no longer applies to another property whose name starts with the same text. That property uses the global MaxPropertyDepth. --- QueryKit.UnitTests/PropertyDepthTests.cs | 9 +++++---- QueryKit/QueryKitPropertyMappings.cs | 7 ++++--- README.md | 2 ++ 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/QueryKit.UnitTests/PropertyDepthTests.cs b/QueryKit.UnitTests/PropertyDepthTests.cs index 8301fbc..35659d2 100644 --- a/QueryKit.UnitTests/PropertyDepthTests.cs +++ b/QueryKit.UnitTests/PropertyDepthTests.cs @@ -257,7 +257,7 @@ public void filter_root_property_allowed_when_max_depth_is_0() } [Fact] - public void filter_per_property_max_depth_applies_to_a_property_that_starts_with_its_name() + public void filter_per_property_max_depth_does_not_apply_to_a_property_that_starts_with_its_name() { var input = """AddressBackup.State == "x" """; var config = new QueryKitConfiguration(settings => @@ -267,11 +267,12 @@ public void filter_per_property_max_depth_applies_to_a_property_that_starts_with }); var act = () => FilterParser.ParseFilter(input, config); - act.Should().NotThrow(); + act.Should().Throw() + .WithMessage("*AddressBackup.State*depth of 1*maximum allowed depth of 0*"); } [Fact] - public void sort_per_property_max_depth_applies_to_a_property_that_starts_with_its_name() + public void sort_per_property_max_depth_does_not_apply_to_a_property_that_starts_with_its_name() { var input = "AddressBackup.State"; var config = new QueryKitConfiguration(settings => @@ -281,7 +282,7 @@ public void sort_per_property_max_depth_applies_to_a_property_that_starts_with_i }); var act = () => SortParser.ParseSort(input, config); - act.Should().NotThrow(); + act.Should().Throw(); } private class Owner diff --git a/QueryKit/QueryKitPropertyMappings.cs b/QueryKit/QueryKitPropertyMappings.cs index 9408114..a0aec17 100644 --- a/QueryKit/QueryKitPropertyMappings.cs +++ b/QueryKit/QueryKitPropertyMappings.cs @@ -412,11 +412,12 @@ private static string GetOperator(ExpressionType nodeType) if (string.IsNullOrEmpty(propertyPath)) return null; - // Check if the property path starts with any configured property that has MaxDepth + // Check if the property path is, or is under, any configured property that has MaxDepth foreach (var mapping in _propertyMappings.Values) { - if (mapping.MaxDepth.HasValue && - propertyPath.StartsWith(mapping.Name ?? "", StringComparison.OrdinalIgnoreCase)) + if (mapping.MaxDepth.HasValue && !string.IsNullOrEmpty(mapping.Name) && + (propertyPath.Equals(mapping.Name, StringComparison.OrdinalIgnoreCase) || + propertyPath.StartsWith(mapping.Name + ".", StringComparison.OrdinalIgnoreCase))) { return mapping.MaxDepth; } diff --git a/README.md b/README.md index 2dc63d2..9bc8267 100644 --- a/README.md +++ b/README.md @@ -787,6 +787,8 @@ var config = new QueryKitConfiguration(config => // "Publisher.Address.City" throws (depth 2, exceeds global limit of 1) ``` +The override applies only to the property and the paths below it. For example, `HasMaxDepth` on `Address` does not apply to `AddressBackup.State`, so `AddressBackup.State` uses the global limit. + **Depth Calculation:** - `Title` = depth 0 (root property) - `Author.Name` = depth 1 (one level of nesting)