1use std::sync::Arc;
15use std::time::{Duration, Instant};
16
17use reqwest::{Method, RequestBuilder};
18use serde::de::DeserializeOwned;
19use url::Url;
20
21use crate::api::HeaderPairs;
22use crate::swarm::{Error, RESPONSE_BODY_CAP};
23
24#[derive(Debug)]
26pub(crate) struct Inner {
27 pub(crate) base_url: Url,
28 pub(crate) http: reqwest::Client,
29}
30
31impl Inner {
32 pub(crate) fn url(&self, path: &str) -> Result<Url, Error> {
35 self.base_url
36 .join(path)
37 .map_err(|e| Error::argument(format!("invalid url: {e}")))
38 }
39
40 pub(crate) async fn send(&self, builder: RequestBuilder) -> Result<reqwest::Response, Error> {
49 let request = builder.build()?;
50 let method = request.method().to_string();
51 let url = request.url().to_string();
52 let start = Instant::now();
53
54 let resp = self.http.execute(request).await?;
55 let elapsed_ms = start.elapsed().as_millis() as u64;
56 let status = resp.status().as_u16();
57
58 if resp.status().is_success() {
59 tracing::debug!(
60 target: "bee::http",
61 method = %method,
62 url = %url,
63 status,
64 elapsed_ms,
65 "bee api request"
66 );
67 return Ok(resp);
68 }
69 let status_text = format!(
70 "{status} {}",
71 resp.status().canonical_reason().unwrap_or("")
72 )
73 .trim_end()
74 .to_string();
75 let body = resp.bytes().await.map(|b| b.to_vec()).unwrap_or_default();
76 let n = body.len().min(RESPONSE_BODY_CAP);
77 tracing::debug!(
78 target: "bee::http",
79 method = %method,
80 url = %url,
81 status,
82 elapsed_ms,
83 body_len = body.len(),
84 "bee api error response"
85 );
86 Err(Error::Response {
87 method,
88 url,
89 status,
90 status_text,
91 body: body[..n].to_vec(),
92 })
93 }
94
95 pub(crate) async fn send_json<T: DeserializeOwned>(
97 &self,
98 builder: RequestBuilder,
99 ) -> Result<T, Error> {
100 let resp = self.send(builder).await?;
101 let bytes = resp.bytes().await?;
102 Ok(serde_json::from_slice(&bytes)?)
103 }
104
105 pub(crate) fn apply_headers(builder: RequestBuilder, headers: HeaderPairs) -> RequestBuilder {
107 let mut b = builder;
108 for (name, value) in headers {
109 b = b.header(name, value);
110 }
111 b
112 }
113}
114
115#[derive(Clone, Debug)]
117pub struct Client {
118 pub(crate) inner: Arc<Inner>,
119}
120
121impl Client {
122 pub fn new(url: &str) -> Result<Self, Error> {
126 let mut owned = url.to_owned();
127 if !owned.ends_with('/') {
128 owned.push('/');
129 }
130 let base_url =
131 Url::parse(&owned).map_err(|e| Error::argument(format!("invalid url: {e}")))?;
132 let http = reqwest::Client::builder()
133 .build()
134 .map_err(Error::Transport)?;
135 Ok(Self {
136 inner: Arc::new(Inner { base_url, http }),
137 })
138 }
139
140 pub fn with_http_client(url: &str, http: reqwest::Client) -> Result<Self, Error> {
144 let mut owned = url.to_owned();
145 if !owned.ends_with('/') {
146 owned.push('/');
147 }
148 let base_url =
149 Url::parse(&owned).map_err(|e| Error::argument(format!("invalid url: {e}")))?;
150 Ok(Self {
151 inner: Arc::new(Inner { base_url, http }),
152 })
153 }
154
155 pub fn with_token(url: &str, token: &str) -> Result<Self, Error> {
163 use reqwest::header::{AUTHORIZATION, HeaderMap, HeaderValue};
164 let value = HeaderValue::from_str(&format!("Bearer {token}"))
165 .map_err(|e| Error::argument(format!("invalid token: {e}")))?;
166 let mut headers = HeaderMap::new();
167 headers.insert(AUTHORIZATION, value);
168 let http = reqwest::Client::builder()
169 .default_headers(headers)
170 .build()
171 .map_err(Error::Transport)?;
172 Self::with_http_client(url, http)
173 }
174
175 pub fn base_url(&self) -> &Url {
177 &self.inner.base_url
178 }
179
180 pub async fn ping(&self) -> Result<Duration, Error> {
184 let url = self.inner.url("health")?;
185 let builder = self.inner.http.request(Method::GET, url);
186 let start = Instant::now();
187 let _ = self.inner.send(builder).await?;
188 Ok(start.elapsed())
189 }
190
191 pub fn file(&self) -> crate::file::FileApi {
194 crate::file::FileApi::new(self.inner.clone())
195 }
196
197 pub fn postage(&self) -> crate::postage::PostageApi {
200 crate::postage::PostageApi::new(self.inner.clone())
201 }
202
203 pub fn debug(&self) -> crate::debug::DebugApi {
206 crate::debug::DebugApi::new(self.inner.clone())
207 }
208
209 pub fn api(&self) -> crate::api::ApiService {
212 crate::api::ApiService::new(self.inner.clone())
213 }
214
215 pub fn pss(&self) -> crate::pss::PssApi {
217 crate::pss::PssApi::new(self.inner.clone())
218 }
219
220 pub fn gsoc(&self) -> crate::gsoc::GsocApi {
222 crate::gsoc::GsocApi::new(self.inner.clone())
223 }
224}
225
226pub(crate) fn request(inner: &Inner, method: Method, path: &str) -> Result<RequestBuilder, Error> {
229 let url = inner.url(path)?;
230 Ok(inner.http.request(method, url))
231}