use crate::error::{Error, Result};
use crate::message::Message;
use parking_lot::RwLock;
use std::sync::Arc;
use tracing::debug;
pub type ServiceHandler<Req, Res> =
Arc<dyn Fn(Req) -> Result<Res> + Send + Sync + 'static>;
pub struct Queryable<Req: Message, Res: Message> {
name: String,
handler: ServiceHandler<Req, Res>,
stats: Arc<RwLock<ServiceStats>>,
}
#[derive(Debug, Default)]
struct ServiceStats {
pub requests_handled: u64,
pub errors: u64,
}
impl<Req: Message, Res: Message> Queryable<Req, Res> {
pub fn new<F>(name: impl Into<String>, handler: F) -> Self
where
F: Fn(Req) -> Result<Res> + Send + Sync + 'static,
{
let name = name.into();
debug!("Creating queryable service: {}", name);
Self {
name,
handler: Arc::new(handler),
stats: Arc::new(RwLock::new(ServiceStats::default())),
}
}
pub async fn handle(&self, request: Req) -> Result<Res> {
let result = (self.handler)(request);
let mut stats = self.stats.write();
stats.requests_handled += 1;
if result.is_err() {
stats.errors += 1;
}
result
}
pub fn name(&self) -> &str {
&self.name
}
pub fn stats(&self) -> (u64, u64) {
let stats = self.stats.read();
(stats.requests_handled, stats.errors)
}
}
pub struct Service<Req: Message, Res: Message> {
name: String,
_phantom: std::marker::PhantomData<(Req, Res)>,
}
impl<Req: Message, Res: Message> Service<Req, Res> {
pub fn new(name: impl Into<String>) -> Self {
let name = name.into();
debug!("Creating service client: {}", name);
Self {
name,
_phantom: std::marker::PhantomData,
}
}
pub async fn call(&self, _request: Req) -> Result<Res> {
Err(Error::Other(anyhow::anyhow!("Service call not implemented")))
}
pub fn name(&self) -> &str {
&self.name
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::message::RobotState;
#[tokio::test]
async fn test_queryable() {
let queryable = Queryable::new("compute", |req: RobotState| {
Ok(RobotState {
position: req.position,
velocity: [1.0, 2.0, 3.0],
timestamp: req.timestamp + 1,
})
});
let request = RobotState::default();
let response = queryable.handle(request).await.unwrap();
assert_eq!(response.velocity, [1.0, 2.0, 3.0]);
let (handled, errors) = queryable.stats();
assert_eq!(handled, 1);
assert_eq!(errors, 0);
}
#[test]
fn test_service_client() {
let service = Service::<RobotState, RobotState>::new("compute");
assert_eq!(service.name(), "compute");
}
}