use std::time::Duration;
pub const CONNECT: Duration = Duration::from_secs(5);
pub const REQUEST: Duration = Duration::from_secs(15);
pub const TRANSFER: Duration = Duration::from_secs(300);
pub fn client() -> reqwest::Client {
client_with(CONNECT, REQUEST)
}
pub fn patient_client() -> reqwest::Client {
client_with(CONNECT, TRANSFER)
}
pub fn client_with(connect: Duration, request: Duration) -> reqwest::Client {
reqwest::Client::builder()
.connect_timeout(connect)
.timeout(request)
.build()
.unwrap_or_else(|_| reqwest::Client::new())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Reach {
Connect,
Timeout,
Transport,
}
pub fn reach(failure: &reqwest::Error) -> Reach {
if failure.is_connect() {
Reach::Connect
} else if failure.is_timeout() {
Reach::Timeout
} else {
Reach::Transport
}
}
pub fn describe(url: &str, reach: Reach) -> String {
match reach {
Reach::Connect => {
format!("cannot reach the platform at {url} — no connection could be opened")
}
Reach::Timeout => format!("cannot reach the platform at {url} — it did not answer in time"),
Reach::Transport => format!("cannot reach the platform at {url} — the connection failed"),
}
}
pub fn unreachable(url: &str, failure: reqwest::Error) -> anyhow::Error {
let sentence = describe(url, reach(&failure));
anyhow::Error::new(failure).context(sentence)
}
pub fn refused(url: &str, status: u16, body: &str) -> String {
match crate::api::error_code(body) {
Some(code) => format!("the platform answered {status} ({code}) at {url}"),
None => format!("the platform answered {status} at {url}"),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_deadline_is_finite_and_ordered() {
assert!(CONNECT < REQUEST);
assert!(REQUEST < TRANSFER);
assert!(!CONNECT.is_zero());
}
#[test]
fn an_unreachable_platform_names_the_url_that_was_tried() {
let url = "https://api-staging.portaki.app/api/v1/auth/device/code";
for reach in [Reach::Connect, Reach::Timeout, Reach::Transport] {
let sentence = describe(url, reach);
assert!(sentence.contains(url), "{sentence}");
assert!(sentence.contains("cannot reach the platform"), "{sentence}");
}
}
#[test]
fn the_three_causes_read_differently() {
let url = "https://example.test";
assert_ne!(describe(url, Reach::Connect), describe(url, Reach::Timeout));
assert_ne!(
describe(url, Reach::Timeout),
describe(url, Reach::Transport)
);
}
#[test]
fn a_status_is_never_reported_as_unreachable() {
let sentence = refused("https://example.test/api/v1/auth/device/code", 404, "");
assert!(sentence.contains("404"), "{sentence}");
assert!(sentence.contains("https://example.test"), "{sentence}");
assert!(!sentence.contains("cannot reach"), "{sentence}");
}
#[test]
fn a_refusal_carries_its_error_code_when_there_is_one() {
let body = r#"{"success":false,"error_code":"unsupported_grant_type"}"#;
assert!(refused("https://example.test/x", 400, body).contains("unsupported_grant_type"));
}
}