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. The implementation follows the canonical Go evaluator, so a flag definition that works under a flagd provider picks the same variants here.

Feature flag (Rust crate). fractional and sem_ver require the flagd feature (off by default in the Rust crate; enabled in every language binding). An engine built with EngineBuilder::with_families includes them only when Family::Flagd is listed. See the feature table.

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

Argument counts: fractional takes one or more arguments; sem_ver takes exactly three.

Both operators return null on malformed input (a wrong argument count, an unparseable version, a missing targeting key, a bucket that is not a [variant, weight] array) 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. An error raised while evaluating an argument, such as a throw, still propagates.

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, as the Go evaluator’s core/pkg/evaluator/fractional.go does. The crate vendors the hash, so the feature adds no hashing dependency.

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, and a user isn’t locked into one cohort across your whole product.

2. Implicit bucketing key

Omit the first argument, or let it evaluate to null (for example a missing var). The operator parses any other non-string first argument, such as a number or boolean, as a bucket definition; since that argument is not an array, the whole call returns 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, and weights above i32::MAX (2,147,483,647) clamp to it, since flagd’s weights are 32-bit: two equal weights of 2^40 split like [1, 1]. If every weight is 0, the call returns null.

Composing with if

To bucket only the users who pass a precondition, put fractional in a branch of if:

{
  "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:

The release workflow runs the full suite on the tag before it publishes.

See Also