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).
fractionalandsem_verrequire theflagdfeature (off by default in the Rust crate; enabled in every language binding). An engine built withEngineBuilder::with_familiesincludes them only whenFamily::Flagdis 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:
| Operator | Meaning |
|---|---|
= | 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:
- Strip leading
v/V:"v1.2.3","V1.2.3", and"1.2.3"are all equivalent. - Pad partial versions:
"1"becomes"1.0.0","1.2"becomes"1.2.0". - Coerce numeric input:
1(a JSON number) is treated as the string"1", then padded. - 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
- Control Flow Operators — combine
fractionalandsem_verwithifand?? - String Operators — build bucketing keys with
cat - Use Cases & Examples — feature flag and rollout rule patterns
- Operators Overview — full operator catalog and feature-flag table