1use crate::{request::ApiRequest, Backend, BoxStream};
10use async_trait::async_trait;
11use bytes::Bytes;
12use common_multipart_rfc7578::client::multipart;
13use serde::{Serialize, Serializer};
14use std::time::Duration;
15
16#[cfg_attr(feature = "with-builder", derive(TypedBuilder))]
21#[derive(Serialize, Default)]
22#[serde(rename_all = "kebab-case")]
23pub struct GlobalOptions {
24 #[cfg_attr(feature = "with-builder", builder(default, setter(strip_option)))]
25 pub offline: Option<bool>,
26
27 #[cfg_attr(feature = "with-builder", builder(default, setter(strip_option)))]
28 #[serde(serialize_with = "duration_as_secs_ns")]
29 pub timeout: Option<Duration>,
30}
31
32fn duration_as_secs_ns<S>(duration: &Option<Duration>, serializer: S) -> Result<S::Ok, S::Error>
33where
34 S: Serializer,
35{
36 match duration {
37 Some(duration) => serializer.serialize_str(&format!(
38 "{}s{}ns",
39 duration.as_secs(),
40 duration.subsec_nanos(),
41 )),
42 None => serializer.serialize_none(),
43 }
44}
45
46pub struct BackendWithGlobalOptions<Back: Backend> {
48 backend: Back,
49 options: GlobalOptions,
50}
51
52#[derive(Serialize)]
53struct OptCombiner<'a, Req>
54where
55 Req: ApiRequest,
56{
57 #[serde(flatten)]
58 global: &'a GlobalOptions,
59
60 #[serde(flatten)]
61 request: Req,
62}
63
64impl<Back: Backend> BackendWithGlobalOptions<Back> {
65 pub fn into_inner(self) -> Back {
67 self.backend
68 }
69
70 pub fn new(backend: Back, options: GlobalOptions) -> Self {
74 Self { backend, options }
75 }
76
77 fn with_credentials<U, P>(self, username: U, password: P) -> Self
78 where
79 U: Into<String>,
80 P: Into<String>,
81 {
82 Self {
83 backend: self.backend.with_credentials(username, password),
84 options: self.options,
85 }
86 }
87
88 fn combine<Req>(&self, req: Req) -> OptCombiner<Req>
89 where
90 Req: ApiRequest,
91 {
92 OptCombiner {
93 global: &self.options,
94 request: req,
95 }
96 }
97}
98
99impl<'a, Req> ApiRequest for OptCombiner<'a, Req>
100where
101 Req: ApiRequest,
102{
103 const PATH: &'static str = <Req as ApiRequest>::PATH;
104
105 const METHOD: http::Method = http::Method::POST;
106}
107
108#[cfg(feature = "with-send-sync")]
109#[async_trait]
110impl<Back: Backend + Send + Sync> Backend for BackendWithGlobalOptions<Back> {
111 type HttpRequest = Back::HttpRequest;
112
113 type HttpResponse = Back::HttpResponse;
114
115 type Error = Back::Error;
116
117 fn with_credentials<U, P>(self, username: U, password: P) -> Self
118 where
119 U: Into<String>,
120 P: Into<String>,
121 {
122 (self as BackendWithGlobalOptions<Back>).with_credentials(username, password)
123 }
124
125 fn build_base_request<Req>(
126 &self,
127 req: Req,
128 form: Option<multipart::Form<'static>>,
129 ) -> Result<Self::HttpRequest, Self::Error>
130 where
131 Req: ApiRequest,
132 {
133 self.backend.build_base_request(self.combine(req), form)
134 }
135
136 fn get_header(
137 res: &Self::HttpResponse,
138 key: http::header::HeaderName,
139 ) -> Option<&http::HeaderValue> {
140 Back::get_header(res, key)
141 }
142
143 async fn request_raw<Req>(
144 &self,
145 req: Req,
146 form: Option<multipart::Form<'static>>,
147 ) -> Result<(http::StatusCode, bytes::Bytes), Self::Error>
148 where
149 Req: ApiRequest,
150 {
151 self.backend.request_raw(self.combine(req), form).await
152 }
153
154 fn response_to_byte_stream(res: Self::HttpResponse) -> BoxStream<Bytes, Self::Error> {
155 Back::response_to_byte_stream(res)
156 }
157
158 fn request_stream<Res, F>(
159 &self,
160 req: Self::HttpRequest,
161 process: F,
162 ) -> BoxStream<Res, Self::Error>
163 where
164 F: 'static + Send + Fn(Self::HttpResponse) -> BoxStream<Res, Self::Error>,
165 {
166 self.backend.request_stream(req, process)
167 }
168}
169
170#[cfg(not(feature = "with-send-sync"))]
171#[async_trait(?Send)]
172impl<Back: Backend> Backend for BackendWithGlobalOptions<Back> {
173 type HttpRequest = Back::HttpRequest;
174
175 type HttpResponse = Back::HttpResponse;
176
177 type Error = Back::Error;
178
179 fn with_credentials<U, P>(self, username: U, password: P) -> Self
180 where
181 U: Into<String>,
182 P: Into<String>,
183 {
184 (self as BackendWithGlobalOptions<Back>).with_credentials(username, password)
185 }
186
187 fn build_base_request<Req>(
188 &self,
189 req: Req,
190 form: Option<multipart::Form<'static>>,
191 ) -> Result<Self::HttpRequest, Self::Error>
192 where
193 Req: ApiRequest,
194 {
195 self.backend.build_base_request(self.combine(req), form)
196 }
197
198 fn get_header(
199 res: &Self::HttpResponse,
200 key: http::header::HeaderName,
201 ) -> Option<&http::HeaderValue> {
202 Back::get_header(res, key)
203 }
204
205 async fn request_raw<Req>(
206 &self,
207 req: Req,
208 form: Option<multipart::Form<'static>>,
209 ) -> Result<(http::StatusCode, bytes::Bytes), Self::Error>
210 where
211 Req: ApiRequest,
212 {
213 self.backend.request_raw(self.combine(req), form).await
214 }
215
216 fn response_to_byte_stream(res: Self::HttpResponse) -> BoxStream<Bytes, Self::Error> {
217 Back::response_to_byte_stream(res)
218 }
219
220 fn request_stream<Res, F>(
221 &self,
222 req: Self::HttpRequest,
223 process: F,
224 ) -> BoxStream<Res, Self::Error>
225 where
226 F: 'static + Send + Fn(Self::HttpResponse) -> BoxStream<Res, Self::Error>,
227 {
228 self.backend.request_stream(req, process)
229 }
230}