Vary the response for one path
When more than one rule matches the same request, the rule set’s
strategy decides which one answers. Five exist.
first_match (the default)
No configuration needed — the first matching rule in file order wins, every time. Deterministic.
priority
[strategy]
priority = { tiebreaker = "first_match" }
[[rules]]
when.request.url_path = "/widgets"
respond.text = "general response"
priority = 1
[[rules]]
when.request.url_path = "/widgets"
respond.text = "special response (higher priority)"
priority = 10
Among matching rules, the highest priority wins — deterministically,
regardless of file order. tiebreaker (first_match or
uniform_random) decides what happens when two matching rules share
the top priority. Note priority always needs its own table, even for
a default tiebreaker — strategy = "priority" as a bare string is a
parse error, unlike the other four.
weighted_random
[strategy]
weighted_random = { seed = 7 } # omit `seed` for real randomness
[[rules]]
when.request.url_path = "/weighted"
respond.text = "variant-a"
weight = 3
[[rules]]
when.request.url_path = "/weighted"
respond.text = "variant-b"
weight = 1
Random among matches, weighted by weight (default 1 if omitted) —
variant-a above is picked roughly 3 times as often as variant-b.
seed, if set, makes the pick fully deterministic — not a fixed
sequence, but the same result every single request, since a fresh
RNG is seeded per call. That’s useful for a reproducible test; it is
not a way to preview a realistic distribution. Omit seed entirely to
see genuine variation across requests.
uniform_random
Same shape as weighted_random (an optional seed), but every match
has equal probability — weight is not consulted.
round_robin
strategy = "round_robin"
[[rules]]
when.request.url_path = "/round-robin"
respond.text = "server-a"
[[rules]]
when.request.url_path = "/round-robin"
respond.text = "server-b"
Cycles through matches in file order, one per request:
server-a, server-b, server-a, server-b, … Deterministic, and
doesn’t need weight or priority set on any rule.
Rotation is per match group, not per rule set (RFC 070). The
example above has one group — every request to /round-robin matches
the same two rules — so “cycles through matches in file order” is the
whole story there. A rule set that serves more than one distinct
request shape rotates each shape independently:
strategy = "round_robin"
[[rules]]
when.request.url_path = "/a"
respond.text = "a1"
[[rules]]
when.request.url_path = "/a"
respond.text = "a2"
[[rules]]
when.request.url_path = "/b"
respond.text = "b1"
[[rules]]
when.request.url_path = "/b"
respond.text = "b2"
[[rules]]
when.request.url_path = "/b"
respond.text = "b3"
Requesting /a four times in a row gives a1 a2 a1 a2, exactly as the
single-group case above. Requesting /a and /b alternately gives
/a: a1 a2 a1 a2 and /b: b1 b2 b3 b1 — each path’s own two- or
three-rule cycle, independent of how often the other path is also
requested. Two requests that match the same set of rules always share
one counter; two requests that match a different set of rules never
share one, no matter how they’re interleaved.
Where strategy goes
service.strategy sets the default for every rule set. A rule set’s
own top-level strategy field overrides that default for itself only
— which is how you can run several strategies side by side, each
scoped to its own rule set (and, typically, its own [prefix]). See
Rule-set schema.
A worked, verified example running all three of priority,
weighted_random, and round_robin from one server, each in its own
rule set:
crates/apimock/examples/vary-response-by-strategy/.