use std::pin::Pin;
use async_trait::async_trait;
use futures::channel::oneshot;
use futures::Stream;
use arora_types::call::{Call, CallResult};
use arora_types::data::{Key, StateChange};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct DeviceInfo {
pub name: Option<String>,
pub description: Option<String>,
pub model_family: Option<String>,
pub hardware_version: Option<String>,
pub software_version: Option<String>,
pub owners: Vec<String>,
}
#[derive(Debug, Clone)]
pub enum BridgeOp {
Get(Vec<Key>),
Update(StateChange),
Call(Call),
ListKeys {
prefix: Option<String>,
},
ListMethods {
prefix: Option<String>,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BridgeError {
Disconnected(String),
Unregistered,
Other(String),
}
impl std::fmt::Display for BridgeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BridgeError::Disconnected(m) => write!(f, "bridge disconnected: {m}"),
BridgeError::Unregistered => write!(f, "device unregistered from the remote"),
BridgeError::Other(m) => write!(f, "{m}"),
}
}
}
impl std::error::Error for BridgeError {}
pub type BridgeResult<T> = Result<T, BridgeError>;
pub struct BridgeCommand {
pub op: BridgeOp,
reply: oneshot::Sender<Result<CallResult, String>>,
}
impl BridgeCommand {
pub fn new(op: BridgeOp, reply: oneshot::Sender<Result<CallResult, String>>) -> Self {
Self { op, reply }
}
pub fn reply(self, result: Result<CallResult, String>) {
let _ = self.reply.send(result);
}
}
pub type DeviceInfoStream = Pin<Box<dyn Stream<Item = BridgeResult<Option<DeviceInfo>>> + Send>>;
pub type DataRequestedStream = Pin<Box<dyn Stream<Item = bool> + Send>>;
pub type CommandStream = Pin<Box<dyn Stream<Item = BridgeCommand> + Send>>;
#[async_trait]
pub trait Bridge: Send + Sync {
async fn get_device_info(&self) -> BridgeResult<Option<DeviceInfo>>;
async fn device_info_updated(&self) -> BridgeResult<DeviceInfoStream>;
async fn update_device_info(
&self,
info: Option<DeviceInfo>,
) -> BridgeResult<Option<DeviceInfo>>;
async fn data_requested(&self) -> DataRequestedStream;
async fn send_data(&self, data: StateChange) -> BridgeResult<()>;
async fn commands(&self) -> CommandStream;
}
#[derive(Clone, Default)]
pub struct FakeBridge;
impl FakeBridge {
pub fn new() -> Self {
Self
}
}
#[async_trait]
impl Bridge for FakeBridge {
async fn get_device_info(&self) -> BridgeResult<Option<DeviceInfo>> {
Ok(None)
}
async fn device_info_updated(&self) -> BridgeResult<DeviceInfoStream> {
Ok(Box::pin(futures::stream::empty()))
}
async fn update_device_info(
&self,
info: Option<DeviceInfo>,
) -> BridgeResult<Option<DeviceInfo>> {
Ok(info)
}
async fn data_requested(&self) -> DataRequestedStream {
Box::pin(futures::stream::empty())
}
async fn send_data(&self, _data: StateChange) -> BridgeResult<()> {
Ok(())
}
async fn commands(&self) -> CommandStream {
Box::pin(futures::stream::empty())
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures::StreamExt;
#[tokio::test]
async fn fake_bridge_is_usable_as_trait_object() {
let bridge: Box<dyn Bridge> = Box::new(FakeBridge::new());
assert_eq!(bridge.get_device_info().await.unwrap(), None);
bridge.send_data(StateChange::new()).await.unwrap();
assert!(bridge
.device_info_updated()
.await
.unwrap()
.next()
.await
.is_none());
assert!(bridge.commands().await.next().await.is_none());
}
#[tokio::test]
async fn command_reply_round_trips() {
let (tx, rx) = oneshot::channel();
let cmd = BridgeCommand::new(BridgeOp::Get(vec![Key::from("a")]), tx);
match &cmd.op {
BridgeOp::Get(keys) => assert_eq!(keys[0], Key::from("a")),
_ => panic!("wrong op"),
}
cmd.reply(Err("not implemented".to_string()));
assert!(rx.await.unwrap().is_err());
}
}