pub struct Assertions {
pub status: Option<u16>,
pub status_in: Option<Vec<u16>>,
pub headers: BTreeMap<String, Option<String>>,
pub body_contains: Option<String>,
pub body_matches: Option<String>,
pub elapsed_ms_under: Option<u64>,
pub json: BTreeMap<String, Value>,
pub not: Option<NotAssertions>,
}Expand description
The assertions block of a request, exactly as it appears on disk.
Each field is a separate kind of check, and each entry within a field is
one assertion — headers with three entries is three assertions, reported
individually. All of them are evaluated on every response; none short-circuit
the others, because “which of my expectations held” is the question this
feature exists to answer and stopping at the first failure would answer it
only partially.
Unknown keys are rejected, like everywhere else in Sendra’s schema: an assertion silently ignored because of a typo is worse than no assertion at all, since it reads as a check that is passing.
Fields§
§status: Option<u16>The exact status code the response must carry.
Equality against one code rather than a class (2xx) or a range: the
two are not the same assertion, and “this endpoint answers 201” is the
one worth writing down. A class matcher can be added as its own key
later without changing what this one means.
status_in: Option<Vec<u16>>The status code must be one of these.
Its own key rather than folding into status (a list there would
change what a bare status: 200 means) — status is “exactly this
code”, status_in is “one of these codes”, and a file should be able
to write either without the other’s presence changing its meaning.
headers: BTreeMap<String, Option<String>>Headers the response must carry, by name.
A value asserts the header is present and equal to it; a null value
(x-request-id: with nothing after it) asserts only that the header is
there. One key covers both because they are the same assertion with and
without an expectation about the value, and a second key
(headers_present) would make the file say twice what the value’s
presence already says.
Names are matched case-insensitively, because HTTP header names are.
Values are matched exactly: content-type: application/json does not
match application/json; charset=utf-8. That is the strict reading, and
the honest one — a substring match would quietly accept
application/json-seq too. When a server decorates a value, assert the
whole value or drop to presence-only.
A repeated header (set-cookie) passes if any of its values matches.
body_contains: Option<String>A substring the response body must contain, matched case-sensitively on the body as printed.
body_matches: Option<String>A regular expression the response body must match, anywhere in it —
the same “somewhere in the body” reach as body_contains, not an
anchored whole-body match, so body_matches: '"id":\s*\d+' finds that
pattern wherever it sits.
The engine is regex, already in the
dependency tree as a transitive dependency of jsonpath-rust — this
adds no new crate, only a direct declaration of one already being
built.
The pattern is checked when the assertion runs, not when the file is
loaded, for the same reason a JSON path is: a stricter release of
regex should not start rejecting files that used to load, for a
request Sendra could still send. An invalid pattern is a failed
assertion naming the parse error, not a panic or a load-time error.
elapsed_ms_under: Option<u64>The response must have arrived in under this many milliseconds.
Backed by Response::elapsed, which is wall-clock time for the
request as sent — DNS, connect and TLS included, the same number a
person timing the request by hand would get. A strict “under”, not
“at or under”: a threshold is normally chosen as a round number the
response should beat, and elapsed_ms_under: 500 reads as “faster
than half a second,” which an exact 500ms response is not.
json: BTreeMap<String, Value>JSON path expressions mapped to the value each must select.
json:
$.user.id: 42
$.user.name: ada
$.tags: [a, b]The expected value is written as YAML and held as a serde_json::Value
— parsed once, when the file is loaded, into the form it will be compared
against, so a value that has no JSON equivalent is a parse error naming
the file rather than a surprise at response time.
A path must select exactly one value. Nothing matched, or several
matched, is a failure with that stated: $.users[*].id against three
users is a question with no single answer, and picking the first would
make the assertion depend on ordering the author never specified.
The engine is jsonpath-rust, chosen
over serde_json_path, the other RFC 9535 implementation, on
maintenance and stability: at the time of writing jsonpath-rust is at
1.0 with releases landing this year, while serde_json_path has not
released since February 2025 and is still pre-1.0. Both are correct
and both query serde_json::Value directly, which is what keeps this
dependency swappable if that ever changes: it is confined to
Assertions::evaluate, behind a path string and a value comparison.
Beyond a bare equality value, a path may map to an operator object
with exactly one of these keys:
json:
$.count: { greater_than: 5 } # numeric: actual > 5
$.count: { greater_than_or_equal: 5 } # numeric: actual >= 5
$.count: { less_than: 5 } # numeric: actual < 5
$.count: { less_than_or_equal: 5 } # numeric: actual <= 5
$.tags: { contains: b } # substring of a string, or array membership
$.tags: { length: 2 } # array/string length equals 2
$.tags: { length: { greater_than: 1 } } # length compared, not just equal
$.id: { matches: '^[0-9a-f-]{36}$' } # regex, scoped to this path's
# string value — see `body_matches`
# for the whole-body equivalentSee the module docs for exactly how a bare value is told apart from an operator, and what that costs.
not: Option<NotAssertions>Every assertion in this block, inverted: passes exactly when the
wrapped one would have failed, and vice versa. See the module docs for
what a wrapper buys over a not_-prefixed key per assertion type, and
for why a hard error underneath — a malformed path, an invalid regex,
a type mismatch — is not something this can turn into a pass.
Implementations§
Source§impl Assertions
impl Assertions
Sourcepub fn is_empty(&self) -> bool
pub fn is_empty(&self) -> bool
True when the block asserts nothing — assertions: {}, or a block whose
every key was omitted.
Sourcepub fn evaluate(&self, response: &Response) -> AssertionReport
pub fn evaluate(&self, response: &Response) -> AssertionReport
Check every assertion against response and report all of them.
The response must be the one that actually came back from the request as sent — after variable substitution and after config was applied — since that is the request the assertions were written about.
Trait Implementations§
Source§impl Clone for Assertions
impl Clone for Assertions
Source§fn clone(&self) -> Assertions
fn clone(&self) -> Assertions
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more