http_client_vcr/
noop_client.rs

1use async_trait::async_trait;
2use http_client::{Config, Error, HttpClient, Request, Response};
3
4/// A no-op HTTP client that always fails with an error.
5///
6/// This client is useful for testing VCR in replay mode to ensure
7/// that no real HTTP requests are made. Any attempt to send a request
8/// will result in an error.
9///
10/// ## Usage
11///
12/// ```rust,no_run
13/// use http_client_vcr::{VcrClient, VcrMode, NoOpClient};
14/// use http_client::HttpClient;
15/// use http_types::{Request, Method, Url};
16///
17/// # #[tokio::main]
18/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
19/// // Ensure no real HTTP requests can be made
20/// let vcr_client = VcrClient::builder("fixtures/test.yaml")
21///     .inner_client(Box::new(NoOpClient::new()))
22///     .mode(VcrMode::Replay) // Only replay from cassette
23///     .build()
24///     .await?;
25///
26/// // This will work if the request is in the cassette
27/// let request = Request::new(Method::Get, Url::parse("https://example.com")?);
28/// let response = vcr_client.send(request).await?;
29///
30/// // If not in cassette, VCR will return an error before reaching NoOpClient
31/// // If somehow a request reaches NoOpClient, it will panic with a clear message
32/// # Ok(())
33/// # }
34/// ```
35#[derive(Debug, Clone)]
36pub struct NoOpClient {
37    error_message: String,
38    config: Config,
39}
40
41impl NoOpClient {
42    /// Create a new NoOpClient with a default error message.
43    pub fn new() -> Self {
44        Self {
45            error_message: "NoOpClient: Real HTTP requests are not allowed. This indicates a VCR configuration issue - requests should be replayed from cassette.".to_string(),
46            config: Config::new(),
47        }
48    }
49
50    /// Create a NoOpClient with a custom error message.
51    pub fn with_message(message: impl Into<String>) -> Self {
52        Self {
53            error_message: message.into(),
54            config: Config::new(),
55        }
56    }
57
58    /// Create a NoOpClient that panics instead of returning an error.
59    /// This is useful for catching unexpected HTTP requests during development.
60    pub fn panicking() -> PanickingNoOpClient {
61        PanickingNoOpClient::new()
62    }
63}
64
65impl Default for NoOpClient {
66    fn default() -> Self {
67        Self::new()
68    }
69}
70
71#[async_trait]
72impl HttpClient for NoOpClient {
73    async fn send(&self, req: Request) -> Result<Response, Error> {
74        Err(Error::from_str(
75            500,
76            format!(
77                "{} Attempted request: {} {}",
78                self.error_message,
79                req.method(),
80                req.url()
81            ),
82        ))
83    }
84
85    fn set_config(&mut self, config: Config) -> Result<(), Error> {
86        self.config = config;
87        Ok(())
88    }
89
90    fn config(&self) -> &Config {
91        &self.config
92    }
93}
94
95/// A variant of NoOpClient that panics instead of returning an error.
96///
97/// This is useful during development to catch unexpected HTTP requests
98/// with a clear stack trace showing exactly where the request originated.
99#[derive(Debug, Clone)]
100pub struct PanickingNoOpClient {
101    panic_message: String,
102    config: Config,
103}
104
105impl PanickingNoOpClient {
106    pub fn new() -> Self {
107        Self {
108            panic_message: "PanickingNoOpClient: Unexpected HTTP request detected! This should not happen in VCR replay mode.".to_string(),
109            config: Config::new(),
110        }
111    }
112
113    pub fn with_message(message: impl Into<String>) -> Self {
114        Self {
115            panic_message: message.into(),
116            config: Config::new(),
117        }
118    }
119}
120
121impl Default for PanickingNoOpClient {
122    fn default() -> Self {
123        Self::new()
124    }
125}
126
127#[async_trait]
128impl HttpClient for PanickingNoOpClient {
129    async fn send(&self, req: Request) -> Result<Response, Error> {
130        panic!(
131            "{} Attempted request: {} {} - Check your VCR configuration and cassette contents.",
132            self.panic_message,
133            req.method(),
134            req.url()
135        );
136    }
137
138    fn set_config(&mut self, config: Config) -> Result<(), Error> {
139        self.config = config;
140        Ok(())
141    }
142
143    fn config(&self) -> &Config {
144        &self.config
145    }
146}