cognite/api/request_builder/
builder.rs

1use reqwest::header::{HeaderName, HeaderValue, ACCEPT, CONTENT_TYPE, USER_AGENT};
2
3use prost::Message;
4use serde::de::DeserializeOwned;
5use serde::Serialize;
6
7use crate::ApiClient;
8use crate::Error;
9use crate::SkipAuthentication;
10use reqwest::{IntoUrl, Response};
11
12use crate::Result;
13
14use super::{
15    JsonResponseHandler, NoResponseHandler, ProtoResponseHandler, RawResponseHandler,
16    ResponseHandler,
17};
18
19/// Generic request builder. Used to construct custom requests towards CDF.
20pub struct RequestBuilder<'a, T = ()> {
21    inner: reqwest_middleware::RequestBuilder,
22    client: &'a ApiClient,
23    output: T,
24}
25
26impl<'a> RequestBuilder<'a, ()> {
27    /// Create a POST request to `url`.
28    ///
29    /// # Arguments
30    ///
31    /// * `client` - API Client
32    /// * `url` - URL to send request to.
33    pub fn post(client: &'a ApiClient, url: impl IntoUrl) -> RequestBuilder<'a, ()> {
34        RequestBuilder {
35            inner: client.client().post(url),
36            client,
37            output: (),
38        }
39    }
40
41    /// Create a GET request to `url`.
42    ///
43    /// # Arguments
44    ///
45    /// * `client` - API Client
46    /// * `url` - URL to send request to.
47    pub fn get(client: &'a ApiClient, url: impl IntoUrl) -> RequestBuilder<'a, ()> {
48        RequestBuilder {
49            inner: client.client().get(url),
50            client,
51            output: (),
52        }
53    }
54
55    /// Create a DELETE request to `url`.
56    ///
57    /// # Arguments
58    ///
59    /// * `client` - API Client
60    /// * `url` - URL to send request to.
61    pub fn delete(client: &'a ApiClient, url: impl IntoUrl) -> RequestBuilder<'a, ()> {
62        RequestBuilder {
63            inner: client.client().delete(url),
64            client,
65            output: (),
66        }
67    }
68
69    /// Create a PUT request to `url`.
70    ///
71    /// # Arguments
72    ///
73    /// * `client` - API Client
74    /// * `url` - URL to send request to.
75    pub fn put(client: &'a ApiClient, url: impl IntoUrl) -> RequestBuilder<'a, ()> {
76        RequestBuilder {
77            inner: client.client().put(url),
78            client,
79            output: (),
80        }
81    }
82}
83
84impl<'a, T> RequestBuilder<'a, T> {
85    /// Add a header to the request.
86    ///
87    /// # Arguments
88    ///
89    /// * `key` - Header key
90    /// * `value` - Header value
91    pub fn header<K, V>(mut self, key: K, value: V) -> Self
92    where
93        HeaderName: TryFrom<K>,
94        <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
95        HeaderValue: TryFrom<V>,
96        <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
97    {
98        self.inner = self.inner.header(key, value);
99        self
100    }
101
102    /// Add a query to the request.
103    pub fn query<Q: Serialize + ?Sized>(mut self, query: &Q) -> Self {
104        self.inner = self.inner.query(query);
105        self
106    }
107
108    /// Add a JSON body to the request. This sets `CONTENT_TYPE`.
109    pub fn json<B: Serialize + ?Sized>(mut self, body: &B) -> Result<Self> {
110        self.inner = self.inner.header(
111            CONTENT_TYPE,
112            const { HeaderValue::from_static("application/json") },
113        );
114        Ok(self.body(serde_json::to_vec(body)?))
115    }
116
117    /// Add a body to the request. You will typically want to set `CONTENT_TYPE` yourself.
118    pub fn body(mut self, body: impl Into<reqwest::Body>) -> Self {
119        self.inner = self.inner.body(body);
120        self
121    }
122
123    /// Add a protobuf message as body to the request. This sets `CONTENT_TYPE`
124    pub fn protobuf<B: Message>(mut self, body: &B) -> Self {
125        self.inner = self.inner.header(
126            CONTENT_TYPE,
127            const { HeaderValue::from_static("application/protobuf") },
128        );
129        self.body(body.encode_to_vec())
130    }
131
132    /// Omit authentication headers in the request.
133    ///
134    /// This should be set before calling untrusted URLs.
135    pub fn omit_auth_headers(mut self) -> Self {
136        self.inner = self.inner.with_extension(SkipAuthentication);
137        self
138    }
139
140    /// Modify the inner request builder.
141    pub fn with_inner<
142        R: FnOnce(reqwest_middleware::RequestBuilder) -> reqwest_middleware::RequestBuilder,
143    >(
144        mut self,
145        m: R,
146    ) -> Self {
147        self.inner = m(self.inner);
148        self
149    }
150}
151
152impl<'a> RequestBuilder<'a, ()> {
153    /// Expect the response for a successful request to be `T` encoded as JSON.
154    pub fn accept_json<T: DeserializeOwned>(self) -> RequestBuilder<'a, JsonResponseHandler<T>> {
155        self.accept(JsonResponseHandler::new())
156    }
157
158    /// Expect the response for a successful request to be `T` encoded as protobuf.
159    pub fn accept_protobuf<T: Message + Default + Send + Sync>(
160        self,
161    ) -> RequestBuilder<'a, ProtoResponseHandler<T>> {
162        self.accept(ProtoResponseHandler::new())
163    }
164
165    /// Ignore the response for a successful request.
166    pub fn accept_nothing(self) -> RequestBuilder<'a, NoResponseHandler> {
167        self.accept(NoResponseHandler)
168    }
169
170    /// Simply return the raw payload on a successful request.
171    pub fn accept_raw(self) -> RequestBuilder<'a, RawResponseHandler> {
172        self.accept(RawResponseHandler)
173    }
174
175    /// Set the response handler `T`.
176    pub fn accept<T: ResponseHandler>(self, handler: T) -> RequestBuilder<'a, T> {
177        RequestBuilder {
178            inner: self
179                .inner
180                .header(ACCEPT, const { HeaderValue::from_static(T::ACCEPT_HEADER) }),
181            client: self.client,
182            output: handler,
183        }
184    }
185}
186
187async fn handle_error(response: Response) -> Error {
188    let request_id = response
189        .headers()
190        .get("x-request-id")
191        .and_then(|x| x.to_str().ok())
192        .map(|x| x.to_string());
193
194    let status = response.status();
195
196    match &response.text().await {
197        Ok(s) => match serde_json::from_str(s) {
198            Ok(error_message) => Error::new_from_cdf(status, error_message, request_id),
199            Err(e) => Error::new_without_json(status, format!("{e}. Raw: {s}"), request_id),
200        },
201        Err(e) => Error::new_without_json(status, e.to_string(), request_id),
202    }
203}
204
205const SDK_USER_AGENT: &str = concat!("CogniteSdkRust/", env!("CARGO_PKG_VERSION"));
206const SDK_VERSION: &str = concat!("rust-sdk-v", env!("CARGO_PKG_VERSION"));
207
208impl<T: ResponseHandler> RequestBuilder<'_, T> {
209    /// Send the request. This sets a few core headers, and converts any errors into
210    /// [crate::Error]
211    pub async fn send(mut self) -> Result<T::Output> {
212        self.inner.extensions().insert(self.client.client().clone());
213
214        self.inner = self
215            .inner
216            .header(
217                USER_AGENT,
218                const { HeaderValue::from_static(SDK_USER_AGENT) },
219            )
220            .header("x-cdp-sdk", const { HeaderValue::from_static(SDK_VERSION) })
221            .header(
222                "x-cdp-app",
223                HeaderValue::from_str(self.client.app_name()).expect("Invalid app name"),
224            );
225        if let Some(cdf_version) = self.client.api_version() {
226            self.inner = self.inner.header(
227                "cdf-version",
228                HeaderValue::from_str(cdf_version).expect("Invalid CDF version"),
229            );
230        }
231
232        match self.inner.send().await {
233            Ok(response) => {
234                if response.status().is_success() {
235                    self.output.handle_response(response).await
236                } else {
237                    Err(handle_error(response).await)
238                }
239            }
240            Err(e) => Err(e.into()),
241        }
242    }
243}