Expand description
HTTP effects for agent-effects.
HttpEffect builds an effect’s action from an HTTP request: it sends
the effect’s stable idempotency key in a header, and classifies every
failure so the runtime knows whether the request may have applied.
use agent_effects::{EffectKind, Runtime};
use agent_effects_http::HttpEffect;
use agent_effects_memory::MemoryStore;
#[derive(serde::Serialize)]
struct Charge { amount: u64, currency: &'static str }
#[derive(serde::Serialize, serde::Deserialize)]
struct Payment { id: String }
let client = reqwest::Client::new();
let runtime = Runtime::new(MemoryStore::new());
let outcome = runtime
.effect("payment.charge", "order-42")
.kind(EffectKind::IrreversibleWrite)
.remote_idempotency(true) // the provider honours Idempotency-Key
.run(
HttpEffect::post(&client, "https://api.example.com/v1/charges")
.json(&Charge { amount: 4200, currency: "eur" })
.idempotency_key_header()
.send_json::<Payment>(),
)
.await?;The crate enables no reqwest features: TLS and the rest come from your
own reqwest dependency and the reqwest::Client you pass in.
§How failures are classified
A failure is only definite when the request provably never reached the server, or the server’s answer says it did not apply. Everything in between is ambiguous, and the runtime then verifies, re-runs only if safe, or escalates.
| Outcome | Class |
|---|---|
| connect, DNS or TLS failure | Ambiguous; Transient (never sent) only with HttpEffect::client_follows_no_redirects |
| invalid request (bad URL or header) | Validation (never sent) |
| timeout or connection lost after sending | Ambiguous |
| 2xx with an unreadable or unparseable body | Ambiguous (it applied, the answer was lost) |
| 3xx (seen only when the client does not follow redirects) | Ambiguous |
| any failing status after the client followed a redirect | Ambiguous (it answers for the redirect’s target) |
| 408, 425, 503 | Transient |
429, or 503 with Retry-After | RateLimited, honouring Retry-After |
| 400, 422 | Validation |
| 401 | Authentication |
| 403 | Authorization |
| 409, 500, 502, 504, other 5xx | Ambiguous |
| 501, other 4xx | Permanent |
Override per request with HttpEffect::classify_status.
A client that follows redirects (reqwest’s default) can fail to connect
to a redirect’s target after the original request applied, and reqwest
reports that exactly like a failure to reach the original server. So a
connection failure only proves the request was never sent when the
client does not follow redirects: build the client with
.redirect(reqwest::redirect::Policy::none()) and say so with
HttpEffect::client_follows_no_redirects.
The same goes for statuses: after a redirect, the status is the
target’s, so a failing one says nothing about the original request and
is ambiguous. reqwest keeps no redirect history, so a redirect is seen
by the response’s URL differing from the request’s; one that leads back
to the same URL (POST /orders → 303 → GET /orders) is not seen.
For irreversible writes, use a client that does not follow redirects.
Structs§
- Http
Effect - An HTTP request as an effect’s action. Build it, then turn it into an
action with
Self::send_jsonorSelf::send. - Http
Response - A successful response, as stored and replayed.
Enums§
- Status
Class - What a response status means.
Constants§
- IDEMPOTENCY_
KEY - The conventional idempotency header.
Functions§
- classify_
error - The class of a request that failed without a response, from a client that may follow redirects: everything but an invalid request is ambiguous, a connection failure included (see the crate docs).
- classify_
error_ without_ redirects - Like
classify_error, for a client that does not follow redirects: a connection failure (including a connect timeout) means nothing reached the server, so the request was never sent. - classify_
status - The default meaning of
status; see the crate docs.