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