zerodds_rpc/
function_call.rs1extern crate alloc;
33
34use alloc::string::String;
35use alloc::vec::Vec;
36
37use crate::error::{RpcError, RpcResult};
38
39pub trait FunctionStub {
44 fn service_name(&self) -> &str;
46}
47
48pub trait FunctionSkeleton {
53 fn service_name(&self) -> &str;
55
56 fn operations(&self) -> &[(&'static str, u32)];
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct OperationDescriptor {
68 pub name: String,
70 pub opcode: u32,
72 pub one_way: bool,
74 pub in_params: Vec<String>,
76 pub out_params: Vec<String>,
79}
80
81#[derive(Debug, Clone, PartialEq, Eq, Default)]
83pub struct ServiceDescriptor {
84 pub name: String,
86 pub operations: Vec<OperationDescriptor>,
88}
89
90impl ServiceDescriptor {
91 #[must_use]
93 pub fn new(name: impl Into<String>) -> Self {
94 Self {
95 name: name.into(),
96 operations: Vec::new(),
97 }
98 }
99
100 pub fn add_operation(
106 &mut self,
107 name: impl Into<String>,
108 one_way: bool,
109 in_params: Vec<String>,
110 out_params: Vec<String>,
111 ) -> RpcResult<&OperationDescriptor> {
112 let opcode = u32::try_from(self.operations.len()).map_err(|_| {
113 RpcError::Codec("ServiceDescriptor: too many operations (>u32::MAX)".into())
114 })?;
115 self.operations.push(OperationDescriptor {
116 name: name.into(),
117 opcode,
118 one_way,
119 in_params,
120 out_params,
121 });
122 self.operations
123 .last()
124 .ok_or_else(|| RpcError::Codec("ServiceDescriptor: push failed".into()))
125 }
126
127 #[must_use]
129 pub fn operation(&self, name: &str) -> Option<&OperationDescriptor> {
130 self.operations.iter().find(|o| o.name == name)
131 }
132
133 #[must_use]
135 pub fn operation_by_opcode(&self, opcode: u32) -> Option<&OperationDescriptor> {
136 self.operations.iter().find(|o| o.opcode == opcode)
137 }
138}
139
140pub fn dispatch_request<F, T>(service: &ServiceDescriptor, opcode: u32, handler: F) -> RpcResult<T>
148where
149 F: FnOnce(&OperationDescriptor) -> RpcResult<T>,
150{
151 let op = service
152 .operation_by_opcode(opcode)
153 .ok_or_else(|| RpcError::Codec("function-call: unknown opcode".into()))?;
154 handler(op)
155}
156
157#[cfg(test)]
158#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
159mod tests {
160 use super::*;
161
162 fn calculator_service() -> ServiceDescriptor {
163 let mut s = ServiceDescriptor::new("Calculator");
164 s.add_operation(
165 "add",
166 false,
167 alloc::vec!["a".into(), "b".into()],
168 alloc::vec!["result".into()],
169 )
170 .expect("add");
171 s.add_operation(
172 "subtract",
173 false,
174 alloc::vec!["a".into(), "b".into()],
175 alloc::vec!["result".into()],
176 )
177 .expect("subtract");
178 s.add_operation("ping", true, alloc::vec![], alloc::vec![])
179 .expect("ping");
180 s
181 }
182
183 #[test]
184 fn service_descriptor_assigns_monotonic_opcodes() {
185 let s = calculator_service();
186 assert_eq!(s.operations[0].opcode, 0);
187 assert_eq!(s.operations[1].opcode, 1);
188 assert_eq!(s.operations[2].opcode, 2);
189 }
190
191 #[test]
192 fn service_descriptor_lookup_by_name() {
193 let s = calculator_service();
194 assert_eq!(s.operation("add").map(|o| o.opcode), Some(0));
195 assert_eq!(s.operation("subtract").map(|o| o.opcode), Some(1));
196 assert!(s.operation("nonexistent").is_none());
197 }
198
199 #[test]
200 fn service_descriptor_lookup_by_opcode() {
201 let s = calculator_service();
202 assert_eq!(
203 s.operation_by_opcode(0).map(|o| o.name.as_str()),
204 Some("add")
205 );
206 assert!(s.operation_by_opcode(99).is_none());
207 }
208
209 #[test]
210 fn one_way_operation_marked_correctly() {
211 let s = calculator_service();
212 let ping = s.operation("ping").expect("ping");
213 assert!(ping.one_way);
214 let add = s.operation("add").expect("add");
215 assert!(!add.one_way);
216 }
217
218 #[test]
219 fn dispatch_request_routes_by_opcode() {
220 let s = calculator_service();
221 let result = dispatch_request(&s, 0, |op| Ok::<String, RpcError>(op.name.clone()))
222 .expect("dispatch");
223 assert_eq!(result, "add");
224 }
225
226 #[test]
227 fn dispatch_request_unknown_opcode_returns_codec_error() {
228 let s = calculator_service();
229 let err = dispatch_request(&s, 99, |_op| Ok::<(), RpcError>(())).expect_err("unknown");
230 assert!(matches!(err, RpcError::Codec(_)));
231 }
232
233 #[test]
234 fn out_params_first_member_is_return_value() {
235 let s = calculator_service();
238 let add = s.operation("add").expect("add");
239 assert_eq!(add.out_params.first().map(String::as_str), Some("result"));
240 }
241
242 struct CalculatorStub {
244 service_name: String,
245 }
246 impl FunctionStub for CalculatorStub {
247 fn service_name(&self) -> &str {
248 &self.service_name
249 }
250 }
251
252 struct CalculatorSkeleton;
254 impl FunctionSkeleton for CalculatorSkeleton {
255 fn service_name(&self) -> &str {
256 "Calculator"
257 }
258 fn operations(&self) -> &[(&'static str, u32)] {
259 &[("add", 0), ("subtract", 1), ("ping", 2)]
260 }
261 }
262
263 #[test]
264 fn stub_and_skeleton_traits_are_object_safe() {
265 let stub: alloc::boxed::Box<dyn FunctionStub> = alloc::boxed::Box::new(CalculatorStub {
266 service_name: "Calc".into(),
267 });
268 let skel: alloc::boxed::Box<dyn FunctionSkeleton> =
269 alloc::boxed::Box::new(CalculatorSkeleton);
270 assert_eq!(stub.service_name(), "Calc");
271 assert_eq!(skel.service_name(), "Calculator");
272 assert_eq!(skel.operations().len(), 3);
273 }
274}