1use std::time::Duration;
7
8use serde_json::Value;
9
10use crate::auth::{self, GatewayAuth};
11use crate::error::{Error, Result};
12use crate::nets::adc::AsyncAdc;
13use crate::nets::arm::AsyncArm;
14use crate::nets::battery::AsyncBattery;
15use crate::nets::ble::AsyncBle;
16use crate::nets::blufi::AsyncBlufi;
17use crate::nets::dac::AsyncDac;
18use crate::nets::debug::AsyncDebugNet;
19use crate::nets::eload::AsyncEload;
20use crate::nets::energy::AsyncEnergyAnalyzer;
21use crate::nets::gpio::AsyncGpio;
22use crate::nets::i2c::AsyncI2c;
23use crate::nets::router::AsyncRouter;
24use crate::nets::scope::Scope;
25use crate::nets::solar::AsyncSolar;
26use crate::nets::spi::AsyncSpi;
27use crate::nets::supply::AsyncSupply;
28use crate::nets::thermocouple::AsyncThermocouple;
29use crate::nets::usb::AsyncUsbPort;
30use crate::nets::watt::AsyncWattMeter;
31use crate::nets::webcam::AsyncWebcam;
32use crate::nets::wifi::AsyncWifi;
33use crate::wire::{self, BoxStatus, Health, HttpRequest, Method, NetRecord, Op, Timeout};
34
35pub struct AsyncLagerBox {
52 base: String,
53 debug_base: String,
54 http: reqwest::Client,
55 default_timeout: Duration,
56 auth: GatewayAuth,
57}
58
59pub struct AsyncLagerBoxBuilder {
62 host: String,
63 debug_url: Option<String>,
64 default_timeout: Duration,
65 bearer_token: Option<String>,
66}
67
68impl AsyncLagerBoxBuilder {
69 pub fn timeout(mut self, timeout: Duration) -> Self {
72 self.default_timeout = timeout;
73 self
74 }
75
76 pub fn debug_service_url(mut self, url: impl Into<String>) -> Self {
79 self.debug_url = Some(url.into());
80 self
81 }
82
83 pub fn bearer_token(mut self, token: impl Into<String>) -> Self {
93 self.bearer_token = Some(token.into());
94 self
95 }
96
97 pub fn build(self) -> Result<AsyncLagerBox> {
99 let base = wire::base_url(&self.host)?;
100 let debug_base = match self.debug_url {
101 Some(url) => wire::base_url_with_port(&url, wire::DEBUG_SERVICE_PORT)?,
102 None => wire::service_base(&base, wire::DEBUG_SERVICE_PORT),
103 };
104 let auth = GatewayAuth::new(&base, self.bearer_token);
105 Ok(AsyncLagerBox {
106 base,
107 debug_base,
108 http: reqwest::Client::new(),
109 default_timeout: self.default_timeout,
110 auth,
111 })
112 }
113}
114
115impl AsyncLagerBox {
116 pub fn connect(host: impl Into<String>) -> Result<Self> {
119 Self::builder(host).build()
120 }
121
122 pub fn from_env() -> Result<Self> {
125 let host = std::env::var(crate::BOX_HOST_ENV)
126 .map_err(|_| Error::Config(format!("{} is not set", crate::BOX_HOST_ENV)))?;
127 Self::connect(host)
128 }
129
130 pub fn builder(host: impl Into<String>) -> AsyncLagerBoxBuilder {
132 AsyncLagerBoxBuilder {
133 host: host.into(),
134 debug_url: std::env::var(crate::DEBUG_SERVICE_URL_ENV).ok(),
135 default_timeout: wire::DEFAULT_TIMEOUT,
136 bearer_token: None,
137 }
138 }
139
140 pub fn base_url(&self) -> &str {
142 &self.base
143 }
144
145 pub(crate) async fn execute(&self, req: &HttpRequest) -> Result<(u16, Value)> {
149 self.execute_at(&self.base, req).await
150 }
151
152 pub(crate) async fn execute_debug(&self, req: &HttpRequest) -> Result<(u16, Value)> {
154 self.execute_at(&self.debug_base, req).await
155 }
156
157 async fn execute_at(&self, base: &str, req: &HttpRequest) -> Result<(u16, Value)> {
162 let token = self.current_token().await;
163 let (status, gateway, resp_body) = self.send_once(base, req, token.as_deref()).await?;
164
165 let Some(auth_url) = gateway else {
166 return Ok((status, resp_body));
167 };
168 self.auth.learn_auth_server(&auth_url);
171 if status == 401 && !self.auth.has_static_token() {
172 if let Some(fresh) = self
173 .auth
174 .resolve_token_async(&auth_url, token.as_deref())
175 .await
176 {
177 let (status, gateway, resp_body) =
178 self.send_once(base, req, Some(&fresh)).await?;
179 let Some(auth_url) = gateway else {
180 return Ok((status, resp_body));
181 };
182 return Err(auth::denial_error(
183 status,
184 self.auth.box_host(),
185 &auth_url,
186 true,
187 ));
188 }
189 }
190 Err(auth::denial_error(
191 status,
192 self.auth.box_host(),
193 &auth_url,
194 token.is_some(),
195 ))
196 }
197
198 async fn current_token(&self) -> Option<String> {
201 if let Some(token) = self.auth.cached_token() {
202 return Some(token);
203 }
204 if self.auth.wants_store_token() {
205 let auth_url = self.auth.auth_url()?;
206 return self.auth.resolve_token_async(&auth_url, None).await;
207 }
208 None
209 }
210
211 async fn send_once(
215 &self,
216 base: &str,
217 req: &HttpRequest,
218 token: Option<&str>,
219 ) -> Result<(u16, Option<String>, Value)> {
220 let url = format!("{}{}", base, req.path);
221 let mut r = match req.method {
222 Method::Get => self.http.get(&url),
223 Method::Post => self.http.post(&url),
224 };
225 match req.timeout {
226 Timeout::Default => r = r.timeout(self.default_timeout),
227 Timeout::After(d) => r = r.timeout(d),
228 Timeout::Unbounded => {}
229 }
230 if let Some(token) = token {
231 r = r.header("Authorization", format!("Bearer {token}"));
232 }
233 if let Some(body) = &req.body {
234 r = r.json(body);
235 }
236 let resp = r.send().await.map_err(|e| {
237 if e.is_timeout() {
238 Error::Timeout(e.to_string())
239 } else {
240 Error::Connection(e.to_string())
241 }
242 })?;
243 let status = resp.status().as_u16();
244 let gateway = if auth::is_denial(status) {
245 resp.headers()
246 .get(auth::DISCOVERY_HEADER)
247 .and_then(|v| v.to_str().ok())
248 .map(str::to_string)
249 } else {
250 None
251 };
252 let body: Value = match resp.json().await {
253 Ok(body) => body,
254 Err(e) if e.is_timeout() => return Err(Error::Timeout(e.to_string())),
255 Err(e) if status < 400 => {
256 return Err(Error::Decode(format!("non-JSON response: {e}")))
257 }
258 Err(_) => Value::Null,
260 };
261 Ok((status, gateway, body))
262 }
263
264 pub(crate) async fn run<T>(&self, op: Op<T>) -> Result<T> {
266 let (status, body) = self.execute(&op.req).await?;
267 let resp = wire::parse_command(status, body)?;
268 (op.parse)(resp)
269 }
270
271 pub(crate) async fn debug_net_record(&self, name: &str) -> Result<Value> {
273 let records = self.nets_raw().await?;
274 crate::nets::debug::find_debug_record(records, name)
275 }
276
277 async fn nets_raw(&self) -> Result<Vec<Value>> {
280 let body = match self.get_json("/nets/list").await {
281 Ok(body) => body,
282 Err(primary) => self.get_json("/uart/nets/list").await.map_err(|_| primary)?,
283 };
284 Ok(wire::nets_list_values(body))
285 }
286
287 async fn get_json(&self, path: &str) -> Result<Value> {
288 let (status, body) = self.execute(&wire::get(path)).await?;
289 if status != 200 {
290 return Err(Error::Box {
291 status,
292 message: body
293 .get("error")
294 .and_then(Value::as_str)
295 .unwrap_or("request failed")
296 .to_string(),
297 });
298 }
299 Ok(body)
300 }
301
302 pub async fn nets(&self) -> Result<Vec<NetRecord>> {
309 match self.get_json("/nets/list").await {
310 Ok(body) => wire::nets_from_body(body),
311 Err(primary) => match self.get_json("/uart/nets/list").await {
312 Ok(body) => wire::nets_from_body(body),
313 Err(_) => Err(primary),
314 },
315 }
316 }
317
318 pub async fn health(&self) -> Result<Health> {
320 let body = self.get_json("/health").await?;
321 serde_json::from_value(body).map_err(Into::into)
322 }
323
324 pub async fn status(&self) -> Result<BoxStatus> {
326 let body = self.get_json("/status").await?;
327 serde_json::from_value(body).map_err(Into::into)
328 }
329
330 pub fn supply(&self, name: impl Into<String>) -> AsyncSupply<'_> {
334 AsyncSupply { client: self, name: name.into() }
335 }
336
337 pub fn battery(&self, name: impl Into<String>) -> AsyncBattery<'_> {
339 AsyncBattery { client: self, name: name.into() }
340 }
341
342 pub fn eload(&self, name: impl Into<String>) -> AsyncEload<'_> {
344 AsyncEload { client: self, name: name.into() }
345 }
346
347 pub fn solar(&self, name: impl Into<String>) -> AsyncSolar<'_> {
349 AsyncSolar { client: self, name: name.into() }
350 }
351
352 pub fn gpio(&self, name: impl Into<String>) -> AsyncGpio<'_> {
354 AsyncGpio { client: self, name: name.into() }
355 }
356
357 pub fn adc(&self, name: impl Into<String>) -> AsyncAdc<'_> {
359 AsyncAdc { client: self, name: name.into() }
360 }
361
362 pub fn dac(&self, name: impl Into<String>) -> AsyncDac<'_> {
364 AsyncDac { client: self, name: name.into() }
365 }
366
367 pub fn thermocouple(&self, name: impl Into<String>) -> AsyncThermocouple<'_> {
369 AsyncThermocouple { client: self, name: name.into() }
370 }
371
372 pub fn watt_meter(&self, name: impl Into<String>) -> AsyncWattMeter<'_> {
374 AsyncWattMeter { client: self, name: name.into() }
375 }
376
377 pub fn energy_analyzer(&self, name: impl Into<String>) -> AsyncEnergyAnalyzer<'_> {
379 AsyncEnergyAnalyzer { client: self, name: name.into() }
380 }
381
382 pub fn spi(&self, name: impl Into<String>) -> AsyncSpi<'_> {
384 AsyncSpi { client: self, name: name.into() }
385 }
386
387 pub fn i2c(&self, name: impl Into<String>) -> AsyncI2c<'_> {
389 AsyncI2c { client: self, name: name.into() }
390 }
391
392 pub fn usb(&self, name: impl Into<String>) -> AsyncUsbPort<'_> {
394 AsyncUsbPort { client: self, name: name.into() }
395 }
396
397 pub fn arm(&self, name: impl Into<String>) -> AsyncArm<'_> {
399 AsyncArm { client: self, name: name.into() }
400 }
401
402 pub fn webcam(&self, name: impl Into<String>) -> AsyncWebcam<'_> {
404 AsyncWebcam { client: self, name: name.into() }
405 }
406
407 pub fn router(&self, name: impl Into<String>) -> AsyncRouter<'_> {
409 AsyncRouter { client: self, name: name.into() }
410 }
411
412 pub fn ble(&self) -> AsyncBle<'_> {
414 AsyncBle { client: self }
415 }
416
417 pub fn wifi(&self) -> AsyncWifi<'_> {
419 AsyncWifi { client: self }
420 }
421
422 pub fn blufi(&self) -> AsyncBlufi<'_> {
424 AsyncBlufi { client: self }
425 }
426
427 pub fn debug(&self, name: impl Into<String>) -> AsyncDebugNet<'_> {
430 AsyncDebugNet { client: self, name: name.into() }
431 }
432
433 pub fn scope(&self, name: impl Into<String>) -> Scope {
435 Scope::new(name)
436 }
437}