use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use serde_json::{json, Value};
use crate::contract::{new_context, status_for_error, Caller, Emit, Handler, MethodTable};
use crate::error::ServiceError;
#[derive(Clone)]
pub struct DeviceCalls {
table: Arc<MethodTable>,
handler: Arc<dyn Handler>,
}
static NEXT_CALL: AtomicU64 = AtomicU64::new(1 << 62);
impl DeviceCalls {
pub fn new(table: MethodTable, handler: Arc<dyn Handler>) -> Self {
Self {
table: Arc::new(table),
handler,
}
}
pub fn table(&self) -> &MethodTable {
&self.table
}
pub(crate) fn run(
&self,
device_id: &str,
scopes: Vec<String>,
method: &str,
body: &[u8],
) -> (u16, Value) {
let caller = Caller::device(device_id, scopes);
if let Err(e) = self.table.authorize_device_call(&caller, method) {
return error_body(&e);
}
if method == "ping" {
return (200, json!({ "pong": true }));
}
let params = if body.is_empty() {
json!({})
} else {
match serde_json::from_slice::<Value>(body) {
Ok(v) => v,
Err(_) => {
return error_body(&ServiceError::new("bad_request", "body is not JSON"));
}
}
};
let cx = new_context(
NEXT_CALL.fetch_add(1, Ordering::Relaxed),
caller,
Arc::new(AtomicBool::new(false)),
Arc::new(AtomicBool::new(false)),
);
let emit: Emit = Arc::new(|_| {});
let result = catch_unwind(AssertUnwindSafe(|| {
self.handler.call(&cx, method, ¶ms, &emit)
}))
.unwrap_or_else(|_| Err(ServiceError::new("panic", "handler panicked")));
match result {
Ok(v) => (200, v),
Err(e) => error_body(&e),
}
}
}
fn error_body(e: &ServiceError) -> (u16, Value) {
(
status_for_error(e),
json!({ "error": { "code": e.code, "message": e.message, "retryable": e.retryable } }),
)
}