Skip to main content

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}