]>
Commit | Line | Data |
---|---|---|
0a29b90c FG |
1 | use crate::client::WriteMode; |
2 | ||
3 | /// The error used by the [Http] trait. | |
4 | #[derive(Debug, thiserror::Error)] | |
5 | #[allow(missing_docs)] | |
6 | pub enum Error { | |
4b012472 | 7 | #[error("Could not initialize the http client")] |
0a29b90c FG |
8 | InitHttpClient { |
9 | source: Box<dyn std::error::Error + Send + Sync + 'static>, | |
10 | }, | |
11 | #[error("{description}")] | |
12 | Detail { description: String }, | |
13 | #[error("An IO error occurred while uploading the body of a POST request")] | |
14 | PostBody(#[from] std::io::Error), | |
15 | } | |
16 | ||
17 | impl crate::IsSpuriousError for Error { | |
18 | fn is_spurious(&self) -> bool { | |
19 | match self { | |
20 | Error::PostBody(err) => err.is_spurious(), | |
21 | #[cfg(any(feature = "http-client-reqwest", feature = "http-client-curl"))] | |
22 | Error::InitHttpClient { source } => { | |
23 | #[cfg(feature = "http-client-curl")] | |
24 | if let Some(err) = source.downcast_ref::<crate::client::http::curl::Error>() { | |
25 | return err.is_spurious(); | |
26 | }; | |
27 | #[cfg(feature = "http-client-reqwest")] | |
28 | if let Some(err) = source.downcast_ref::<crate::client::http::reqwest::remote::Error>() { | |
29 | return err.is_spurious(); | |
30 | }; | |
31 | false | |
32 | } | |
33 | _ => false, | |
34 | } | |
35 | } | |
36 | } | |
37 | ||
fe692bf9 | 38 | /// The return value of [`Http::get()`]. |
0a29b90c FG |
39 | pub struct GetResponse<H, B> { |
40 | /// The response headers. | |
41 | pub headers: H, | |
42 | /// The response body. | |
43 | pub body: B, | |
44 | } | |
45 | ||
fe692bf9 | 46 | /// The return value of [`Http::post()`]. |
0a29b90c FG |
47 | pub struct PostResponse<H, B, PB> { |
48 | /// The body to post to the server as part of the request. | |
49 | /// | |
50 | /// **Note**: Implementations should drop the handle to avoid deadlocks. | |
51 | pub post_body: PB, | |
52 | /// The headers of the post response. | |
53 | pub headers: H, | |
54 | /// The body of the post response. | |
55 | pub body: B, | |
56 | } | |
57 | ||
58 | /// Whether or not the post body is expected to fit into memory or not. | |
59 | #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] | |
60 | pub enum PostBodyDataKind { | |
61 | /// We know how much data we are sending and think it will fit into memory. This allows to collect it into a buffer | |
62 | /// and send it with `Content-Length: <body-len>`. | |
63 | BoundedAndFitsIntoMemory, | |
64 | /// We don't know how much data we will send and assume it won't fit into memory. This enables streaming mode. | |
65 | Unbounded, | |
66 | } | |
67 | ||
68 | impl From<WriteMode> for PostBodyDataKind { | |
69 | fn from(m: WriteMode) -> Self { | |
70 | match m { | |
71 | WriteMode::Binary => PostBodyDataKind::Unbounded, | |
72 | WriteMode::OneLfTerminatedLinePerWriteCall => PostBodyDataKind::BoundedAndFitsIntoMemory, | |
73 | } | |
74 | } | |
75 | } | |
76 | ||
77 | impl<A, B, C> From<PostResponse<A, B, C>> for GetResponse<A, B> { | |
78 | fn from(v: PostResponse<A, B, C>) -> Self { | |
79 | GetResponse { | |
80 | headers: v.headers, | |
81 | body: v.body, | |
82 | } | |
83 | } | |
84 | } | |
85 | ||
86 | /// A trait to abstract the HTTP operations needed to power all git interactions: read via GET and write via POST. | |
49aad941 | 87 | /// Note that 401 must be turned into `std::io::Error(PermissionDenied)`, and other non-success http statuses must be transformed |
0a29b90c FG |
88 | /// into `std::io::Error(Other)` |
89 | #[allow(clippy::type_complexity)] | |
90 | pub trait Http { | |
91 | /// A type providing headers line by line. | |
92 | type Headers: std::io::BufRead + Unpin; | |
93 | /// A type providing the response. | |
94 | type ResponseBody: std::io::BufRead; | |
95 | /// A type allowing to write the content to post. | |
96 | type PostBody: std::io::Write; | |
97 | ||
98 | /// Initiate a `GET` request to `url` provided the given `headers`, where `base_url` is so that `base_url + tail == url`. | |
99 | /// | |
100 | /// The `base_url` helps to validate redirects and to swap it with the effective base after a redirect. | |
101 | /// | |
102 | /// The `headers` are provided verbatim and include both the key as well as the value. | |
103 | fn get( | |
104 | &mut self, | |
105 | url: &str, | |
106 | base_url: &str, | |
107 | headers: impl IntoIterator<Item = impl AsRef<str>>, | |
108 | ) -> Result<GetResponse<Self::Headers, Self::ResponseBody>, Error>; | |
109 | ||
110 | /// Initiate a `POST` request to `url` providing with the given `headers`, where `base_url` is so that `base_url + tail == url`. | |
111 | /// | |
112 | /// The `base_url` helps to validate redirects and to swap it with the effective base after a redirect. | |
113 | /// | |
114 | /// The `headers` are provided verbatim and include both the key as well as the value. | |
115 | /// Note that the [`PostResponse`] contains the [`post_body`][PostResponse::post_body] field which implements [`std::io::Write`] | |
116 | /// and is expected to receive the body to post to the server. **It must be dropped** before reading the response | |
117 | /// to prevent deadlocks. | |
118 | fn post( | |
119 | &mut self, | |
120 | url: &str, | |
121 | base_url: &str, | |
122 | headers: impl IntoIterator<Item = impl AsRef<str>>, | |
123 | body: PostBodyDataKind, | |
124 | ) -> Result<PostResponse<Self::Headers, Self::ResponseBody, Self::PostBody>, Error>; | |
125 | ||
126 | /// Pass `config` which can deserialize in the implementation's configuration, as documented separately. | |
127 | /// | |
128 | /// The caller must know how that `config` data looks like for the intended implementation. | |
129 | fn configure( | |
130 | &mut self, | |
131 | config: &dyn std::any::Any, | |
132 | ) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>>; | |
133 | } |