kunobi_decision/client/
call.rs1use std::future::{Future, IntoFuture};
4use std::pin::Pin;
5use std::time::Duration;
6
7use bytes::Bytes;
8use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
9use reqwest::{Method, StatusCode};
10use serde::de::DeserializeOwned;
11
12use super::Client;
13use super::builder::validate_timeout;
14use super::transport::Resolved;
15use crate::error::{Error, Result};
16use crate::retry::RetryPolicy;
17
18pub(crate) struct DecodeFailure {
20 pub(crate) message: String,
21 pub(crate) source: Option<serde_json::Error>,
22}
23
24pub(crate) type Parser<T> = fn(&[u8]) -> std::result::Result<T, DecodeFailure>;
25
26pub(crate) fn parse_json<T: DeserializeOwned>(
27 body: &[u8],
28) -> std::result::Result<T, DecodeFailure> {
29 serde_json::from_slice(body).map_err(|err| DecodeFailure {
30 message: format!("Could not parse the response body: {err}"),
31 source: Some(err),
32 })
33}
34
35#[must_use = "a call does nothing until it is awaited"]
55pub struct Call<T> {
56 client: Client,
57 method: Method,
58 path: String,
59 body: Result<Option<Bytes>>,
60 timeout: Option<Duration>,
61 total_timeout: Option<Option<Duration>>,
64 retry: Option<RetryPolicy>,
65 max_retries: Option<u32>,
66 headers: HeaderMap,
67 parse: Parser<T>,
68}
69
70#[derive(Debug, Clone)]
72pub struct WithResponse<T> {
73 pub data: T,
75 pub status: StatusCode,
77 pub headers: HeaderMap,
79 pub request_id: Option<String>,
81}
82
83#[derive(Debug, Clone)]
85pub struct RawResponse {
86 pub status: StatusCode,
88 pub headers: HeaderMap,
90 pub body: Bytes,
92 pub request_id: Option<String>,
94}
95
96impl<T> Call<T> {
97 pub(crate) fn new(
98 client: Client,
99 method: Method,
100 path: impl Into<String>,
101 body: Result<Option<Vec<u8>>>,
102 parse: Parser<T>,
103 ) -> Self {
104 Self {
105 client,
106 method,
107 path: path.into(),
108 body: body.map(|body| body.map(Bytes::from)),
109 timeout: None,
110 total_timeout: None,
111 retry: None,
112 max_retries: None,
113 headers: HeaderMap::new(),
114 parse,
115 }
116 }
117
118 pub fn timeout(mut self, timeout: Duration) -> Self {
120 self.timeout = Some(timeout);
121 self
122 }
123
124 pub fn total_timeout(mut self, total_timeout: impl Into<Option<Duration>>) -> Self {
131 self.total_timeout = Some(total_timeout.into());
132 self
133 }
134
135 pub fn retry(mut self, retry: RetryPolicy) -> Self {
137 self.retry = Some(retry);
138 self
139 }
140
141 pub fn max_retries(mut self, max_retries: u32) -> Self {
145 self.max_retries = Some(max_retries);
146 self
147 }
148
149 pub fn header(mut self, name: HeaderName, value: HeaderValue) -> Self {
153 self.headers.insert(name, value);
154 self
155 }
156
157 pub fn headers(mut self, headers: HeaderMap) -> Self {
159 self.headers.extend(headers);
160 self
161 }
162
163 pub async fn raw(self) -> Result<RawResponse> {
165 let client = self.client.clone();
166 let resolved = self.resolve()?;
167 client.execute(resolved).await
168 }
169
170 pub async fn with_response(self) -> Result<WithResponse<T>> {
172 let parse = self.parse;
173 let raw = self.raw().await?;
174 match parse(&raw.body) {
175 Ok(data) => Ok(WithResponse {
176 data,
177 status: raw.status,
178 headers: raw.headers,
179 request_id: raw.request_id,
180 }),
181 Err(failure) => Err(Error::Decode {
182 message: failure.message,
183 request_id: raw.request_id,
184 source: failure.source,
185 }),
186 }
187 }
188
189 fn resolve(self) -> Result<Resolved> {
190 let body = self.body?;
191 let timeout = validate_timeout(self.timeout.unwrap_or(self.client.timeout()))?;
192 let total_timeout = match self.total_timeout.unwrap_or(self.client.total_timeout()) {
193 Some(total) => Some(validate_timeout(total).map_err(|_| {
194 Error::Config("`total_timeout` must be a positive duration, got 0.".into())
195 })?),
196 None => None,
197 };
198 let mut retry = self.retry.unwrap_or_else(|| self.client.retry().clone());
199 if let Some(max_retries) = self.max_retries {
200 retry.max_retries = max_retries;
201 }
202 retry.validate()?;
203
204 let mut headers = self.client.default_headers().clone();
205 headers.extend(self.headers);
206 Ok(Resolved {
207 method: self.method,
208 path: self.path,
209 body,
210 headers,
211 timeout,
212 retry,
213 total_timeout,
214 })
215 }
216}
217
218impl<T: Send + 'static> IntoFuture for Call<T> {
219 type Output = Result<T>;
220 type IntoFuture = Pin<Box<dyn Future<Output = Result<T>> + Send>>;
221
222 fn into_future(self) -> Self::IntoFuture {
223 Box::pin(async move { self.with_response().await.map(|response| response.data) })
224 }
225}