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::dfu::AsyncDfu;
20use crate::nets::eload::AsyncEload;
21use crate::nets::energy::AsyncEnergyAnalyzer;
22use crate::nets::gpio::AsyncGpio;
23use crate::nets::i2c::AsyncI2c;
24use crate::nets::router::AsyncRouter;
25use crate::nets::scope::Scope;
26use crate::nets::solar::AsyncSolar;
27use crate::nets::spi::AsyncSpi;
28use crate::nets::supply::AsyncSupply;
29use crate::nets::thermocouple::AsyncThermocouple;
30use crate::nets::usb::AsyncUsbPort;
31use crate::nets::watt::AsyncWattMeter;
32use crate::nets::webcam::AsyncWebcam;
33use crate::nets::wifi::AsyncWifi;
34use crate::wire::{
35 self, BoxLock, BoxStatus, Health, HttpRequest, Method, NetRecord, Op, Timeout,
36 UsbDeviceFilter, UsbDeviceInfo,
37};
38
39pub struct AsyncLagerBox {
56 base: String,
57 debug_base: String,
58 http: reqwest::Client,
59 default_timeout: Duration,
60 auth: GatewayAuth,
61}
62
63pub struct AsyncLagerBoxBuilder {
66 host: String,
67 debug_url: Option<String>,
68 default_timeout: Duration,
69 bearer_token: Option<String>,
70}
71
72impl AsyncLagerBoxBuilder {
73 pub fn timeout(mut self, timeout: Duration) -> Self {
76 self.default_timeout = timeout;
77 self
78 }
79
80 pub fn debug_service_url(mut self, url: impl Into<String>) -> Self {
83 self.debug_url = Some(url.into());
84 self
85 }
86
87 pub fn bearer_token(mut self, token: impl Into<String>) -> Self {
97 self.bearer_token = Some(token.into());
98 self
99 }
100
101 pub fn build(self) -> Result<AsyncLagerBox> {
103 let base = wire::base_url(&self.host)?;
104 let debug_base = match self.debug_url {
105 Some(url) => wire::base_url_with_port(&url, wire::DEBUG_SERVICE_PORT)?,
106 None => wire::service_base(&base, wire::DEBUG_SERVICE_PORT),
107 };
108 let auth = GatewayAuth::new(&base, self.bearer_token);
109 Ok(AsyncLagerBox {
110 base,
111 debug_base,
112 http: reqwest::Client::new(),
113 default_timeout: self.default_timeout,
114 auth,
115 })
116 }
117}
118
119impl AsyncLagerBox {
120 pub fn connect(host: impl Into<String>) -> Result<Self> {
123 Self::builder(host).build()
124 }
125
126 pub fn from_env() -> Result<Self> {
129 let host = std::env::var(crate::BOX_HOST_ENV)
130 .map_err(|_| Error::Config(format!("{} is not set", crate::BOX_HOST_ENV)))?;
131 Self::connect(host)
132 }
133
134 pub fn builder(host: impl Into<String>) -> AsyncLagerBoxBuilder {
136 AsyncLagerBoxBuilder {
137 host: host.into(),
138 debug_url: std::env::var(crate::DEBUG_SERVICE_URL_ENV).ok(),
139 default_timeout: wire::DEFAULT_TIMEOUT,
140 bearer_token: None,
141 }
142 }
143
144 pub fn base_url(&self) -> &str {
146 &self.base
147 }
148
149 pub(crate) async fn execute(&self, req: &HttpRequest) -> Result<(u16, Value)> {
153 self.execute_at(&self.base, req).await
154 }
155
156 pub(crate) async fn execute_debug(&self, req: &HttpRequest) -> Result<(u16, Value)> {
158 self.execute_at(&self.debug_base, req).await
159 }
160
161 async fn execute_at(&self, base: &str, req: &HttpRequest) -> Result<(u16, Value)> {
166 let token = self.current_token().await;
167 let (status, gateway, resp_body) = self.send_once(base, req, token.as_deref()).await?;
168
169 let Some(auth_url) = gateway else {
170 return Ok((status, resp_body));
171 };
172 self.auth.learn_auth_server(&auth_url);
175 if status == 401 && !self.auth.has_static_token() {
176 if let Some(fresh) = self
177 .auth
178 .resolve_token_async(&auth_url, token.as_deref())
179 .await
180 {
181 let (status, gateway, resp_body) =
182 self.send_once(base, req, Some(&fresh)).await?;
183 let Some(auth_url) = gateway else {
184 return Ok((status, resp_body));
185 };
186 return Err(auth::denial_error(
187 status,
188 self.auth.box_host(),
189 &auth_url,
190 true,
191 ));
192 }
193 }
194 Err(auth::denial_error(
195 status,
196 self.auth.box_host(),
197 &auth_url,
198 token.is_some(),
199 ))
200 }
201
202 async fn current_token(&self) -> Option<String> {
205 if let Some(token) = self.auth.cached_token() {
206 return Some(token);
207 }
208 if self.auth.wants_store_token() {
209 let auth_url = self.auth.auth_url()?;
210 return self.auth.resolve_token_async(&auth_url, None).await;
211 }
212 None
213 }
214
215 async fn send_once(
219 &self,
220 base: &str,
221 req: &HttpRequest,
222 token: Option<&str>,
223 ) -> Result<(u16, Option<String>, Value)> {
224 let url = format!("{}{}", base, req.path);
225 let mut r = match req.method {
226 Method::Get => self.http.get(&url),
227 Method::Post => self.http.post(&url),
228 };
229 match req.timeout {
230 Timeout::Default => r = r.timeout(self.default_timeout),
231 Timeout::After(d) => r = r.timeout(d),
232 Timeout::Unbounded => {}
233 }
234 if let Some(token) = token {
235 r = r.header("Authorization", format!("Bearer {token}"));
236 }
237 if let Some(body) = &req.body {
238 r = r.json(body);
239 }
240 let resp = r.send().await.map_err(|e| {
241 if e.is_timeout() {
242 Error::Timeout(e.to_string())
243 } else {
244 Error::Connection(e.to_string())
245 }
246 })?;
247 let status = resp.status().as_u16();
248 let gateway = if auth::is_denial(status) {
249 resp.headers()
250 .get(auth::DISCOVERY_HEADER)
251 .and_then(|v| v.to_str().ok())
252 .map(str::to_string)
253 } else {
254 None
255 };
256 let body: Value = match resp.json().await {
257 Ok(body) => body,
258 Err(e) if e.is_timeout() => return Err(Error::Timeout(e.to_string())),
259 Err(e) if status < 400 => {
260 return Err(Error::Decode(format!("non-JSON response: {e}")))
261 }
262 Err(_) => Value::Null,
264 };
265 Ok((status, gateway, body))
266 }
267
268 pub(crate) async fn run<T>(&self, op: Op<T>) -> Result<T> {
270 let (status, body) = self.execute(&op.req).await?;
271 let resp = wire::parse_command(status, body)?;
272 (op.parse)(resp)
273 }
274
275 pub(crate) async fn debug_net_record(&self, name: &str) -> Result<Value> {
277 let records = self.nets_raw().await?;
278 crate::nets::debug::find_debug_record(records, name)
279 }
280
281 async fn nets_raw(&self) -> Result<Vec<Value>> {
284 let body = match self.get_json("/nets/list").await {
285 Ok(body) => body,
286 Err(primary) => self.get_json("/uart/nets/list").await.map_err(|_| primary)?,
287 };
288 Ok(wire::nets_list_values(body))
289 }
290
291 async fn get_json(&self, path: &str) -> Result<Value> {
292 let (status, body) = self.execute(&wire::get(path)).await?;
293 if status != 200 {
294 return Err(Error::Box {
295 status,
296 message: body
297 .get("error")
298 .and_then(Value::as_str)
299 .unwrap_or("request failed")
300 .to_string(),
301 });
302 }
303 Ok(body)
304 }
305
306 pub async fn nets(&self) -> Result<Vec<NetRecord>> {
313 match self.get_json("/nets/list").await {
314 Ok(body) => wire::nets_from_body(body),
315 Err(primary) => match self.get_json("/uart/nets/list").await {
316 Ok(body) => wire::nets_from_body(body),
317 Err(_) => Err(primary),
318 },
319 }
320 }
321
322 pub async fn health(&self) -> Result<Health> {
324 let body = self.get_json("/health").await?;
325 serde_json::from_value(body).map_err(Into::into)
326 }
327
328 pub async fn status(&self) -> Result<BoxStatus> {
330 let body = self.get_json("/status").await?;
331 serde_json::from_value(body).map_err(Into::into)
332 }
333
334 pub async fn usb_devices(&self) -> Result<Vec<UsbDeviceInfo>> {
343 self.usb_devices_matching(&UsbDeviceFilter::default()).await
344 }
345
346 pub async fn usb_devices_matching(
349 &self,
350 filter: &UsbDeviceFilter,
351 ) -> Result<Vec<UsbDeviceInfo>> {
352 match self.execute(&wire::usb_devices(filter)).await {
353 Ok((status, body)) => wire::parse_usb_devices(status, body),
354 Err(e) => Err(wire::map_route_missing(e, wire::usb_devices_unsupported)),
355 }
356 }
357
358 async fn lock_call(&self, req: &HttpRequest) -> Result<BoxLock> {
361 match self.execute(req).await {
362 Ok((status, body)) => wire::parse_lock(status, body),
363 Err(e) => Err(wire::map_route_missing(e, wire::lock_unsupported)),
364 }
365 }
366
367 pub async fn lock_status(&self) -> Result<BoxLock> {
369 self.lock_call(&wire::lock_status()).await
370 }
371
372 pub async fn lock(&self, user: &str) -> Result<BoxLock> {
377 self.lock_call(&wire::lock_acquire(user, "user", None)).await
378 }
379
380 pub async fn lock_with(
384 &self,
385 user: &str,
386 holder_type: &str,
387 ttl_seconds: Option<u64>,
388 ) -> Result<BoxLock> {
389 self.lock_call(&wire::lock_acquire(user, holder_type, ttl_seconds))
390 .await
391 }
392
393 pub async fn lock_heartbeat(&self, user: &str) -> Result<BoxLock> {
396 self.lock_call(&wire::lock_heartbeat(user)).await
397 }
398
399 pub async fn unlock(&self, user: &str) -> Result<()> {
403 self.lock_call(&wire::unlock(user, false)).await.map(|_| ())
404 }
405
406 pub async fn unlock_force(&self, user: &str) -> Result<()> {
409 self.lock_call(&wire::unlock(user, true)).await.map(|_| ())
410 }
411
412 pub fn supply(&self, name: impl Into<String>) -> AsyncSupply<'_> {
416 AsyncSupply { client: self, name: name.into() }
417 }
418
419 pub fn battery(&self, name: impl Into<String>) -> AsyncBattery<'_> {
421 AsyncBattery { client: self, name: name.into() }
422 }
423
424 pub fn eload(&self, name: impl Into<String>) -> AsyncEload<'_> {
426 AsyncEload { client: self, name: name.into() }
427 }
428
429 pub fn solar(&self, name: impl Into<String>) -> AsyncSolar<'_> {
431 AsyncSolar { client: self, name: name.into() }
432 }
433
434 pub fn gpio(&self, name: impl Into<String>) -> AsyncGpio<'_> {
436 AsyncGpio { client: self, name: name.into() }
437 }
438
439 pub fn adc(&self, name: impl Into<String>) -> AsyncAdc<'_> {
441 AsyncAdc { client: self, name: name.into() }
442 }
443
444 pub fn dac(&self, name: impl Into<String>) -> AsyncDac<'_> {
446 AsyncDac { client: self, name: name.into() }
447 }
448
449 pub fn thermocouple(&self, name: impl Into<String>) -> AsyncThermocouple<'_> {
451 AsyncThermocouple { client: self, name: name.into() }
452 }
453
454 pub fn watt_meter(&self, name: impl Into<String>) -> AsyncWattMeter<'_> {
456 AsyncWattMeter { client: self, name: name.into() }
457 }
458
459 pub fn energy_analyzer(&self, name: impl Into<String>) -> AsyncEnergyAnalyzer<'_> {
461 AsyncEnergyAnalyzer { client: self, name: name.into() }
462 }
463
464 pub fn spi(&self, name: impl Into<String>) -> AsyncSpi<'_> {
466 AsyncSpi { client: self, name: name.into() }
467 }
468
469 pub fn i2c(&self, name: impl Into<String>) -> AsyncI2c<'_> {
471 AsyncI2c { client: self, name: name.into() }
472 }
473
474 pub fn usb(&self, name: impl Into<String>) -> AsyncUsbPort<'_> {
476 AsyncUsbPort { client: self, name: name.into() }
477 }
478
479 pub fn arm(&self, name: impl Into<String>) -> AsyncArm<'_> {
481 AsyncArm { client: self, name: name.into() }
482 }
483
484 pub fn webcam(&self, name: impl Into<String>) -> AsyncWebcam<'_> {
486 AsyncWebcam { client: self, name: name.into() }
487 }
488
489 pub fn router(&self, name: impl Into<String>) -> AsyncRouter<'_> {
491 AsyncRouter { client: self, name: name.into() }
492 }
493
494 pub fn ble(&self) -> AsyncBle<'_> {
496 AsyncBle { client: self }
497 }
498
499 pub fn wifi(&self) -> AsyncWifi<'_> {
501 AsyncWifi { client: self }
502 }
503
504 pub fn blufi(&self) -> AsyncBlufi<'_> {
506 AsyncBlufi { client: self }
507 }
508
509 pub fn dfu(&self) -> AsyncDfu<'_> {
511 AsyncDfu { client: self }
512 }
513
514 pub fn debug(&self, name: impl Into<String>) -> AsyncDebugNet<'_> {
517 AsyncDebugNet { client: self, name: name.into(), record: Default::default() }
518 }
519
520 pub fn scope(&self, name: impl Into<String>) -> Scope {
522 Scope::new(name)
523 }
524}