dig_urn_resolver/transport.rs
1//! The injected HTTP transport — the one seam between the resolver's protocol
2//! logic and the runtime's networking.
3//!
4//! The resolver never calls `reqwest` or the browser `fetch` directly; it depends
5//! only on [`HttpTransport`], so the SAME orchestration is exercised natively (the
6//! `reqwest` impl behind the `native` feature), in the browser (the `fetch` impl
7//! behind the `wasm` feature), and under test (an in-memory mock — no network).
8
9use async_trait::async_trait;
10
11/// A minimal HTTP response the resolver cares about: status, headers, and body.
12#[derive(Debug, Clone)]
13pub struct HttpResponse {
14 /// The HTTP status code.
15 pub status: u16,
16 /// Response headers, lowercased names.
17 pub headers: Vec<(String, String)>,
18 /// The raw response body bytes.
19 pub body: Vec<u8>,
20}
21
22impl HttpResponse {
23 /// A `2xx` status.
24 pub fn is_success(&self) -> bool {
25 (200..300).contains(&self.status)
26 }
27
28 /// Look up a response header (case-insensitive), returning its value.
29 pub fn header(&self, name: &str) -> Option<&str> {
30 let name = name.to_ascii_lowercase();
31 self.headers
32 .iter()
33 .find(|(k, _)| k.eq_ignore_ascii_case(&name))
34 .map(|(_, v)| v.as_str())
35 }
36}
37
38/// A transport-level failure (DNS, TLS, connect, timeout, malformed HTTP). The
39/// ladder treats any transport error on a tier as "unreachable" and falls through.
40#[derive(Debug, Clone)]
41pub struct TransportError(pub String);
42
43impl core::fmt::Display for TransportError {
44 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
45 write!(f, "{}", self.0)
46 }
47}
48
49impl std::error::Error for TransportError {}
50
51/// The async HTTP surface the resolver needs: a GET (content + health probes) and
52/// a JSON POST (the JSON-RPC calls).
53///
54/// `?Send` — browser futures are not `Send`; native `reqwest` futures satisfy the
55/// relaxed bound anyway, so one trait serves both runtimes.
56#[async_trait(?Send)]
57pub trait HttpTransport {
58 /// GET `url`. A network/timeout failure is a [`TransportError`]; an HTTP error
59 /// status (404, 5xx) is a successful [`HttpResponse`] the caller interprets.
60 async fn get(&self, url: &str) -> Result<HttpResponse, TransportError>;
61
62 /// POST a JSON body to `url` with `content-type: application/json`.
63 async fn post_json(&self, url: &str, body: String) -> Result<HttpResponse, TransportError>;
64}