use reqwest::header::{HeaderName, HeaderValue, ACCEPT, CONTENT_TYPE, USER_AGENT};
use prost::Message;
use serde::de::DeserializeOwned;
use serde::Serialize;
use crate::ApiClient;
use crate::CondSend;
use crate::CondSync;
use crate::Error;
use crate::SkipAuthentication;
use reqwest::{IntoUrl, Response};
use crate::Result;
use super::{
JsonResponseHandler, NoResponseHandler, ProtoResponseHandler, RawResponseHandler,
ResponseHandler,
};
pub struct RequestBuilder<'a, T = ()> {
inner: reqwest_middleware::RequestBuilder,
client: &'a ApiClient,
output: T,
}
impl<'a> RequestBuilder<'a, ()> {
pub fn post(client: &'a ApiClient, url: impl IntoUrl) -> RequestBuilder<'a, ()> {
RequestBuilder {
inner: client.client().post(url),
client,
output: (),
}
}
pub fn get(client: &'a ApiClient, url: impl IntoUrl) -> RequestBuilder<'a, ()> {
RequestBuilder {
inner: client.client().get(url),
client,
output: (),
}
}
pub fn delete(client: &'a ApiClient, url: impl IntoUrl) -> RequestBuilder<'a, ()> {
RequestBuilder {
inner: client.client().delete(url),
client,
output: (),
}
}
pub fn put(client: &'a ApiClient, url: impl IntoUrl) -> RequestBuilder<'a, ()> {
RequestBuilder {
inner: client.client().put(url),
client,
output: (),
}
}
}
impl<'a, T> RequestBuilder<'a, T> {
pub fn header<K, V>(mut self, key: K, value: V) -> Self
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
{
self.inner = self.inner.header(key, value);
self
}
pub fn query<Q: Serialize + ?Sized>(mut self, query: &Q) -> Self {
self.inner = self.inner.query(query);
self
}
pub fn json<B: Serialize + ?Sized>(mut self, body: &B) -> Result<Self> {
self.inner = self.inner.header(
CONTENT_TYPE,
const { HeaderValue::from_static("application/json") },
);
Ok(self.body(serde_json::to_vec(body)?))
}
pub fn body(mut self, body: impl Into<reqwest::Body>) -> Self {
self.inner = self.inner.body(body);
self
}
pub fn protobuf<B: Message>(mut self, body: &B) -> Self {
self.inner = self.inner.header(
CONTENT_TYPE,
const { HeaderValue::from_static("application/protobuf") },
);
self.body(body.encode_to_vec())
}
pub fn omit_auth_headers(mut self) -> Self {
self.inner = self.inner.with_extension(SkipAuthentication);
self
}
pub fn with_inner<
R: FnOnce(reqwest_middleware::RequestBuilder) -> reqwest_middleware::RequestBuilder,
>(
mut self,
m: R,
) -> Self {
self.inner = m(self.inner);
self
}
}
impl<'a> RequestBuilder<'a, ()> {
pub fn accept_json<T: DeserializeOwned>(self) -> RequestBuilder<'a, JsonResponseHandler<T>> {
self.accept(JsonResponseHandler::new())
}
pub fn accept_protobuf<T: Message + Default + CondSend + CondSync>(
self,
) -> RequestBuilder<'a, ProtoResponseHandler<T>> {
self.accept(ProtoResponseHandler::new())
}
pub fn accept_nothing(self) -> RequestBuilder<'a, NoResponseHandler> {
self.accept(NoResponseHandler)
}
pub fn accept_raw(self) -> RequestBuilder<'a, RawResponseHandler> {
self.accept(RawResponseHandler)
}
pub fn accept<T: ResponseHandler>(self, handler: T) -> RequestBuilder<'a, T> {
RequestBuilder {
inner: self
.inner
.header(ACCEPT, const { HeaderValue::from_static(T::ACCEPT_HEADER) }),
client: self.client,
output: handler,
}
}
}
async fn handle_error(response: Response) -> Error {
let request_id = response
.headers()
.get("x-request-id")
.and_then(|x| x.to_str().ok())
.map(|x| x.to_string());
let status = response.status();
match &response.text().await {
Ok(s) => match serde_json::from_str(s) {
Ok(error_message) => Error::new_from_cdf(status, error_message, request_id),
Err(e) => Error::new_without_json(status, format!("{e}. Raw: {s}"), request_id),
},
Err(e) => Error::new_without_json(status, e.to_string(), request_id),
}
}
const SDK_USER_AGENT: &str = concat!("CogniteSdkRust/", env!("CARGO_PKG_VERSION"));
const SDK_VERSION: &str = concat!("rust-sdk-v", env!("CARGO_PKG_VERSION"));
impl<T: ResponseHandler> RequestBuilder<'_, T> {
pub async fn send(mut self) -> Result<T::Output> {
self.inner.extensions().insert(self.client.client().clone());
self.inner = self
.inner
.header(
USER_AGENT,
const { HeaderValue::from_static(SDK_USER_AGENT) },
)
.header("x-cdp-sdk", const { HeaderValue::from_static(SDK_VERSION) })
.header(
"x-cdp-app",
HeaderValue::from_str(self.client.app_name()).expect("Invalid app name"),
);
if let Some(cdf_version) = self.client.api_version() {
self.inner = self.inner.header(
"cdf-version",
HeaderValue::from_str(cdf_version).expect("Invalid CDF version"),
);
}
match self.inner.send().await {
Ok(response) => {
if response.status().is_success() {
self.output.handle_response(response).await
} else {
Err(handle_error(response).await)
}
}
Err(e) => Err(e.into()),
}
}
}