use bytes::Bytes;
use http::HeaderName;
pub use http::{
HeaderMap, HeaderValue, Method, Request, Response, StatusCode, Uri, request::Builder,
};
mod erased;
pub mod framing;
pub mod middleware;
pub mod multipart;
use crate::wasm_compat::*;
pub use erased::DynHttpClient;
pub use middleware::HttpMiddleware;
pub use multipart::MultipartForm;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Http error: {0}")]
Protocol(#[from] http::Error),
#[error("Invalid status code {status} with message: {body}")]
InvalidStatusCodeWithDetails {
status: StatusCode,
body: String,
headers: http::HeaderMap,
},
#[error("Header value outside of legal range: {0}")]
InvalidHeaderValue(#[from] http::header::InvalidHeaderValue),
#[error("Request in error state, cannot access headers")]
NoHeaders,
#[error("Stream ended")]
StreamEnded,
#[error("Invalid content type was returned: {0:?}")]
InvalidContentType(HeaderValue),
#[cfg(not(target_family = "wasm"))]
#[error("Http client error: {0}")]
Instance(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),
#[cfg(target_family = "wasm")]
#[error("Http client error: {0}")]
Instance(#[from] Box<dyn std::error::Error + 'static>),
}
impl Error {
pub fn non_success_status(&self) -> Option<StatusCode> {
match self {
Self::InvalidStatusCodeWithDetails { status, .. } => Some(*status),
_ => None,
}
}
pub fn non_success_body(&self) -> Option<&str> {
match self {
Self::InvalidStatusCodeWithDetails { body, .. } => Some(body.as_str()),
_ => None,
}
}
pub fn non_success_with_details(status: StatusCode, headers: HeaderMap, body: String) -> Self {
Self::InvalidStatusCodeWithDetails {
status,
body,
headers,
}
}
pub fn non_success_headers(&self) -> Option<&HeaderMap> {
match self {
Self::InvalidStatusCodeWithDetails { headers, .. } => Some(headers),
_ => None,
}
}
}
pub type Result<T> = std::result::Result<T, Error>;
impl Error {
#[cfg(not(target_family = "wasm"))]
pub fn instance<E: std::error::Error + Send + Sync + 'static>(error: E) -> Self {
Self::Instance(error.into())
}
#[cfg(target_family = "wasm")]
pub fn instance<E: std::error::Error + 'static>(error: E) -> Self {
Self::Instance(error.into())
}
}
pub type LazyBytes = WasmBoxedFuture<'static, Result<Bytes>>;
pub type LazyBody<T> = WasmBoxedFuture<'static, Result<T>>;
pub type BoxedStream = std::pin::Pin<Box<dyn WasmCompatSendStream<InnerItem = Result<Bytes>>>>;
pub type StreamingResponse = Response<BoxedStream>;
#[derive(Debug, Clone, Copy)]
pub struct NoBody;
impl From<NoBody> for Bytes {
fn from(_: NoBody) -> Self {
Bytes::new()
}
}
pub async fn text(response: Response<LazyBody<Vec<u8>>>) -> Result<String> {
let text = response.into_body().await?;
Ok(String::from(String::from_utf8_lossy(&text)))
}
pub fn make_auth_header(key: impl AsRef<str>) -> Result<(HeaderName, HeaderValue)> {
Ok((
http::header::AUTHORIZATION,
HeaderValue::from_str(&format!("Bearer {}", key.as_ref()))?,
))
}
pub fn bearer_auth_header(headers: &mut HeaderMap, key: impl AsRef<str>) -> Result<()> {
let (k, v) = make_auth_header(key)?;
headers.insert(k, v);
Ok(())
}
#[diagnostic::on_unimplemented(
message = "`{Self}` is not an HTTP client Rig can send through",
label = "not an `HttpClientExt`",
note = "use `rig_reqwest::ReqwestClient` (wrap a configured `reqwest::Client` with `ReqwestClient::from(client)`), erase a client with `DynHttpClient::new(client)`, or implement `HttpClientExt`; a provider configuration takes it with `.with_http(client)`"
)]
pub trait HttpClientExt: WasmCompatSend + WasmCompatSync {
fn send<T, U>(
&self,
req: Request<T>,
) -> impl Future<Output = Result<Response<LazyBody<U>>>> + WasmCompatSend + 'static
where
T: Into<Bytes>,
T: WasmCompatSend,
U: From<Bytes>,
U: WasmCompatSend + 'static;
fn send_multipart<U>(
&self,
req: Request<MultipartForm>,
) -> impl Future<Output = Result<Response<LazyBody<U>>>> + WasmCompatSend + 'static
where
U: From<Bytes>,
U: WasmCompatSend + 'static;
fn send_streaming<T>(
&self,
req: Request<T>,
) -> impl Future<Output = Result<StreamingResponse>> + WasmCompatSend
where
T: Into<Bytes> + WasmCompatSend;
}
#[cfg(test)]
mod non_success_header_tests;