Skip to content

Case folding and numeric stringification are inconsistent between SDK local evaluation and the flags service #78019

Description

@haacked

While shipping starts_with/ends_with local evaluation across the SDKs (#72992), a pattern emerged: our case-insensitive property matching is inconsistent between the SDKs and the flags service, and within the flags service itself. Filing this so we can settle it once instead of per code review.

How the flags service folds case

In rust/feature-flags/src/properties/property_matching.rs:

  • icontains / starts_with / ends_with (and negations): to_ascii_lowercase(), ASCII-only. The code comment says it's a deliberate perf choice.
  • exact / is_not: full Unicode to_lowercase().

So the server already applies two different folding rules depending on operator.

What the SDKs use for the icontains/starts_with/ends_with family

SDK Folding Matches server?
PHP strtolower (ASCII-only)
Ruby downcase(:ascii) as of PostHog/posthog-ruby#229
Elixir ASCII-only in PostHog/posthog-elixir#192 (pending)
Python casefold() (most aggressive Unicode; "ss" matches "ß")
Go strings.ToLower (Unicode)
Node toLowerCase() (Unicode)
Rust SDK to_lowercase() (Unicode)
.NET OrdinalIgnoreCase (Unicode simple folding)

Concretely: person property "Äbc" with starts_with: "ä" matches locally in Python/Go/Node/Rust/.NET and does not match on /flags. Same flag, same person, different answer depending on evaluation path — exactly the divergence local evaluation is supposed to avoid.

Adjacent inconsistencies in the same code

  • The Rust SDK's exact operator uses eq_ignore_ascii_case while the server's exact is Unicode — the mirror image of the divergence above.
  • Numeric stringification: the server renders a float-typed 323.0 as "323.0" (serde Display), as do Ruby and Python, but Go and Node render "323", so a JSON 323.0 property matches ends_with: "3" locally but not remotely. .NET additionally used the host locale for decimal separators (3.14"3,14" on de-DE), fixed in fix: use invariant culture when stringifying property values for matching posthog-dotnet#270.

Options

  1. Standardize the SDKs on ASCII-only folding for the icontains/starts/ends family to match the server. PHP and Ruby are already there; the rest are one-line changes plus tests. The numeric stringification fixes for Go/Node belong in the same sweep.
  2. Make the server Unicode-aware and leave the SDKs alone. Friendlier for international text, but changes existing server behavior, and the ASCII choice was deliberate.
  3. Leave it and document the edge case.

I lean toward 1, plus an explicit decision on whether the server's Unicode exact/is_not is intentional. Non-ASCII values differing only by case are rare, but today the answer depends on which SDK and which operator you hit, and that's the worst version.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions