rig_http/http_client/
mod.rs1use bytes::Bytes;
12use http::HeaderName;
13pub use http::{
14 HeaderMap, HeaderValue, Method, Request, Response, StatusCode, Uri, request::Builder,
15};
16mod erased;
17pub mod framing;
18pub mod middleware;
19pub mod multipart;
20use crate::wasm_compat::*;
21pub use erased::DynHttpClient;
22pub use middleware::HttpMiddleware;
23pub use multipart::MultipartForm;
24
25#[derive(Debug, thiserror::Error)]
26pub enum Error {
27 #[error("Http error: {0}")]
28 Protocol(#[from] http::Error),
29 #[error("Invalid status code {status} with message: {body}")]
33 InvalidStatusCodeWithDetails {
34 status: StatusCode,
36 body: String,
38 headers: http::HeaderMap,
40 },
41 #[error("Header value outside of legal range: {0}")]
42 InvalidHeaderValue(#[from] http::header::InvalidHeaderValue),
43 #[error("Request in error state, cannot access headers")]
44 NoHeaders,
45 #[error("Stream ended")]
46 StreamEnded,
47 #[error("Invalid content type was returned: {0:?}")]
48 InvalidContentType(HeaderValue),
49 #[cfg(not(target_family = "wasm"))]
50 #[error("Http client error: {0}")]
51 Instance(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),
52
53 #[cfg(target_family = "wasm")]
54 #[error("Http client error: {0}")]
55 Instance(#[from] Box<dyn std::error::Error + 'static>),
56}
57
58impl Error {
59 pub fn non_success_status(&self) -> Option<StatusCode> {
61 match self {
62 Self::InvalidStatusCodeWithDetails { status, .. } => Some(*status),
63 _ => None,
64 }
65 }
66
67 pub fn non_success_body(&self) -> Option<&str> {
70 match self {
71 Self::InvalidStatusCodeWithDetails { body, .. } => Some(body.as_str()),
72 _ => None,
73 }
74 }
75
76 pub fn non_success_with_details(status: StatusCode, headers: HeaderMap, body: String) -> Self {
79 Self::InvalidStatusCodeWithDetails {
80 status,
81 body,
82 headers,
83 }
84 }
85
86 pub fn non_success_headers(&self) -> Option<&HeaderMap> {
108 match self {
109 Self::InvalidStatusCodeWithDetails { headers, .. } => Some(headers),
110 _ => None,
111 }
112 }
113}
114
115pub type Result<T> = std::result::Result<T, Error>;
116
117impl Error {
118 #[cfg(not(target_family = "wasm"))]
123 pub fn instance<E: std::error::Error + Send + Sync + 'static>(error: E) -> Self {
124 Self::Instance(error.into())
125 }
126
127 #[cfg(target_family = "wasm")]
129 pub fn instance<E: std::error::Error + 'static>(error: E) -> Self {
130 Self::Instance(error.into())
131 }
132}
133
134pub type LazyBytes = WasmBoxedFuture<'static, Result<Bytes>>;
135pub type LazyBody<T> = WasmBoxedFuture<'static, Result<T>>;
136
137pub type BoxedStream = std::pin::Pin<Box<dyn WasmCompatSendStream<InnerItem = Result<Bytes>>>>;
139
140pub type StreamingResponse = Response<BoxedStream>;
141
142#[derive(Debug, Clone, Copy)]
143pub struct NoBody;
144
145impl From<NoBody> for Bytes {
146 fn from(_: NoBody) -> Self {
147 Bytes::new()
148 }
149}
150
151pub async fn text(response: Response<LazyBody<Vec<u8>>>) -> Result<String> {
152 let text = response.into_body().await?;
153 Ok(String::from(String::from_utf8_lossy(&text)))
154}
155
156pub fn make_auth_header(key: impl AsRef<str>) -> Result<(HeaderName, HeaderValue)> {
157 Ok((
158 http::header::AUTHORIZATION,
159 HeaderValue::from_str(&format!("Bearer {}", key.as_ref()))?,
160 ))
161}
162
163pub fn bearer_auth_header(headers: &mut HeaderMap, key: impl AsRef<str>) -> Result<()> {
164 let (k, v) = make_auth_header(key)?;
165
166 headers.insert(k, v);
167
168 Ok(())
169}
170
171#[diagnostic::on_unimplemented(
176 message = "`{Self}` is not an HTTP client Rig can send through",
177 label = "not an `HttpClientExt`",
178 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)`"
179)]
180pub trait HttpClientExt: WasmCompatSend + WasmCompatSync {
181 fn send<T, U>(
183 &self,
184 req: Request<T>,
185 ) -> impl Future<Output = Result<Response<LazyBody<U>>>> + WasmCompatSend + 'static
186 where
187 T: Into<Bytes>,
188 T: WasmCompatSend,
189 U: From<Bytes>,
190 U: WasmCompatSend + 'static;
191
192 fn send_multipart<U>(
194 &self,
195 req: Request<MultipartForm>,
196 ) -> impl Future<Output = Result<Response<LazyBody<U>>>> + WasmCompatSend + 'static
197 where
198 U: From<Bytes>,
199 U: WasmCompatSend + 'static;
200
201 fn send_streaming<T>(
203 &self,
204 req: Request<T>,
205 ) -> impl Future<Output = Result<StreamingResponse>> + WasmCompatSend
206 where
207 T: Into<Bytes> + WasmCompatSend;
208}
209
210#[cfg(test)]
211mod non_success_header_tests;