Skip to main content

sova_http/
transport.rs

1//! Transport seam: real reqwest vs fake.
2
3use crate::error::HttpError;
4use bytes::Bytes;
5use http::{HeaderMap, Method, StatusCode};
6use sova_core::extend::BoxFuture;
7use std::time::Duration;
8
9/// Outbound request after builder normalization.
10#[derive(Debug, Clone)]
11pub struct OutRequest {
12    pub method: Method,
13    pub url: String,
14    pub headers: HeaderMap,
15    pub body: Option<Bytes>,
16    pub timeout: Option<Duration>,
17}
18
19/// Thin response wrapper (not a parallel HTTP stack).
20#[derive(Debug, Clone)]
21pub struct OutResponse {
22    status: StatusCode,
23    headers: HeaderMap,
24    body: Bytes,
25}
26
27impl OutResponse {
28    pub fn new(status: StatusCode, headers: HeaderMap, body: Bytes) -> Self {
29        Self {
30            status,
31            headers,
32            body,
33        }
34    }
35
36    pub fn status(&self) -> StatusCode {
37        self.status
38    }
39
40    pub fn status_u16(&self) -> u16 {
41        self.status.as_u16()
42    }
43
44    pub fn headers(&self) -> &HeaderMap {
45        &self.headers
46    }
47
48    pub fn bytes(&self) -> &Bytes {
49        &self.body
50    }
51
52    pub fn text(&self) -> Result<String, HttpError> {
53        String::from_utf8(self.body.to_vec()).map_err(|e| HttpError::Other(e.to_string()))
54    }
55
56    pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T, HttpError> {
57        serde_json::from_slice(&self.body).map_err(|e| HttpError::Other(e.to_string()))
58    }
59
60    /// Treat 4xx/5xx as [`HttpError::Status`] (5xx → 502 via IntoResponse).
61    pub fn error_for_status(self) -> Result<Self, HttpError> {
62        let code = self.status.as_u16();
63        if code >= 400 {
64            Err(HttpError::Status(code))
65        } else {
66            Ok(self)
67        }
68    }
69}
70
71pub trait Transport: Send + Sync {
72    fn send(&self, req: OutRequest) -> BoxFuture<Result<OutResponse, HttpError>>;
73}