1use std::sync::Arc;
15
16use reqwest::{Method, RequestBuilder};
17use serde::de::DeserializeOwned;
18use url::Url;
19
20use crate::api::HeaderPairs;
21use crate::swarm::{Error, RESPONSE_BODY_CAP};
22
23#[derive(Debug)]
25pub(crate) struct Inner {
26 pub(crate) base_url: Url,
27 pub(crate) http: reqwest::Client,
28}
29
30impl Inner {
31 pub(crate) fn url(&self, path: &str) -> Result<Url, Error> {
34 self.base_url
35 .join(path)
36 .map_err(|e| Error::argument(format!("invalid url: {e}")))
37 }
38
39 pub(crate) async fn send(&self, builder: RequestBuilder) -> Result<reqwest::Response, Error> {
42 let request = builder.build()?;
43 let method = request.method().to_string();
44 let url = request.url().to_string();
45 let resp = self.http.execute(request).await?;
46 if resp.status().is_success() {
47 return Ok(resp);
48 }
49 let status = resp.status().as_u16();
50 let status_text = format!(
51 "{status} {}",
52 resp.status().canonical_reason().unwrap_or("")
53 )
54 .trim_end()
55 .to_string();
56 let body = resp.bytes().await.map(|b| b.to_vec()).unwrap_or_default();
57 let n = body.len().min(RESPONSE_BODY_CAP);
58 Err(Error::Response {
59 method,
60 url,
61 status,
62 status_text,
63 body: body[..n].to_vec(),
64 })
65 }
66
67 pub(crate) async fn send_json<T: DeserializeOwned>(
69 &self,
70 builder: RequestBuilder,
71 ) -> Result<T, Error> {
72 let resp = self.send(builder).await?;
73 let bytes = resp.bytes().await?;
74 Ok(serde_json::from_slice(&bytes)?)
75 }
76
77 pub(crate) fn apply_headers(builder: RequestBuilder, headers: HeaderPairs) -> RequestBuilder {
79 let mut b = builder;
80 for (name, value) in headers {
81 b = b.header(name, value);
82 }
83 b
84 }
85}
86
87#[derive(Clone, Debug)]
89pub struct Client {
90 pub(crate) inner: Arc<Inner>,
91}
92
93impl Client {
94 pub fn new(url: &str) -> Result<Self, Error> {
98 let mut owned = url.to_owned();
99 if !owned.ends_with('/') {
100 owned.push('/');
101 }
102 let base_url =
103 Url::parse(&owned).map_err(|e| Error::argument(format!("invalid url: {e}")))?;
104 let http = reqwest::Client::builder()
105 .build()
106 .map_err(Error::Transport)?;
107 Ok(Self {
108 inner: Arc::new(Inner { base_url, http }),
109 })
110 }
111
112 pub fn with_http_client(url: &str, http: reqwest::Client) -> Result<Self, Error> {
116 let mut owned = url.to_owned();
117 if !owned.ends_with('/') {
118 owned.push('/');
119 }
120 let base_url =
121 Url::parse(&owned).map_err(|e| Error::argument(format!("invalid url: {e}")))?;
122 Ok(Self {
123 inner: Arc::new(Inner { base_url, http }),
124 })
125 }
126
127 pub fn base_url(&self) -> &Url {
129 &self.inner.base_url
130 }
131
132 pub fn file(&self) -> crate::file::FileApi {
135 crate::file::FileApi::new(self.inner.clone())
136 }
137
138 pub fn postage(&self) -> crate::postage::PostageApi {
141 crate::postage::PostageApi::new(self.inner.clone())
142 }
143
144 pub fn debug(&self) -> crate::debug::DebugApi {
147 crate::debug::DebugApi::new(self.inner.clone())
148 }
149
150 pub fn api(&self) -> crate::api::ApiService {
153 crate::api::ApiService::new(self.inner.clone())
154 }
155
156 pub fn pss(&self) -> crate::pss::PssApi {
158 crate::pss::PssApi::new(self.inner.clone())
159 }
160
161 pub fn gsoc(&self) -> crate::gsoc::GsocApi {
163 crate::gsoc::GsocApi::new(self.inner.clone())
164 }
165}
166
167pub(crate) fn request(inner: &Inner, method: Method, path: &str) -> Result<RequestBuilder, Error> {
170 let url = inner.url(path)?;
171 Ok(inner.http.request(method, url))
172}