Skip to content

Commit 03c2285

Browse files
Copilotxperiandri
andauthored
Use CurrentCulture as default string filter fallback
Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com>
1 parent 18240a5 commit 03c2285

2 files changed

Lines changed: 7 additions & 6 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ This filter is mapped by the middleware into the `ObjectListFilter` discriminate
403403
- `Equals` and `Contains` keep the non-generic `System.Collections.IComparer` because they also support non-string comparable values in the public discriminated union.
404404
- When filters come from GraphQL input, suffix casing selects the string comparer automatically: lowercase suffixes use case-insensitive matching and capitalized suffixes use case-sensitive matching.
405405
- When the filtered field is a string and you construct the DU cases directly, pass a `StringComparer` instance in that comparer slot. `StringComparer` is accepted there because it also implements `System.Collections.IComparer`. Use `StringComparer.Ordinal` for case-sensitive matching and `StringComparer.OrdinalIgnoreCase` for case-insensitive matching.
406-
- The built-in case-sensitive operators (`===`, `=@@`, `@@=`, `@=@`) still use the default comparer behavior for you.
406+
- The built-in `=@@`, `@@=`, and string `@=@` operators keep the existing case-sensitive `CurrentCulture` behavior when no comparer is supplied. `===` continues to use the normal equality translation.
407407
- `Contains` is also used for collection membership checks, so the wrapped value type inside `FieldFilter<_>` stays `System.IComparable` instead of being limited to `string`.
408408
- If you were previously constructing `Equals`, `StartsWith`, `EndsWith`, or `Contains` directly, update those call sites to provide the comparer argument explicitly.
409409

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ type FieldFilter<'Val> = { FieldName : string; Value : 'Val }
1212
/// </summary>
1313
/// <remarks>
1414
/// String-based filters can carry a comparer. When the comparer is not provided by the default
15-
/// case-sensitive operators, the behavior is equivalent to using `StringComparer.Ordinal`.
15+
/// string operators, `StartsWith`, `EndsWith`, and string `Contains` preserve the existing
16+
/// case-sensitive `StringComparison.CurrentCulture` behavior.
1617
/// `StringComparer.OrdinalIgnoreCase` enables case-insensitive matching.
1718
/// When filters are provided through GraphQL input, lowercase string suffixes are interpreted
1819
/// as case-insensitive and capitalized suffixes are interpreted as case-sensitive.
@@ -230,7 +231,7 @@ module ObjectListFilter =
230231
|> Seq.head
231232

232233
/// Maps an IComparer to a StringComparison value.
233-
/// Returns ValueNone for null or Ordinal comparers (use default Expression.Equal path).
234+
/// Returns ValueNone only when the comparer is null or is not a recognized StringComparer.
234235
let private comparerToStringComparison (comparer : IComparer) =
235236
match comparer with
236237
| null -> ValueNone
@@ -324,11 +325,11 @@ module ObjectListFilter =
324325
| NonEnumerableCast ``type`` -> Expression.LessThanOrEqual ((unsafeConvertTo ``type`` ``member``), Expression.Constant f.Value)
325326
| StartsWith (f, comparer) ->
326327
let ``member`` = Expression.PropertyOrField (param, f.FieldName)
327-
let comparison = comparerToStringComparison (comparer :> IComparer) |> ValueOption.defaultValue StringComparison.Ordinal
328+
let comparison = comparerToStringComparison (comparer :> IComparer) |> ValueOption.defaultValue StringComparison.CurrentCulture
328329
Expression.Call (normalizeStringMemberExpr ``member``, StringStartsWithMethod, Expression.Constant f.Value, Expression.Constant comparison)
329330
| EndsWith (f, comparer) ->
330331
let ``member`` = Expression.PropertyOrField (param, f.FieldName)
331-
let comparison = comparerToStringComparison (comparer :> IComparer) |> ValueOption.defaultValue StringComparison.Ordinal
332+
let comparison = comparerToStringComparison (comparer :> IComparer) |> ValueOption.defaultValue StringComparison.CurrentCulture
332333
Expression.Call (normalizeStringMemberExpr ``member``, StringEndsWithMethod, Expression.Constant f.Value, Expression.Constant comparison)
333334

334335
| Contains (f, comparer) ->
@@ -367,7 +368,7 @@ module ObjectListFilter =
367368
| :? FieldInfo as field when field.FieldType |> isEnumerable -> callContains field.FieldType
368369
| _ ->
369370
let unwrappedValue = Helpers.unwrap f.Value
370-
let comparison = comparerToStringComparison comparer |> ValueOption.defaultValue StringComparison.Ordinal
371+
let comparison = comparerToStringComparison comparer |> ValueOption.defaultValue StringComparison.CurrentCulture
371372
Expression.Call (normalizeStringMemberExpr ``member``, StringContainsMethod, Expression.Constant (unwrappedValue :?> string, typeof<string>), Expression.Constant comparison)
372373
| In f when not (f.Value.IsEmpty) ->
373374
let ``member`` = Expression.PropertyOrField (param, f.FieldName)

0 commit comments

Comments
 (0)