Skip to main content

gpui/
http_client.rs

1use futures::future::BoxFuture;
2use http::StatusCode;
3
4/// An HTTP status code and a fully buffered response body.
5///
6/// The body is owned bytes, so a caller can decode an image or inspect an error response
7/// after the request has finished. This interface does not expose response headers or a stream.
8pub struct HttpResponse {
9    /// The status returned by the server, including error statuses such as `404 Not Found`.
10    pub status: StatusCode,
11    /// The complete response body, read before the response is returned.
12    pub body: Vec<u8>,
13}
14
15/// The transport GPUI uses for resources such as image URLs.
16///
17/// Install an implementation with [`crate::Application::with_http_client`]; code inside the
18/// app retrieves it with [`crate::App::http_client`]. A newly created app uses
19/// [`NullHttpClient`], so URL-backed loads fail until a client is installed. The web
20/// constructors in `gpui_platform` install a fetch-based client; native applications must
21/// provide one before loading URL-backed assets.
22///
23/// For tests, [`BlockedHttpClient`] rejects network access. With the `test-support` feature,
24/// `FakeHttpClient` supplies a fixed status without making a request. The
25/// [images guide](crate::_documentation::guides::images_and_assets) explains loading,
26/// failure placeholders and cache eviction for a retry.
27pub trait HttpClient: 'static + Send + Sync {
28    /// Sends a GET request to `url` and returns its status and complete body.
29    ///
30    /// Follow redirects when `follow_redirects` is `true`. Return `Err` for a transport or
31    /// body-read failure. Receiving an HTTP error status, such as 404, is still an `Ok`
32    /// response: callers inspect [`HttpResponse::status`] and decide whether to use the body.
33    fn get(
34        &self,
35        url: &str,
36        follow_redirects: bool,
37    ) -> BoxFuture<'static, anyhow::Result<HttpResponse>>;
38}
39
40/// An [`HttpClient`] that fails every request because no client was configured.
41///
42/// A new app uses it until you install another client. The error text is `No HttpClient available`.
43pub struct NullHttpClient;
44
45impl HttpClient for NullHttpClient {
46    fn get(
47        &self,
48        _url: &str,
49        _follow_redirects: bool,
50    ) -> BoxFuture<'static, anyhow::Result<HttpResponse>> {
51        Box::pin(async { anyhow::bail!("No HttpClient available") })
52    }
53}
54
55/// An [`HttpClient`] that refuses every request with an I/O `PermissionDenied` error.
56///
57/// Install it when a run must not reach the network, such as a hermetic test.
58pub struct BlockedHttpClient;
59
60impl BlockedHttpClient {
61    /// Creates a client that refuses every request. This is the same value as
62    /// [`Default::default`].
63    pub fn new() -> Self {
64        Self
65    }
66}
67
68impl Default for BlockedHttpClient {
69    fn default() -> Self {
70        Self
71    }
72}
73
74impl HttpClient for BlockedHttpClient {
75    fn get(
76        &self,
77        _url: &str,
78        _follow_redirects: bool,
79    ) -> BoxFuture<'static, anyhow::Result<HttpResponse>> {
80        Box::pin(async {
81            Err(std::io::Error::new(
82                std::io::ErrorKind::PermissionDenied,
83                "BlockedHttpClient disallowed request",
84            )
85            .into())
86        })
87    }
88}
89
90/// An [`HttpClient`] for tests that answers every request with one status and an empty body.
91///
92/// Available in unit tests and with the `test-support` feature. It never performs network I/O.
93#[cfg(any(test, feature = "test-support"))]
94pub struct FakeHttpClient {
95    status: StatusCode,
96}
97
98#[cfg(any(test, feature = "test-support"))]
99impl FakeHttpClient {
100    /// Returns a client that answers every request with `404 Not Found`.
101    pub fn with_404_response() -> std::sync::Arc<dyn HttpClient> {
102        std::sync::Arc::new(Self {
103            status: StatusCode::NOT_FOUND,
104        })
105    }
106
107    /// Returns a client that answers every request with `200 OK`.
108    pub fn with_200_response() -> std::sync::Arc<dyn HttpClient> {
109        std::sync::Arc::new(Self {
110            status: StatusCode::OK,
111        })
112    }
113}
114
115#[cfg(any(test, feature = "test-support"))]
116impl HttpClient for FakeHttpClient {
117    fn get(
118        &self,
119        _url: &str,
120        _follow_redirects: bool,
121    ) -> BoxFuture<'static, anyhow::Result<HttpResponse>> {
122        let status = self.status;
123        Box::pin(async move {
124            Ok(HttpResponse {
125                status,
126                body: Vec::new(),
127            })
128        })
129    }
130}