1#![forbid(unsafe_op_in_unsafe_fn)]
26
27use facet_core::Shape;
28use std::collections::HashMap;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34pub struct ServiceId(pub u32);
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub struct MethodId(pub u32);
42
43impl MethodId {
44 pub const CONTROL: MethodId = MethodId(0);
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
50#[repr(u8)]
51pub enum Encoding {
52 Postcard = 0,
54 Json = 1,
56}
57
58impl Encoding {
59 pub const ALL: &'static [Encoding] = &[Encoding::Postcard, Encoding::Json];
61}
62
63#[derive(Debug, Clone)]
65pub struct ArgInfo {
66 pub name: &'static str,
68 pub type_name: &'static str,
70}
71
72#[derive(Debug)]
74pub struct MethodEntry {
75 pub id: MethodId,
77 pub name: &'static str,
79 pub full_name: String,
81 pub doc: String,
83 pub args: Vec<ArgInfo>,
85 pub request_shape: &'static Shape,
87 pub response_shape: &'static Shape,
89 pub is_streaming: bool,
91 pub supported_encodings: Vec<Encoding>,
93}
94
95impl MethodEntry {
96 pub fn supports_encoding(&self, encoding: Encoding) -> bool {
98 self.supported_encodings.contains(&encoding)
99 }
100}
101
102#[derive(Debug)]
104pub struct ServiceEntry {
105 pub id: ServiceId,
107 pub name: &'static str,
109 pub doc: String,
111 pub methods: HashMap<&'static str, MethodEntry>,
113}
114
115impl ServiceEntry {
116 pub fn method(&self, name: &str) -> Option<&MethodEntry> {
118 self.methods.get(name)
119 }
120
121 pub fn iter_methods(&self) -> impl Iterator<Item = &MethodEntry> {
123 self.methods.values()
124 }
125}
126
127#[derive(Debug, Default)]
132pub struct ServiceRegistry {
133 services_by_name: HashMap<&'static str, ServiceEntry>,
135 methods_by_id: HashMap<MethodId, MethodLookup>,
137 next_service_id: u32,
139 next_method_id: u32,
141}
142
143#[derive(Debug, Clone)]
145struct MethodLookup {
146 service_name: &'static str,
147 method_name: &'static str,
148}
149
150impl ServiceRegistry {
151 pub fn new() -> Self {
153 Self {
154 services_by_name: HashMap::new(),
155 methods_by_id: HashMap::new(),
156 next_service_id: 0,
157 next_method_id: 1, }
159 }
160
161 pub fn register_service(
165 &mut self,
166 name: &'static str,
167 doc: impl Into<String>,
168 ) -> ServiceBuilder<'_> {
169 let id = ServiceId(self.next_service_id);
170 self.next_service_id += 1;
171
172 ServiceBuilder {
173 registry: self,
174 service_name: name,
175 service_doc: doc.into(),
176 service_id: id,
177 methods: HashMap::new(),
178 }
179 }
180
181 pub fn service(&self, name: &str) -> Option<&ServiceEntry> {
183 self.services_by_name.get(name)
184 }
185
186 pub fn lookup_method(&self, service_name: &str, method_name: &str) -> Option<&MethodEntry> {
188 self.services_by_name
189 .get(service_name)
190 .and_then(|s| s.method(method_name))
191 }
192
193 pub fn method_by_id(&self, id: MethodId) -> Option<&MethodEntry> {
195 let lookup = self.methods_by_id.get(&id)?;
196 self.lookup_method(lookup.service_name, lookup.method_name)
197 }
198
199 pub fn resolve_method_id(&self, service_name: &str, method_name: &str) -> Option<MethodId> {
201 self.lookup_method(service_name, method_name).map(|m| m.id)
202 }
203
204 pub fn iter_services(&self) -> impl Iterator<Item = &ServiceEntry> {
206 self.services_by_name.values()
207 }
208
209 pub fn services(&self) -> impl Iterator<Item = &ServiceEntry> {
211 self.iter_services()
212 }
213
214 pub fn service_by_id(&self, id: ServiceId) -> Option<&ServiceEntry> {
216 self.services_by_name.values().find(|s| s.id == id)
217 }
218
219 pub fn service_count(&self) -> usize {
221 self.services_by_name.len()
222 }
223
224 pub fn method_count(&self) -> usize {
226 self.methods_by_id.len()
227 }
228}
229
230pub struct ServiceBuilder<'a> {
232 registry: &'a mut ServiceRegistry,
233 service_name: &'static str,
234 service_doc: String,
235 service_id: ServiceId,
236 methods: HashMap<&'static str, MethodEntry>,
237}
238
239impl ServiceBuilder<'_> {
240 pub fn add_method(
242 &mut self,
243 name: &'static str,
244 doc: impl Into<String>,
245 args: Vec<ArgInfo>,
246 request_shape: &'static Shape,
247 response_shape: &'static Shape,
248 ) -> MethodId {
249 self.add_method_inner(name, doc.into(), args, request_shape, response_shape, false)
250 }
251
252 pub fn add_streaming_method(
254 &mut self,
255 name: &'static str,
256 doc: impl Into<String>,
257 args: Vec<ArgInfo>,
258 request_shape: &'static Shape,
259 response_shape: &'static Shape,
260 ) -> MethodId {
261 self.add_method_inner(name, doc.into(), args, request_shape, response_shape, true)
262 }
263
264 fn add_method_inner(
265 &mut self,
266 name: &'static str,
267 doc: String,
268 args: Vec<ArgInfo>,
269 request_shape: &'static Shape,
270 response_shape: &'static Shape,
271 is_streaming: bool,
272 ) -> MethodId {
273 let id = MethodId(self.registry.next_method_id);
274 self.registry.next_method_id += 1;
275
276 let full_name = format!("{}.{}", self.service_name, name);
277
278 let entry = MethodEntry {
279 id,
280 name,
281 full_name,
282 doc,
283 args,
284 request_shape,
285 response_shape,
286 is_streaming,
287 supported_encodings: vec![Encoding::Postcard], };
289
290 self.methods.insert(name, entry);
291
292 self.registry.methods_by_id.insert(
294 id,
295 MethodLookup {
296 service_name: self.service_name,
297 method_name: name,
298 },
299 );
300
301 id
302 }
303
304 pub fn finish(self) {
306 let entry = ServiceEntry {
307 id: self.service_id,
308 name: self.service_name,
309 doc: self.service_doc,
310 methods: self.methods,
311 };
312 self.registry
313 .services_by_name
314 .insert(self.service_name, entry);
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321 use facet::Facet;
322
323 #[derive(Facet)]
324 struct AddRequest {
325 a: i32,
326 b: i32,
327 }
328
329 #[derive(Facet)]
330 struct AddResponse {
331 result: i32,
332 }
333
334 #[derive(Facet)]
335 struct RangeRequest {
336 n: u32,
337 }
338
339 #[derive(Facet)]
340 struct RangeItem {
341 value: u32,
342 }
343
344 #[test]
345 fn test_register_service() {
346 let mut registry = ServiceRegistry::new();
347
348 let mut builder = registry.register_service("Adder", "A simple adder service.");
349 let add_id = builder.add_method(
350 "add",
351 "Add two numbers together.",
352 vec![
353 ArgInfo {
354 name: "a",
355 type_name: "i32",
356 },
357 ArgInfo {
358 name: "b",
359 type_name: "i32",
360 },
361 ],
362 <AddRequest as Facet>::SHAPE,
363 <AddResponse as Facet>::SHAPE,
364 );
365 builder.finish();
366
367 assert_eq!(registry.service_count(), 1);
368 assert_eq!(registry.method_count(), 1);
369
370 let service = registry.service("Adder").unwrap();
371 assert_eq!(service.name, "Adder");
372 assert_eq!(service.doc, "A simple adder service.");
373 assert_eq!(service.id.0, 0);
374
375 let method = service.method("add").unwrap();
376 assert_eq!(method.id, add_id);
377 assert_eq!(method.name, "add");
378 assert_eq!(method.full_name, "Adder.add");
379 assert_eq!(method.doc, "Add two numbers together.");
380 assert!(!method.is_streaming);
381 assert_eq!(method.args.len(), 2);
382 assert_eq!(method.args[0].name, "a");
383 assert_eq!(method.args[1].name, "b");
384 }
385
386 #[test]
387 fn test_register_multiple_services() {
388 let mut registry = ServiceRegistry::new();
389
390 let mut builder = registry.register_service("Adder", "");
392 let add_id = builder.add_method(
393 "add",
394 "",
395 vec![
396 ArgInfo {
397 name: "a",
398 type_name: "i32",
399 },
400 ArgInfo {
401 name: "b",
402 type_name: "i32",
403 },
404 ],
405 <AddRequest as Facet>::SHAPE,
406 <AddResponse as Facet>::SHAPE,
407 );
408 builder.finish();
409
410 let mut builder = registry.register_service("RangeService", "");
412 let range_id = builder.add_streaming_method(
413 "range",
414 "",
415 vec![ArgInfo {
416 name: "n",
417 type_name: "u32",
418 }],
419 <RangeRequest as Facet>::SHAPE,
420 <RangeItem as Facet>::SHAPE,
421 );
422 builder.finish();
423
424 assert_eq!(registry.service_count(), 2);
425 assert_eq!(registry.method_count(), 2);
426
427 assert_ne!(add_id, range_id);
429 assert_eq!(add_id.0, 1); assert_eq!(range_id.0, 2);
431
432 let method = registry.lookup_method("RangeService", "range").unwrap();
434 assert!(method.is_streaming);
435
436 let method = registry.method_by_id(range_id).unwrap();
438 assert_eq!(method.full_name, "RangeService.range");
439 }
440
441 #[test]
442 fn test_resolve_method_id() {
443 let mut registry = ServiceRegistry::new();
444
445 let mut builder = registry.register_service("Adder", "");
446 builder.add_method(
447 "add",
448 "",
449 vec![
450 ArgInfo {
451 name: "a",
452 type_name: "i32",
453 },
454 ArgInfo {
455 name: "b",
456 type_name: "i32",
457 },
458 ],
459 <AddRequest as Facet>::SHAPE,
460 <AddResponse as Facet>::SHAPE,
461 );
462 builder.finish();
463
464 let id = registry.resolve_method_id("Adder", "add").unwrap();
465 assert_eq!(id.0, 1);
466
467 assert!(registry.resolve_method_id("Adder", "subtract").is_none());
469 assert!(registry.resolve_method_id("Calculator", "add").is_none());
470 }
471
472 #[test]
473 fn test_method_id_zero_reserved() {
474 assert_eq!(MethodId::CONTROL.0, 0);
475
476 let mut registry = ServiceRegistry::new();
477 let mut builder = registry.register_service("Test", "");
478 let first_method_id = builder.add_method(
479 "test",
480 "",
481 vec![],
482 <AddRequest as Facet>::SHAPE,
483 <AddResponse as Facet>::SHAPE,
484 );
485 builder.finish();
486
487 assert_eq!(first_method_id.0, 1);
489 }
490
491 #[test]
492 fn test_encoding_support() {
493 let mut registry = ServiceRegistry::new();
494
495 let mut builder = registry.register_service("Adder", "");
496 builder.add_method(
497 "add",
498 "",
499 vec![
500 ArgInfo {
501 name: "a",
502 type_name: "i32",
503 },
504 ArgInfo {
505 name: "b",
506 type_name: "i32",
507 },
508 ],
509 <AddRequest as Facet>::SHAPE,
510 <AddResponse as Facet>::SHAPE,
511 );
512 builder.finish();
513
514 let method = registry.lookup_method("Adder", "add").unwrap();
515
516 assert!(method.supports_encoding(Encoding::Postcard));
518 assert!(!method.supports_encoding(Encoding::Json));
519 }
520
521 #[test]
522 fn test_shapes_are_present() {
523 let mut registry = ServiceRegistry::new();
524
525 let mut builder = registry.register_service("Adder", "");
526 builder.add_method(
527 "add",
528 "",
529 vec![
530 ArgInfo {
531 name: "a",
532 type_name: "i32",
533 },
534 ArgInfo {
535 name: "b",
536 type_name: "i32",
537 },
538 ],
539 <AddRequest as Facet>::SHAPE,
540 <AddResponse as Facet>::SHAPE,
541 );
542 builder.finish();
543
544 let method = registry.lookup_method("Adder", "add").unwrap();
545
546 assert!(!method.request_shape.type_identifier.is_empty());
548 assert!(!method.response_shape.type_identifier.is_empty());
549 }
550
551 #[test]
552 fn test_docs_captured() {
553 let mut registry = ServiceRegistry::new();
554
555 let service_doc = "This is the service documentation.\nIt can span multiple lines.";
556 let method_doc = "This method adds two numbers.\n\n# Arguments\n* `a` - First number\n* `b` - Second number";
557
558 let mut builder = registry.register_service("Calculator", service_doc);
559 builder.add_method(
560 "add",
561 method_doc,
562 vec![
563 ArgInfo {
564 name: "a",
565 type_name: "i32",
566 },
567 ArgInfo {
568 name: "b",
569 type_name: "i32",
570 },
571 ],
572 <AddRequest as Facet>::SHAPE,
573 <AddResponse as Facet>::SHAPE,
574 );
575 builder.finish();
576
577 let service = registry.service("Calculator").unwrap();
578 assert_eq!(service.doc, service_doc);
579
580 let method = service.method("add").unwrap();
581 assert_eq!(method.doc, method_doc);
582 }
583}