pub fn resolve_args(
args: &mut Value,
prior_responses: &[Invocation],
) -> Result<(), JmapError>Expand description
Resolve all #key ResultReference fields in args against prior_responses.
For every key in args that starts with #:
- Parse the value as a
ResultReference. - Find the prior response whose call-id matches
rr.result_of(index 2 of tuple). - Verify
rr.namematches the method name of that response (index 0 of tuple). - Apply
rr.pathas an RFC 6901 JSON Pointer (with RFC 8620 §3.7*extension) to the response args (index 1 of tuple). - Collect
(plain_key, resolved_value)pairs.
This is two-phase atomic: args is not modified at all unless every
#key resolves successfully. If any resolution fails, args is returned
unchanged and an error is returned.
prior_responses entries are (method_name, response_args, call_id) — same
layout as Invocation.
§Why two-phase? (bd:JMAP-jfia.12 decision record)
A future contributor will reasonably suggest “this is one extra pass over the keys — just resolve-and-mutate inline, that’s simpler”. That suggestion is WRONG. The two-phase structure is load-bearing:
- RFC 8620 §3.7 semantics are atomic: if ANY
ResultReferencefails to resolve, the entire method call getsinvalidResultReference. A partial-mutation would leave a malformedargsobject that handlers could observe. argsis taken by&mut, so it remains observable to the caller after the function returnsErr. A test that checksargscontents on the error path would see a partial state under inline-mutation.- The test
parse::tests::resolve_args_atomic_on_partial_failuredirectly encodes this contract (“args must be completely unchanged on error”). Without two-phase, that test would fail.
The cost is one Vec<(ref_key, plain_key, resolved_value)>
allocation per call; that cost is the price of the atomicity
contract.