mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-10-20 22:41:22 +02:00
69 lines
2.3 KiB
Rust
69 lines
2.3 KiB
Rust
//! JSON calls to Donut cloud as the signed-in account: the token is refreshed
|
|
//! and the call retried once when it went stale, and a failure becomes the
|
|
//! `{"code":…}` error the frontend translates, with the caller's codes for
|
|
//! the statuses that mean something to it.
|
|
|
|
use crate::cloud_errors::{self, BackendFailure, FailureCodes};
|
|
use serde::de::DeserializeOwned;
|
|
use std::sync::OnceLock;
|
|
use std::time::Duration;
|
|
|
|
const REQUEST_TIMEOUT: Duration = Duration::from_secs(20);
|
|
const CONNECT_TIMEOUT: Duration = Duration::from_secs(5);
|
|
|
|
fn http() -> &'static reqwest::Client {
|
|
static CLIENT: OnceLock<reqwest::Client> = OnceLock::new();
|
|
CLIENT.get_or_init(|| {
|
|
reqwest::Client::builder()
|
|
.timeout(REQUEST_TIMEOUT)
|
|
.connect_timeout(CONNECT_TIMEOUT)
|
|
.build()
|
|
.unwrap_or_else(|_| reqwest::Client::new())
|
|
})
|
|
}
|
|
|
|
pub(crate) async fn call<T: DeserializeOwned>(
|
|
method: reqwest::Method,
|
|
url: String,
|
|
body: Option<serde_json::Value>,
|
|
codes: FailureCodes,
|
|
) -> Result<T, BackendFailure> {
|
|
crate::cloud_auth::CLOUD_AUTH
|
|
.api_call_with_retry(|token| {
|
|
let method = method.clone();
|
|
let url = url.clone();
|
|
let body = body.clone();
|
|
async move {
|
|
let mut builder = http().request(method, &url).bearer_auth(token);
|
|
if let Some(payload) = body {
|
|
builder = builder.json(&payload);
|
|
}
|
|
let response = builder
|
|
.send()
|
|
.await
|
|
.map_err(|e| format!("reach backend: {e}"))?;
|
|
let status = response.status().as_u16();
|
|
if !(200..300).contains(&status) {
|
|
let text = response.text().await.unwrap_or_default();
|
|
// api_call_with_retry and classify_message read the status from this prefix.
|
|
return Err(format!("({status}) {text}"));
|
|
}
|
|
let bytes = response
|
|
.bytes()
|
|
.await
|
|
.map_err(|e| format!("decode response: {e}"))?;
|
|
decode_body::<T>(&bytes).map_err(|e| format!("decode response: {e}"))
|
|
}
|
|
})
|
|
.await
|
|
.map_err(|e| cloud_errors::classify_message(&e, codes))
|
|
}
|
|
|
|
pub(crate) fn decode_body<T: DeserializeOwned>(bytes: &[u8]) -> Result<T, serde_json::Error> {
|
|
if bytes.iter().all(u8::is_ascii_whitespace) {
|
|
serde_json::from_slice(b"null")
|
|
} else {
|
|
serde_json::from_slice(bytes)
|
|
}
|
|
}
|