rig_http/http_client/
mod.rs1pub use bytes::Bytes;
12pub use http::{
13 HeaderMap, HeaderValue, Method, Request, Response, StatusCode, Uri, request::Builder,
14};
15mod erased;
16pub mod framing;
17pub mod middleware;
18pub mod multipart;
19use crate::wasm_compat::*;
20pub use erased::DynHttpClient;
21pub use middleware::{Exchange, HttpMiddleware};
22pub use multipart::MultipartForm;
23
24#[derive(Debug, thiserror::Error)]
27#[non_exhaustive]
28pub enum Error {
29 #[error("Http error: {0}")]
31 Protocol(#[from] http::Error),
32 #[error("Invalid status code {status} with message: {body}")]
36 InvalidStatusCodeWithDetails {
37 status: StatusCode,
39 body: String,
41 headers: http::HeaderMap,
43 },
44 #[error("Header value outside of legal range: {0}")]
46 InvalidHeaderValue(#[from] http::header::InvalidHeaderValue),
47 #[error("Request in error state, cannot access headers")]
49 NoHeaders,
50 #[error("Stream ended")]
52 StreamEnded,
53 #[error("Invalid content type was returned: {0:?}")]
55 InvalidContentType(HeaderValue),
56 #[cfg(not(target_family = "wasm"))]
58 #[error("Http client error: {0}")]
59 Instance(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),
60
61 #[cfg(target_family = "wasm")]
63 #[error("Http client error: {0}")]
64 Instance(#[from] Box<dyn std::error::Error + 'static>),
65
66 #[cfg(not(target_family = "wasm"))]
69 #[error("Http client unavailable: {0}")]
70 Unbuilt(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
71
72 #[cfg(target_family = "wasm")]
75 #[error("Http client unavailable: {0}")]
76 Unbuilt(#[source] Box<dyn std::error::Error + 'static>),
77}
78
79impl Error {
80 pub fn non_success_status(&self) -> Option<StatusCode> {
82 match self {
83 Self::InvalidStatusCodeWithDetails { status, .. } => Some(*status),
84 _ => None,
85 }
86 }
87
88 pub fn non_success_body(&self) -> Option<&str> {
91 match self {
92 Self::InvalidStatusCodeWithDetails { body, .. } => Some(body.as_str()),
93 _ => None,
94 }
95 }
96
97 pub fn non_success_with_details(status: StatusCode, headers: HeaderMap, body: String) -> Self {
100 Self::InvalidStatusCodeWithDetails {
101 status,
102 body,
103 headers,
104 }
105 }
106
107 pub fn non_success_headers(&self) -> Option<&HeaderMap> {
129 match self {
130 Self::InvalidStatusCodeWithDetails { headers, .. } => Some(headers),
131 _ => None,
132 }
133 }
134}
135
136pub type Result<T> = std::result::Result<T, Error>;
138
139impl Error {
140 #[cfg(not(target_family = "wasm"))]
145 pub fn instance<E: std::error::Error + Send + Sync + 'static>(error: E) -> Self {
146 Self::Instance(error.into())
147 }
148
149 #[cfg(target_family = "wasm")]
151 pub fn instance<E: std::error::Error + 'static>(error: E) -> Self {
152 Self::Instance(error.into())
153 }
154
155 #[cfg(not(target_family = "wasm"))]
158 pub fn unbuilt<E: std::error::Error + Send + Sync + 'static>(error: E) -> Self {
159 Self::Unbuilt(error.into())
160 }
161
162 #[cfg(target_family = "wasm")]
165 pub fn unbuilt<E: std::error::Error + 'static>(error: E) -> Self {
166 Self::Unbuilt(error.into())
167 }
168}
169
170pub type LazyBody<T> = WasmBoxedFuture<'static, Result<T>>;
172
173pub type BoxedStream = std::pin::Pin<Box<dyn WasmCompatSendStream<InnerItem = Result<Bytes>>>>;
175
176pub type StreamingResponse = Response<BoxedStream>;
178
179#[derive(Debug, Clone, Copy)]
181pub struct NoBody;
182
183impl From<NoBody> for Bytes {
184 fn from(_: NoBody) -> Self {
185 Bytes::new()
186 }
187}
188
189pub async fn text(response: Response<LazyBody<Vec<u8>>>) -> Result<String> {
191 let text = response.into_body().await?;
192 Ok(String::from(String::from_utf8_lossy(&text)))
193}
194
195#[diagnostic::on_unimplemented(
200 message = "`{Self}` is not an HTTP client Rig can send through",
201 label = "not an `HttpClientExt`",
202 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 `.connect(client)`"
203)]
204pub trait HttpClientExt: WasmCompatSend + WasmCompatSync {
205 fn send<T, U>(
207 &self,
208 req: Request<T>,
209 ) -> impl Future<Output = Result<Response<LazyBody<U>>>> + WasmCompatSend + 'static
210 where
211 T: Into<Bytes>,
212 T: WasmCompatSend,
213 U: From<Bytes>,
214 U: WasmCompatSend + 'static;
215
216 fn send_multipart<U>(
218 &self,
219 req: Request<MultipartForm>,
220 ) -> impl Future<Output = Result<Response<LazyBody<U>>>> + WasmCompatSend + 'static
221 where
222 U: From<Bytes>,
223 U: WasmCompatSend + 'static;
224
225 fn send_streaming<T>(
227 &self,
228 req: Request<T>,
229 ) -> impl Future<Output = Result<StreamingResponse>> + WasmCompatSend
230 where
231 T: Into<Bytes> + WasmCompatSend;
232}
233
234#[cfg(test)]
235mod non_success_header_tests;