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