Skip to content

Commit 2dbd474

Browse files
committed
Update filters to use CurrentCulture string comparison
Updated all string comparison operations in `ObjectListFilter` and filter parsing logic to use `StringComparer.CurrentCulture` or `StringComparer.CurrentCultureIgnoreCase` instead of `Ordinal`/`OrdinalIgnoreCase`. Adjusted related test expectations to match. This ensures string-based filters now respect the current culture's case rules.
1 parent 1f7b012 commit 2dbd474

3 files changed

Lines changed: 30 additions & 30 deletions

File tree

src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ type FieldFilter<'Val> = { FieldName : string; Value : 'Val }
1414
/// String-based filters can carry a comparer. When the comparer is not provided by the default
1515
/// string operators, `StartsWith`, `EndsWith`, and string `Contains` preserve the existing
1616
/// case-sensitive `StringComparison.CurrentCulture` behavior.
17-
/// `StringComparer.OrdinalIgnoreCase` enables case-insensitive matching.
17+
/// `StringComparer.CurrentCultureIgnoreCase` enables case-insensitive matching.
1818
/// When filters are provided through GraphQL input, lowercase string suffixes are interpreted
1919
/// as case-insensitive and capitalized suffixes are interpreted as case-sensitive.
2020
/// </remarks>
@@ -139,16 +139,16 @@ module ObjectListFilter =
139139
let ( !!! ) filter = Not filter
140140

141141
/// Creates a new ObjectListFilter representing a case-insensitive EQUALS operation on a string value.
142-
let ( ===~ ) fname (value : string) = Equals ({ FieldName = fname; Value = value }, StringComparer.OrdinalIgnoreCase)
142+
let ( ===~ ) fname (value : string) = Equals ({ FieldName = fname; Value = value }, StringComparer.CurrentCultureIgnoreCase)
143143

144144
/// Creates a new ObjectListFilter representing a case-insensitive STARTS WITH operation on a string value.
145-
let ( =@@~ ) fname (value : string) = StartsWith ({ FieldName = fname; Value = value }, StringComparer.OrdinalIgnoreCase)
145+
let ( =@@~ ) fname (value : string) = StartsWith ({ FieldName = fname; Value = value }, StringComparer.CurrentCultureIgnoreCase)
146146

147147
/// Creates a new ObjectListFilter representing a case-insensitive ENDS WITH operation on a string value.
148-
let ( @@=~ ) fname (value : string) = EndsWith ({ FieldName = fname; Value = value }, StringComparer.OrdinalIgnoreCase)
148+
let ( @@=~ ) fname (value : string) = EndsWith ({ FieldName = fname; Value = value }, StringComparer.CurrentCultureIgnoreCase)
149149

150150
/// Creates a new ObjectListFilter representing a case-insensitive CONTAINS operation on a string value.
151-
let ( @=@~ ) fname (value : string) = Contains ({ FieldName = fname; Value = value }, StringComparer.OrdinalIgnoreCase)
151+
let ( @=@~ ) fname (value : string) = Contains ({ FieldName = fname; Value = value }, StringComparer.CurrentCultureIgnoreCase)
152152

153153
let private genericWhereMethod =
154154
typeof<Queryable>.GetMethods ()

src/FSharp.Data.GraphQL.Server.Middleware/SchemaDefinitions.fs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,24 @@ let rec private coerceObjectListFilterInput (variables : Variables) inputValue :
2727
let prefix (suffix : string) (s : string) = s.Substring (0, s.Length - suffix.Length)
2828
// Phase 1: case-sensitive string ops – match original string against capitalized/uppercase suffixes
2929
match s with
30-
| s when s.EndsWith FilterSuffixConstants.CS.EndsWithSuffix && s.Length > FilterSuffixConstants.CS.EndsWithSuffix.Length -> EndsWith (prefix FilterSuffixConstants.CS.EndsWithSuffix s, StringComparer.Ordinal)
31-
| s when s.EndsWith FilterSuffixConstants.CS.EWSuffix && s.Length > FilterSuffixConstants.CS.EWSuffix.Length -> EndsWith (prefix FilterSuffixConstants.CS.EWSuffix s, StringComparer.Ordinal)
32-
| s when s.EndsWith FilterSuffixConstants.CS.StartsWithSuffix && s.Length > FilterSuffixConstants.CS.StartsWithSuffix.Length -> StartsWith (prefix FilterSuffixConstants.CS.StartsWithSuffix s, StringComparer.Ordinal)
33-
| s when s.EndsWith FilterSuffixConstants.CS.SWSuffix && s.Length > FilterSuffixConstants.CS.SWSuffix.Length -> StartsWith (prefix FilterSuffixConstants.CS.SWSuffix s, StringComparer.Ordinal)
34-
| s when s.EndsWith FilterSuffixConstants.CS.ContainsSuffix && s.Length > FilterSuffixConstants.CS.ContainsSuffix.Length -> Contains (prefix FilterSuffixConstants.CS.ContainsSuffix s, StringComparer.Ordinal)
35-
| s when s.EndsWith FilterSuffixConstants.CS.EqualsSuffix && s.Length > FilterSuffixConstants.CS.EqualsSuffix.Length -> StringEquals (prefix FilterSuffixConstants.CS.EqualsSuffix s, StringComparer.Ordinal)
36-
| s when s.EndsWith FilterSuffixConstants.CS.EQSuffix && s.Length > FilterSuffixConstants.CS.EQSuffix.Length -> StringEquals (prefix FilterSuffixConstants.CS.EQSuffix s, StringComparer.Ordinal)
30+
| s when s.EndsWith FilterSuffixConstants.CS.EndsWithSuffix && s.Length > FilterSuffixConstants.CS.EndsWithSuffix.Length -> EndsWith (prefix FilterSuffixConstants.CS.EndsWithSuffix s, StringComparer.CurrentCulture)
31+
| s when s.EndsWith FilterSuffixConstants.CS.EWSuffix && s.Length > FilterSuffixConstants.CS.EWSuffix.Length -> EndsWith (prefix FilterSuffixConstants.CS.EWSuffix s, StringComparer.CurrentCulture)
32+
| s when s.EndsWith FilterSuffixConstants.CS.StartsWithSuffix && s.Length > FilterSuffixConstants.CS.StartsWithSuffix.Length -> StartsWith (prefix FilterSuffixConstants.CS.StartsWithSuffix s, StringComparer.CurrentCulture)
33+
| s when s.EndsWith FilterSuffixConstants.CS.SWSuffix && s.Length > FilterSuffixConstants.CS.SWSuffix.Length -> StartsWith (prefix FilterSuffixConstants.CS.SWSuffix s, StringComparer.CurrentCulture)
34+
| s when s.EndsWith FilterSuffixConstants.CS.ContainsSuffix && s.Length > FilterSuffixConstants.CS.ContainsSuffix.Length -> Contains (prefix FilterSuffixConstants.CS.ContainsSuffix s, StringComparer.CurrentCulture)
35+
| s when s.EndsWith FilterSuffixConstants.CS.EqualsSuffix && s.Length > FilterSuffixConstants.CS.EqualsSuffix.Length -> StringEquals (prefix FilterSuffixConstants.CS.EqualsSuffix s, StringComparer.CurrentCulture)
36+
| s when s.EndsWith FilterSuffixConstants.CS.EQSuffix && s.Length > FilterSuffixConstants.CS.EQSuffix.Length -> StringEquals (prefix FilterSuffixConstants.CS.EQSuffix s, StringComparer.CurrentCulture)
3737
| _ ->
3838
// Phase 2: case-insensitive string ops and numeric ops – lower-case before matching
3939
let s = s.ToLowerInvariant ()
4040
match s with
41-
| s when s.EndsWith FilterSuffixConstants.CI.EndsWithSuffix && s.Length > FilterSuffixConstants.CI.EndsWithSuffix.Length -> EndsWith (prefix FilterSuffixConstants.CI.EndsWithSuffix s, StringComparer.OrdinalIgnoreCase)
42-
| s when s.EndsWith FilterSuffixConstants.CI.EWSuffix && s.Length > FilterSuffixConstants.CI.EWSuffix.Length -> EndsWith (prefix FilterSuffixConstants.CI.EWSuffix s, StringComparer.OrdinalIgnoreCase)
43-
| s when s.EndsWith FilterSuffixConstants.CI.StartsWithSuffix && s.Length > FilterSuffixConstants.CI.StartsWithSuffix.Length -> StartsWith (prefix FilterSuffixConstants.CI.StartsWithSuffix s, StringComparer.OrdinalIgnoreCase)
44-
| s when s.EndsWith FilterSuffixConstants.CI.SWSuffix && s.Length > FilterSuffixConstants.CI.SWSuffix.Length -> StartsWith (prefix FilterSuffixConstants.CI.SWSuffix s, StringComparer.OrdinalIgnoreCase)
45-
| s when s.EndsWith FilterSuffixConstants.CI.ContainsSuffix && s.Length > FilterSuffixConstants.CI.ContainsSuffix.Length -> Contains (prefix FilterSuffixConstants.CI.ContainsSuffix s, StringComparer.OrdinalIgnoreCase)
46-
| s when s.EndsWith FilterSuffixConstants.CI.EqualsSuffix && s.Length > FilterSuffixConstants.CI.EqualsSuffix.Length -> StringEquals (prefix FilterSuffixConstants.CI.EqualsSuffix s, StringComparer.OrdinalIgnoreCase)
47-
| s when s.EndsWith FilterSuffixConstants.CI.EQSuffix && s.Length > FilterSuffixConstants.CI.EQSuffix.Length -> StringEquals (prefix FilterSuffixConstants.CI.EQSuffix s, StringComparer.OrdinalIgnoreCase)
41+
| s when s.EndsWith FilterSuffixConstants.CI.EndsWithSuffix && s.Length > FilterSuffixConstants.CI.EndsWithSuffix.Length -> EndsWith (prefix FilterSuffixConstants.CI.EndsWithSuffix s, StringComparer.CurrentCultureIgnoreCase)
42+
| s when s.EndsWith FilterSuffixConstants.CI.EWSuffix && s.Length > FilterSuffixConstants.CI.EWSuffix.Length -> EndsWith (prefix FilterSuffixConstants.CI.EWSuffix s, StringComparer.CurrentCultureIgnoreCase)
43+
| s when s.EndsWith FilterSuffixConstants.CI.StartsWithSuffix && s.Length > FilterSuffixConstants.CI.StartsWithSuffix.Length -> StartsWith (prefix FilterSuffixConstants.CI.StartsWithSuffix s, StringComparer.CurrentCultureIgnoreCase)
44+
| s when s.EndsWith FilterSuffixConstants.CI.SWSuffix && s.Length > FilterSuffixConstants.CI.SWSuffix.Length -> StartsWith (prefix FilterSuffixConstants.CI.SWSuffix s, StringComparer.CurrentCultureIgnoreCase)
45+
| s when s.EndsWith FilterSuffixConstants.CI.ContainsSuffix && s.Length > FilterSuffixConstants.CI.ContainsSuffix.Length -> Contains (prefix FilterSuffixConstants.CI.ContainsSuffix s, StringComparer.CurrentCultureIgnoreCase)
46+
| s when s.EndsWith FilterSuffixConstants.CI.EqualsSuffix && s.Length > FilterSuffixConstants.CI.EqualsSuffix.Length -> StringEquals (prefix FilterSuffixConstants.CI.EqualsSuffix s, StringComparer.CurrentCultureIgnoreCase)
47+
| s when s.EndsWith FilterSuffixConstants.CI.EQSuffix && s.Length > FilterSuffixConstants.CI.EQSuffix.Length -> StringEquals (prefix FilterSuffixConstants.CI.EQSuffix s, StringComparer.CurrentCultureIgnoreCase)
4848
| s when s.EndsWith FilterSuffixConstants.GreaterThanOrEqualSuffix && s.Length > FilterSuffixConstants.GreaterThanOrEqualSuffix.Length -> GreaterThanOrEqual (prefix FilterSuffixConstants.GreaterThanOrEqualSuffix s)
4949
| s when s.EndsWith FilterSuffixConstants.GTESuffix && s.Length > FilterSuffixConstants.GTESuffix.Length -> GreaterThanOrEqual (prefix FilterSuffixConstants.GTESuffix s)
5050
| s when s.EndsWith FilterSuffixConstants.GreaterThanSuffix && s.Length > FilterSuffixConstants.GreaterThanSuffix.Length -> GreaterThan (prefix FilterSuffixConstants.GreaterThanSuffix s)

tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,7 @@ let ``Object list filter: must return filter information in Metadata`` () =
599599
]
600600
]
601601
let expectedFilter : KeyValuePair<obj list, _> =
602-
kvp ([ "A"; "s" ]) (And (Equals ({ FieldName = "id"; Value = 2L }, null), StartsWith ({ FieldName = "value"; Value = "A" }, StringComparer.OrdinalIgnoreCase)))
602+
kvp ([ "A"; "s" ]) (And (Equals ({ FieldName = "id"; Value = 2L }, null), StartsWith ({ FieldName = "value"; Value = "A" }, StringComparer.CurrentCultureIgnoreCase)))
603603
let result = execute query
604604

605605
ensureDirect result <| fun data errors ->
@@ -647,7 +647,7 @@ let ``Object list filter: Must return AND filter information in Metadata`` () =
647647
]
648648
]
649649
let expectedFilter : KeyValuePair<obj list, _> =
650-
kvp ([ "A"; "subjects" ]) (And (StartsWith ({ FieldName = "value"; Value = "3" }, StringComparer.OrdinalIgnoreCase), Equals ({ FieldName = "id"; Value = 6L }, null)))
650+
kvp ([ "A"; "subjects" ]) (And (StartsWith ({ FieldName = "value"; Value = "3" }, StringComparer.CurrentCultureIgnoreCase), Equals ({ FieldName = "id"; Value = 6L }, null)))
651651
let result = execute query
652652

653653
ensureDirect result <| fun data errors ->
@@ -693,7 +693,7 @@ let ``Object list filter: Must return OR filter information in Metadata`` () =
693693
]
694694
]
695695
let expectedFilter : KeyValuePair<obj list, _> =
696-
kvp ([ "A"; "subjects" ]) (Or (StartsWith ({ FieldName = "value"; Value = "3" }, StringComparer.OrdinalIgnoreCase), Equals ({ FieldName = "id"; Value = 6L }, null)))
696+
kvp ([ "A"; "subjects" ]) (Or (StartsWith ({ FieldName = "value"; Value = "3" }, StringComparer.CurrentCultureIgnoreCase), Equals ({ FieldName = "id"; Value = 6L }, null)))
697697
let result = execute query
698698

699699
ensureDirect result <| fun data errors ->
@@ -785,7 +785,7 @@ let ``Object list filter: Must return Contains filter information in Metadata``
785785
]
786786
]
787787
let expectedFilter : KeyValuePair<obj list, _> =
788-
kvp ([ "A"; "subjects" ]) (Contains ({ FieldName = "value"; Value = "3" }, StringComparer.OrdinalIgnoreCase))
788+
kvp ([ "A"; "subjects" ]) (Contains ({ FieldName = "value"; Value = "3" }, StringComparer.CurrentCultureIgnoreCase))
789789
let result = execute query
790790

791791
ensureDirect result <| fun data errors ->
@@ -831,7 +831,7 @@ let ``Object list filter: Must return NOT filter information in Metadata`` () =
831831
]
832832
]
833833
let expectedFilter : KeyValuePair<obj list, _> =
834-
kvp ([ "A"; "subjects" ]) (Not (StartsWith ({ FieldName = "value"; Value = "3" }, StringComparer.OrdinalIgnoreCase)))
834+
kvp ([ "A"; "subjects" ]) (Not (StartsWith ({ FieldName = "value"; Value = "3" }, StringComparer.CurrentCultureIgnoreCase)))
835835
let result = execute query
836836

837837
ensureDirect result <| fun data errors ->
@@ -879,7 +879,7 @@ let ``Object list filter: Must return filter information in Metadata when suppli
879879
do
880880
let notStartsFilter = """{ "not": { "value_starts_with": "3" } }""" |> JsonDocument.Parse |> _.RootElement
881881
let variables = ImmutableDictionary<string, JsonElement>.Empty.Add ("filter", notStartsFilter)
882-
let filter = Not (StartsWith ({ FieldName = "value"; Value = "3" }, StringComparer.OrdinalIgnoreCase))
882+
let filter = Not (StartsWith ({ FieldName = "value"; Value = "3" }, StringComparer.CurrentCultureIgnoreCase))
883883
let expectedFilter : KeyValuePair<obj list, _> = kvp ([ "A"; "subjects" ]) (filter)
884884
let result = executeAndVerifyFilter (query, variables, filter)
885885

@@ -891,7 +891,7 @@ let ``Object list filter: Must return filter information in Metadata when suppli
891891
do
892892
let notEndsFilter = """{ "not": { "value_ends_with": "2" } }""" |> JsonDocument.Parse |> _.RootElement
893893
let variables = ImmutableDictionary<string, JsonElement>.Empty.Add ("filter", notEndsFilter)
894-
let filter = Not (EndsWith ({ FieldName = "value"; Value = "2" }, StringComparer.OrdinalIgnoreCase))
894+
let filter = Not (EndsWith ({ FieldName = "value"; Value = "2" }, StringComparer.CurrentCultureIgnoreCase))
895895
let expectedFilter : KeyValuePair<obj list, _> = kvp ([ "A"; "subjects" ]) (filter)
896896
let result = executeAndVerifyFilter (query, variables, filter)
897897

@@ -903,7 +903,7 @@ let ``Object list filter: Must return filter information in Metadata when suppli
903903
do
904904
let notStartsFilter = """{ "not": { "value_sw": "3" } }""" |> JsonDocument.Parse |> _.RootElement
905905
let variables = ImmutableDictionary<string, JsonElement>.Empty.Add ("filter", notStartsFilter)
906-
let filter = Not (StartsWith ({ FieldName = "value"; Value = "3" }, StringComparer.OrdinalIgnoreCase))
906+
let filter = Not (StartsWith ({ FieldName = "value"; Value = "3" }, StringComparer.CurrentCultureIgnoreCase))
907907
let expectedFilter : KeyValuePair<obj list, _> = kvp ([ "A"; "subjects" ]) (filter)
908908
let result = executeAndVerifyFilter (query, variables, filter)
909909

@@ -915,7 +915,7 @@ let ``Object list filter: Must return filter information in Metadata when suppli
915915
do
916916
let notEndsFilter = """{ "not": { "value_ew": "2" } }""" |> JsonDocument.Parse |> _.RootElement
917917
let variables = ImmutableDictionary<string, JsonElement>.Empty.Add ("filter", notEndsFilter)
918-
let filter = Not (EndsWith ({ FieldName = "value"; Value = "2" }, StringComparer.OrdinalIgnoreCase))
918+
let filter = Not (EndsWith ({ FieldName = "value"; Value = "2" }, StringComparer.CurrentCultureIgnoreCase))
919919
let expectedFilter : KeyValuePair<obj list, _> = kvp ([ "A"; "subjects" ]) (filter)
920920
let result = executeAndVerifyFilter (query, variables, filter)
921921

@@ -1023,7 +1023,7 @@ let ``Object list filter: Must return filter information in Metadata when suppli
10231023
do
10241024
let notContainsFilter = """{ "not": { "value_contains": "A" } }""" |> JsonDocument.Parse |> _.RootElement
10251025
let variables = ImmutableDictionary<string, JsonElement>.Empty.Add ("filter", notContainsFilter)
1026-
let filter = Not (Contains ({ FieldName = "value"; Value = "A" }, StringComparer.OrdinalIgnoreCase))
1026+
let filter = Not (Contains ({ FieldName = "value"; Value = "A" }, StringComparer.CurrentCultureIgnoreCase))
10271027
let expectedFilter : KeyValuePair<obj list, _> = kvp ([ "A"; "subjects" ]) (filter)
10281028
let result = executeAndVerifyFilter (query, variables, filter)
10291029

@@ -1084,7 +1084,7 @@ let ``Object list filter: Must parse filter that references variables`` () =
10841084
do
10851085
let filterValue = "3" |> JsonDocument.Parse |> _.RootElement
10861086
let variables = ImmutableDictionary<string, JsonElement>.Empty.Add ("filter", filterValue)
1087-
let filter = (StartsWith ({ FieldName = "value"; Value = "3" }, StringComparer.OrdinalIgnoreCase))
1087+
let filter = (StartsWith ({ FieldName = "value"; Value = "3" }, StringComparer.CurrentCultureIgnoreCase))
10881088
let expectedFilter : KeyValuePair<obj list, _> = kvp ([ "A"; "subjects" ]) (filter)
10891089
let result = executeAndVerifyFilter (query, variables, filter)
10901090

0 commit comments

Comments
 (0)