1use std::future::Future;
2use std::pin::Pin;
3use std::sync::Arc;
4
5use bytes::Bytes;
6use http::{HeaderMap, Method};
7use url::Url;
8
9use crate::error::Error;
10use crate::response::Response;
11use crate::Result;
12
13#[derive(Debug, Clone)]
15pub struct RequestContext {
16 pub url: Url,
17 pub method: Method,
18 pub headers: HeaderMap,
19 pub body: Option<Bytes>,
20 pub retry_attempt: u32,
24}
25
26#[derive(Debug, Clone)]
28pub struct ResponseContext {
29 pub request: RequestContext,
30 pub response: Response,
31}
32
33#[derive(Debug, Clone)]
35pub struct SuccessContext {
36 pub request: RequestContext,
37 pub response: Response,
38}
39
40#[derive(Debug, Clone)]
42pub struct ErrorContext {
43 pub request: RequestContext,
44 pub response: Option<Response>,
45 pub error: Error,
46}
47
48type RequestHookFn = Arc<
49 dyn Fn(RequestContext) -> Pin<Box<dyn Future<Output = Result<RequestContext>> + Send>>
50 + Send
51 + Sync,
52>;
53
54type ResponseHookFn = Arc<
55 dyn Fn(ResponseContext) -> Pin<Box<dyn Future<Output = Result<Response>> + Send>> + Send + Sync,
56>;
57
58type SuccessHookFn =
59 Arc<dyn Fn(SuccessContext) -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + Sync>;
60
61type ErrorHookFn =
62 Arc<dyn Fn(ErrorContext) -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + Sync>;
63
64type RetryHookFn =
65 Arc<dyn Fn(ResponseContext) -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + Sync>;
66
67#[derive(Clone, Default)]
69pub struct Hooks {
70 pub(crate) on_request: Vec<RequestHookFn>,
71 pub(crate) on_response: Vec<ResponseHookFn>,
72 pub(crate) on_success: Vec<SuccessHookFn>,
73 pub(crate) on_error: Vec<ErrorHookFn>,
74 pub(crate) on_retry: Vec<RetryHookFn>,
75}
76
77impl Hooks {
78 pub fn new() -> Self {
79 Self::default()
80 }
81
82 pub fn on_request<F, Fut>(mut self, f: F) -> Self
83 where
84 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
85 Fut: Future<Output = Result<RequestContext>> + Send + 'static,
86 {
87 self.on_request.push(Arc::new(move |ctx| Box::pin(f(ctx))));
88 self
89 }
90
91 pub fn on_response<F, Fut>(mut self, f: F) -> Self
92 where
93 F: Fn(ResponseContext) -> Fut + Send + Sync + 'static,
94 Fut: Future<Output = Result<Response>> + Send + 'static,
95 {
96 self.on_response.push(Arc::new(move |ctx| Box::pin(f(ctx))));
97 self
98 }
99
100 pub fn on_success<F, Fut>(mut self, f: F) -> Self
101 where
102 F: Fn(SuccessContext) -> Fut + Send + Sync + 'static,
103 Fut: Future<Output = ()> + Send + 'static,
104 {
105 self.on_success.push(Arc::new(move |ctx| Box::pin(f(ctx))));
106 self
107 }
108
109 pub fn on_error<F, Fut>(mut self, f: F) -> Self
110 where
111 F: Fn(ErrorContext) -> Fut + Send + Sync + 'static,
112 Fut: Future<Output = ()> + Send + 'static,
113 {
114 self.on_error.push(Arc::new(move |ctx| Box::pin(f(ctx))));
115 self
116 }
117
118 pub fn on_retry<F, Fut>(mut self, f: F) -> Self
119 where
120 F: Fn(ResponseContext) -> Fut + Send + Sync + 'static,
121 Fut: Future<Output = ()> + Send + 'static,
122 {
123 self.on_retry.push(Arc::new(move |ctx| Box::pin(f(ctx))));
124 self
125 }
126
127 pub(crate) fn merge(mut self, other: Hooks) -> Self {
128 self.on_request.extend(other.on_request);
129 self.on_response.extend(other.on_response);
130 self.on_success.extend(other.on_success);
131 self.on_error.extend(other.on_error);
132 self.on_retry.extend(other.on_retry);
133 self
134 }
135
136 pub(crate) async fn run_on_request(&self, mut ctx: RequestContext) -> Result<RequestContext> {
137 for hook in &self.on_request {
138 ctx = hook(ctx).await?;
139 }
140 Ok(ctx)
141 }
142
143 pub(crate) async fn run_on_response(&self, ctx: ResponseContext) -> Result<Response> {
144 let request = ctx.request;
145 let mut response = ctx.response;
146 for hook in &self.on_response {
147 response = hook(ResponseContext {
148 request: request.clone(),
149 response,
150 })
151 .await?;
152 }
153 Ok(response)
154 }
155
156 pub(crate) async fn run_on_success(&self, ctx: SuccessContext) {
157 for hook in &self.on_success {
158 hook(ctx.clone()).await;
159 }
160 }
161
162 pub(crate) async fn run_on_error(&self, ctx: ErrorContext) {
163 for hook in &self.on_error {
164 hook(ctx.clone()).await;
165 }
166 }
167
168 pub(crate) async fn run_on_retry(&self, ctx: ResponseContext) {
169 for hook in &self.on_retry {
170 hook(ctx.clone()).await;
171 }
172 }
173}