extern crate alloc;
use alloc::string::String;
use alloc::vec::Vec;
use crate::error::{RpcError, RpcResult};
pub trait FunctionStub {
fn service_name(&self) -> &str;
}
pub trait FunctionSkeleton {
fn service_name(&self) -> &str;
fn operations(&self) -> &[(&'static str, u32)];
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OperationDescriptor {
pub name: String,
pub opcode: u32,
pub one_way: bool,
pub in_params: Vec<String>,
pub out_params: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ServiceDescriptor {
pub name: String,
pub operations: Vec<OperationDescriptor>,
}
impl ServiceDescriptor {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
operations: Vec::new(),
}
}
pub fn add_operation(
&mut self,
name: impl Into<String>,
one_way: bool,
in_params: Vec<String>,
out_params: Vec<String>,
) -> RpcResult<&OperationDescriptor> {
let opcode = u32::try_from(self.operations.len()).map_err(|_| {
RpcError::Codec("ServiceDescriptor: too many operations (>u32::MAX)".into())
})?;
self.operations.push(OperationDescriptor {
name: name.into(),
opcode,
one_way,
in_params,
out_params,
});
self.operations
.last()
.ok_or_else(|| RpcError::Codec("ServiceDescriptor: push failed".into()))
}
#[must_use]
pub fn operation(&self, name: &str) -> Option<&OperationDescriptor> {
self.operations.iter().find(|o| o.name == name)
}
#[must_use]
pub fn operation_by_opcode(&self, opcode: u32) -> Option<&OperationDescriptor> {
self.operations.iter().find(|o| o.opcode == opcode)
}
}
pub fn dispatch_request<F, T>(service: &ServiceDescriptor, opcode: u32, handler: F) -> RpcResult<T>
where
F: FnOnce(&OperationDescriptor) -> RpcResult<T>,
{
let op = service
.operation_by_opcode(opcode)
.ok_or_else(|| RpcError::Codec("function-call: unknown opcode".into()))?;
handler(op)
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
fn calculator_service() -> ServiceDescriptor {
let mut s = ServiceDescriptor::new("Calculator");
s.add_operation(
"add",
false,
alloc::vec!["a".into(), "b".into()],
alloc::vec!["result".into()],
)
.expect("add");
s.add_operation(
"subtract",
false,
alloc::vec!["a".into(), "b".into()],
alloc::vec!["result".into()],
)
.expect("subtract");
s.add_operation("ping", true, alloc::vec![], alloc::vec![])
.expect("ping");
s
}
#[test]
fn service_descriptor_assigns_monotonic_opcodes() {
let s = calculator_service();
assert_eq!(s.operations[0].opcode, 0);
assert_eq!(s.operations[1].opcode, 1);
assert_eq!(s.operations[2].opcode, 2);
}
#[test]
fn service_descriptor_lookup_by_name() {
let s = calculator_service();
assert_eq!(s.operation("add").map(|o| o.opcode), Some(0));
assert_eq!(s.operation("subtract").map(|o| o.opcode), Some(1));
assert!(s.operation("nonexistent").is_none());
}
#[test]
fn service_descriptor_lookup_by_opcode() {
let s = calculator_service();
assert_eq!(
s.operation_by_opcode(0).map(|o| o.name.as_str()),
Some("add")
);
assert!(s.operation_by_opcode(99).is_none());
}
#[test]
fn one_way_operation_marked_correctly() {
let s = calculator_service();
let ping = s.operation("ping").expect("ping");
assert!(ping.one_way);
let add = s.operation("add").expect("add");
assert!(!add.one_way);
}
#[test]
fn dispatch_request_routes_by_opcode() {
let s = calculator_service();
let result = dispatch_request(&s, 0, |op| Ok::<String, RpcError>(op.name.clone()))
.expect("dispatch");
assert_eq!(result, "add");
}
#[test]
fn dispatch_request_unknown_opcode_returns_codec_error() {
let s = calculator_service();
let err = dispatch_request(&s, 99, |_op| Ok::<(), RpcError>(())).expect_err("unknown");
assert!(matches!(err, RpcError::Codec(_)));
}
#[test]
fn out_params_first_member_is_return_value() {
let s = calculator_service();
let add = s.operation("add").expect("add");
assert_eq!(add.out_params.first().map(String::as_str), Some("result"));
}
struct CalculatorStub {
service_name: String,
}
impl FunctionStub for CalculatorStub {
fn service_name(&self) -> &str {
&self.service_name
}
}
struct CalculatorSkeleton;
impl FunctionSkeleton for CalculatorSkeleton {
fn service_name(&self) -> &str {
"Calculator"
}
fn operations(&self) -> &[(&'static str, u32)] {
&[("add", 0), ("subtract", 1), ("ping", 2)]
}
}
#[test]
fn stub_and_skeleton_traits_are_object_safe() {
let stub: alloc::boxed::Box<dyn FunctionStub> = alloc::boxed::Box::new(CalculatorStub {
service_name: "Calc".into(),
});
let skel: alloc::boxed::Box<dyn FunctionSkeleton> =
alloc::boxed::Box::new(CalculatorSkeleton);
assert_eq!(stub.service_name(), "Calc");
assert_eq!(skel.service_name(), "Calculator");
assert_eq!(skel.operations().len(), 3);
}
}