1use async_trait::async_trait;
2use http::{HeaderMap, Method, StatusCode};
3use std::error::Error as StdError;
4
5use thiserror::Error;
6use url::Url;
7
8#[derive(Clone, Debug)]
9pub struct HttpRequest {
10 pub method: Method,
11 pub url: Url,
12 pub headers: HeaderMap,
13 pub body: Vec<u8>,
14}
15
16#[derive(Clone, Debug)]
17pub struct HttpResponse {
18 pub status: StatusCode,
19 pub final_url: Url,
20 pub headers: HeaderMap,
21 pub body: Vec<u8>,
22}
23
24#[derive(Debug, Error)]
25#[error("{message}")]
26pub struct TransportError {
27 message: String,
28 #[source]
29 source: Option<Box<dyn StdError + Send + Sync>>,
30}
31
32impl TransportError {
33 pub fn new(message: impl Into<String>) -> Self {
34 Self {
35 message: message.into(),
36 source: None,
37 }
38 }
39
40 pub fn with_source(
41 message: impl Into<String>,
42 source: impl StdError + Send + Sync + 'static,
43 ) -> Self {
44 Self {
45 message: message.into(),
46 source: Some(Box::new(source)),
47 }
48 }
49}
50
51#[async_trait]
52pub trait HttpTransport: Send + Sync {
53 async fn send(&self, request: HttpRequest) -> Result<HttpResponse, TransportError>;
54}
55
56#[cfg(test)]
57mod tests {
58 use std::fmt;
59
60 use super::*;
61
62 #[derive(Debug)]
63 struct ExampleError;
64
65 impl fmt::Display for ExampleError {
66 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
67 formatter.write_str("cause")
68 }
69 }
70
71 impl StdError for ExampleError {}
72
73 #[test]
74 fn preserves_transport_error_context() {
75 assert_eq!(
76 TransportError::new("request failed").to_string(),
77 "request failed"
78 );
79 let error = TransportError::with_source("request failed", ExampleError);
80 assert_eq!(error.to_string(), "request failed");
81 assert_eq!(
82 StdError::source(&error).map(ToString::to_string),
83 Some("cause".to_owned())
84 );
85 }
86}