Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

flagd-Compat Operators

Two operators specified by the OpenFeature flagd in-process provider (fractional, semantic version) for feature-flag targeting. Implemented to match the canonical Go evaluator byte-for-byte, so a flag definition that works under any flagd provider will produce identical variants here.

Cargo feature: flagd. Off by default; opt in via:

datalogic-rs = { version = "5", features = ["flagd"] }

Both operators return null on malformed input (wrong arg count, unparseable version, missing targeting context, etc.) rather than raising. flagd’s evaluator observes the null and falls back to the flag’s default variant; non-flagd callers can compose with ?? or if for the same effect.

fractional

Deterministic percentage bucketing for A/B tests and gradual rollouts. Buckets are sticky per bucketing key: the same input always lands in the same variant across runs.

Reference: flagd Fractional spec

Algorithm. MurmurHash3 x86-32 of the bucketing key, then bucket = (hash * total_weight) >> 32 and walk cumulative integer weight bands. Identical to the Go evaluator’s core/pkg/evaluator/fractional.go. The hash is vendored inline (~30 LOC) for portability across every target.

Two argument shapes:

1. Explicit bucketing key

The first argument evaluates to a string; the remaining args are [variant, weight] pairs.

{
  "fractional": [
    { "cat": [{ "var": "$flagd.flagKey" }, { "var": "email" }] },
    ["red",   50],
    ["blue",  20],
    ["green", 30]
  ]
}

The canonical pattern concatenates $flagd.flagKey + email so the same email gets different variants on different flags; users aren’t always in the same cohort across your whole product.

2. Implicit bucketing key

Omit the first argument, or let it evaluate to null (for example a missing var). Any other non-string first argument, such as a number or boolean, is parsed as a bucket definition and, not being an array, makes the whole call return null. The bucketing key is built from the root context as flagKey + targetingKey (the order the flagd Go evaluator uses):

{
  "fractional": [
    ["new-ui", 50],
    ["old-ui", 50]
  ]
}

targetingKey (a non-empty string) is required. $flagd.flagKey is optional: when it is absent the key is the targetingKey alone, which still buckets deterministically but loses the per-flag cohort separation. flagd in-process providers stamp both onto the context as:

{
  "targetingKey": "alice@example.com",
  "$flagd": { "flagKey": "header-color" }
}

Missing, empty, or non-string targetingKey in implicit form returns null: there’s no key to hash and flagd’s contract is to fall back to the default variant.

Weights

Weights are relative, not percentages: [50, 50] and [1, 1] produce identical splits because the operator divides by the total. This lets you grow a rollout from [1, 99] to [50, 50] to [99, 1] without renormalizing.

Weights must be non-negative integers. Omitted weights default to 1, so ["red"], ["blue"] is equivalent to ["red", 1], ["blue", 1]. A non-integer weight (a fraction such as 99.9, or a string such as "50") also falls back to 1, so [["a", 99.9], ["b", 0.1]] behaves exactly like [["a", 1], ["b", 1]]. Negative weights clamp to 0.

Composing with if

Real-world usage typically gates fractional behind a precondition rather than running it unconditionally:

{
  "if": [
    { "in": ["@example.com", { "var": "email" }] },
    {
      "fractional": [
        { "cat": [{ "var": "$flagd.flagKey" }, { "var": "email" }] },
        ["new-ui", 50],
        ["old-ui", 50]
      ]
    },
    "old-ui"
  ]
}

sem_ver

Semantic-version comparison with the four normalizations the flagd spec calls for.

Reference: flagd SemVer spec

Syntax:

{ "sem_ver": [version1, operator, version2] }

Operators:

OperatorMeaning
=Exact match
!=Not equal
<Less than
<=Less or equal
>Greater than
>=Greater or equal
^Same major version (caret-style “compatible”)
~Same major + minor (tilde-style “approximate”)

Comparison follows SemVer 2.0 precedence, including pre-release ordering: 1.0.0-alpha < 1.0.0-beta < 1.0.0.

Input normalizations

The operator applies four normalizations to both version arguments before parsing, matching what the flagd evaluator and most other flagd providers do:

  1. Strip leading v / V: "v1.2.3", "V1.2.3", and "1.2.3" are all equivalent.
  2. Pad partial versions: "1" becomes "1.0.0", "1.2" becomes "1.2.0".
  3. Coerce numeric input: 1 (a JSON number) is treated as the string "1", then padded.
  4. Drop build metadata: "1.2.3+build.7" is treated as "1.2.3". (SemVer 2.0 specifies build metadata is ignored when determining precedence.)

Examples

// Simple comparison
{ "sem_ver": [{ "var": "app_version" }, ">=", "1.2.0"] }

// Caret: same major
{ "sem_ver": [{ "var": "app_version" }, "^", "1.0.0"] }
// matches 1.0.0, 1.5.3, 1.99.99, but not 2.0.0

// Tilde: same major + minor
{ "sem_ver": [{ "var": "app_version" }, "~", "1.2.0"] }
// matches 1.2.0, 1.2.5, 1.2.99, but not 1.3.0

// v-prefixed input is handled transparently
{ "sem_ver": ["v1.2.3", "=", "1.2.3"] }            // true
{ "sem_ver": ["1.2", "<", "1.2.1"] }                // true (1.2 padded to 1.2.0)
{ "sem_ver": [1, "=", "v1.0.0"] }                   // true (int 1 coerced)

Gated rollout pattern

The common shape for shipping a feature only to clients on a recent version:

{
  "if": [
    { "sem_ver": [{ "var": "app_version" }, ">=", "2.0.0"] },
    { "fractional": [
        { "cat": [{ "var": "$flagd.flagKey" }, { "var": "user_id" }] },
        ["new-checkout", 10],
        ["old-checkout", 90]
    ]},
    "old-checkout"
  ]
}

Conformance

The conformance test suites live under crates/datalogic-rs/tests/suites/flagd/ and mirror the upstream Go test fixtures:

Every release runs the full suite, so any flagd-spec drift gets caught before publish.