escouade_plugin_kit/
http.rs1#[cfg(any(test, target_family = "wasm"))]
8pub mod fetch;
9
10#[cfg(not(target_family = "wasm"))]
11pub use ::reqwest;
12#[cfg(target_family = "wasm")]
13pub use fetch as reqwest;
14
15use self::reqwest::header::HeaderMap;
16use crate::errors::{http_error_in, HttpError};
17use crate::tr;
18use anyhow::{anyhow, Result};
19use serde_json::Value;
20use std::time::Duration;
21
22#[derive(Debug, Clone, Default, PartialEq)]
27pub struct Network {
28 pub proxy: String,
29 pub no_proxy: String,
30 pub insecure_tls: bool,
31}
32
33impl Network {
34 pub fn from_env() -> Self {
35 let var = |k: &str| std::env::var(k).unwrap_or_default();
36 Self {
37 proxy: var("ESCOUADE_PROXY").trim().to_string(),
38 no_proxy: var("ESCOUADE_NO_PROXY"),
39 insecure_tls: var("ESCOUADE_INSECURE_TLS").trim() == "1",
40 }
41 }
42
43 pub fn client(&self) -> Result<reqwest::Client> {
45 let mut b = reqwest::Client::builder()
46 .timeout(Duration::from_secs(15))
47 .danger_accept_invalid_certs(self.insecure_tls);
48 if !self.proxy.is_empty() {
49 let proxy = reqwest::Proxy::all(&self.proxy)?
50 .no_proxy(reqwest::NoProxy::from_string(&self.no_proxy));
51 b = b.proxy(proxy);
52 }
53 Ok(b.build()?)
54 }
55}
56
57pub async fn send_named(name: &str, rb: reqwest::RequestBuilder) -> Result<(Value, HeaderMap)> {
60 let resp = rb.send().await.map_err(|e| {
61 let e = anyhow::Error::from(e.without_url());
62 anyhow!(tr!(
63 "{name} injoignable : {e:#}",
64 "{name} can’t be reached: {e:#}"
65 ))
66 })?;
67 let status = resp.status().as_u16();
68 let headers = resp.headers().clone();
69 let text = resp.text().await.unwrap_or_default();
70 if !(200..300).contains(&status) {
71 return Err(HttpError {
72 status,
73 message: http_error_in(crate::i18n::ui(), name, status, &text),
74 }
75 .into());
76 }
77 if text.trim().is_empty() {
78 return Ok((Value::Null, headers));
79 }
80 let v = serde_json::from_str(&text).map_err(|_| {
81 anyhow!(tr!(
82 "{name} : réponse illisible",
83 "{name}: unreadable answer"
84 ))
85 })?;
86 Ok((v, headers))
87}
88
89pub async fn call(name: &str, rb: reqwest::RequestBuilder) -> Result<Value> {
91 Ok(send_named(name, rb).await?.0)
92}
93
94pub async fn call_page(name: &str, rb: reqwest::RequestBuilder) -> Result<(Value, bool)> {
96 let (v, headers) = send_named(name, rb).await?;
97 let next = headers
98 .get_all(reqwest::header::LINK)
99 .iter()
100 .filter_map(|h| h.to_str().ok())
101 .flat_map(|h| h.split(','))
102 .any(|link| link.contains("rel=\"next\""));
103 Ok((v, next))
104}
105
106pub fn encode(s: &str) -> String {
108 let mut out = String::new();
109 for b in s.bytes() {
110 if b.is_ascii_alphanumeric() || b"-_.~".contains(&b) {
111 out.push(b as char);
112 } else {
113 out.push_str(&format!("%{b:02X}"));
114 }
115 }
116 out
117}
118
119pub fn host_of(url: &str) -> String {
122 let rest = url.split_once("://").map_or(url, |(_, r)| r);
123 rest.split('/').next().unwrap_or("").to_ascii_lowercase()
124}