1#![doc = include_str!("../README.md")]
2#![forbid(unsafe_op_in_unsafe_fn)]
3
4pub mod introspection;
5
6use facet_core::Shape;
7use std::collections::HashMap;
8use std::sync::LazyLock;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub struct ServiceId(pub u32);
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub struct MethodId(pub u32);
23
24impl MethodId {
25 pub const CONTROL: MethodId = MethodId(0);
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31#[repr(u8)]
32pub enum Encoding {
33 Postcard = 0,
35 Json = 1,
37}
38
39impl Encoding {
40 pub const ALL: &'static [Encoding] = &[Encoding::Postcard, Encoding::Json];
42}
43
44#[derive(Debug, Clone)]
46pub struct ArgInfo {
47 pub name: &'static str,
49 pub type_name: &'static str,
51}
52
53#[derive(Debug)]
55pub struct MethodEntry {
56 pub id: MethodId,
58 pub name: &'static str,
60 pub full_name: String,
62 pub doc: String,
64 pub args: Vec<ArgInfo>,
66 pub request_shape: &'static Shape,
68 pub response_shape: &'static Shape,
70 pub is_streaming: bool,
72 pub supported_encodings: Vec<Encoding>,
74}
75
76impl MethodEntry {
77 pub fn supports_encoding(&self, encoding: Encoding) -> bool {
79 self.supported_encodings.contains(&encoding)
80 }
81}
82
83#[derive(Debug)]
85pub struct ServiceEntry {
86 pub id: ServiceId,
88 pub name: &'static str,
90 pub doc: String,
92 pub methods: HashMap<&'static str, MethodEntry>,
94}
95
96impl ServiceEntry {
97 pub fn method(&self, name: &str) -> Option<&MethodEntry> {
99 self.methods.get(name)
100 }
101
102 pub fn iter_methods(&self) -> impl Iterator<Item = &MethodEntry> {
104 self.methods.values()
105 }
106}
107
108#[derive(Debug, Default)]
113pub struct ServiceRegistry {
114 services_by_name: HashMap<&'static str, ServiceEntry>,
116 methods_by_id: HashMap<MethodId, MethodLookup>,
118 next_service_id: u32,
120}
121
122#[derive(Debug, Clone)]
124struct MethodLookup {
125 service_name: &'static str,
126 method_name: &'static str,
127}
128
129impl ServiceRegistry {
130 pub fn new() -> Self {
132 Self {
133 services_by_name: HashMap::new(),
134 methods_by_id: HashMap::new(),
135 next_service_id: 0,
136 }
137 }
138
139 pub fn register_service(
143 &mut self,
144 name: &'static str,
145 doc: impl Into<String>,
146 ) -> ServiceBuilder<'_> {
147 let id = ServiceId(self.next_service_id);
148 self.next_service_id += 1;
149
150 ServiceBuilder {
151 registry: self,
152 service_name: name,
153 service_doc: doc.into(),
154 service_id: id,
155 methods: HashMap::new(),
156 }
157 }
158
159 pub fn service(&self, name: &str) -> Option<&ServiceEntry> {
161 self.services_by_name.get(name)
162 }
163
164 pub fn lookup_method(&self, service_name: &str, method_name: &str) -> Option<&MethodEntry> {
166 self.services_by_name
167 .get(service_name)
168 .and_then(|s| s.method(method_name))
169 }
170
171 pub fn method_by_id(&self, id: MethodId) -> Option<&MethodEntry> {
173 let lookup = self.methods_by_id.get(&id)?;
174 self.lookup_method(lookup.service_name, lookup.method_name)
175 }
176
177 pub fn resolve_method_id(&self, service_name: &str, method_name: &str) -> Option<MethodId> {
179 self.lookup_method(service_name, method_name).map(|m| m.id)
180 }
181
182 pub fn iter_services(&self) -> impl Iterator<Item = &ServiceEntry> {
184 self.services_by_name.values()
185 }
186
187 pub fn services(&self) -> impl Iterator<Item = &ServiceEntry> {
189 self.iter_services()
190 }
191
192 pub fn service_by_id(&self, id: ServiceId) -> Option<&ServiceEntry> {
194 self.services_by_name.values().find(|s| s.id == id)
195 }
196
197 pub fn service_count(&self) -> usize {
199 self.services_by_name.len()
200 }
201
202 pub fn method_count(&self) -> usize {
204 self.methods_by_id.len()
205 }
206
207 pub fn global() -> &'static parking_lot::RwLock<ServiceRegistry> {
212 &GLOBAL_REGISTRY
213 }
214
215 pub fn with_global<F, R>(f: F) -> R
229 where
230 F: FnOnce(&ServiceRegistry) -> R,
231 {
232 f(&GLOBAL_REGISTRY.read())
233 }
234
235 pub fn with_global_mut<F, R>(f: F) -> R
249 where
250 F: FnOnce(&mut ServiceRegistry) -> R,
251 {
252 f(&mut GLOBAL_REGISTRY.write())
253 }
254}
255
256fn compute_method_id(service_name: &str, method_name: &str) -> MethodId {
257 const FNV_OFFSET: u64 = 0xcbf29ce484222325;
259 const FNV_PRIME: u64 = 0x100000001b3;
260
261 let mut hash: u64 = FNV_OFFSET;
262
263 for byte in service_name.bytes() {
264 hash ^= byte as u64;
265 hash = hash.wrapping_mul(FNV_PRIME);
266 }
267
268 hash ^= b'.' as u64;
269 hash = hash.wrapping_mul(FNV_PRIME);
270
271 for byte in method_name.bytes() {
272 hash ^= byte as u64;
273 hash = hash.wrapping_mul(FNV_PRIME);
274 }
275
276 MethodId(((hash >> 32) ^ hash) as u32)
277}
278
279static GLOBAL_REGISTRY: LazyLock<parking_lot::RwLock<ServiceRegistry>> =
285 LazyLock::new(|| parking_lot::RwLock::new(ServiceRegistry::new()));
286
287pub struct ServiceBuilder<'a> {
289 registry: &'a mut ServiceRegistry,
290 service_name: &'static str,
291 service_doc: String,
292 service_id: ServiceId,
293 methods: HashMap<&'static str, MethodEntry>,
294}
295
296impl ServiceBuilder<'_> {
297 pub fn add_method(
299 &mut self,
300 name: &'static str,
301 doc: impl Into<String>,
302 args: Vec<ArgInfo>,
303 request_shape: &'static Shape,
304 response_shape: &'static Shape,
305 ) -> MethodId {
306 self.add_method_inner(name, doc.into(), args, request_shape, response_shape, false)
307 }
308
309 pub fn add_streaming_method(
311 &mut self,
312 name: &'static str,
313 doc: impl Into<String>,
314 args: Vec<ArgInfo>,
315 request_shape: &'static Shape,
316 response_shape: &'static Shape,
317 ) -> MethodId {
318 self.add_method_inner(name, doc.into(), args, request_shape, response_shape, true)
319 }
320
321 fn add_method_inner(
322 &mut self,
323 name: &'static str,
324 doc: String,
325 args: Vec<ArgInfo>,
326 request_shape: &'static Shape,
327 response_shape: &'static Shape,
328 is_streaming: bool,
329 ) -> MethodId {
330 let id = compute_method_id(self.service_name, name);
331
332 let full_name = format!("{}.{}", self.service_name, name);
333
334 if let Some(existing) = self.registry.methods_by_id.get(&id) {
335 panic!(
338 "method id collision: {:?} used by {}.{} and {}.{}",
339 id, existing.service_name, existing.method_name, self.service_name, name
340 );
341 }
342
343 let entry = MethodEntry {
344 id,
345 name,
346 full_name,
347 doc,
348 args,
349 request_shape,
350 response_shape,
351 is_streaming,
352 supported_encodings: vec![Encoding::Postcard], };
354
355 self.methods.insert(name, entry);
356
357 self.registry.methods_by_id.insert(
359 id,
360 MethodLookup {
361 service_name: self.service_name,
362 method_name: name,
363 },
364 );
365
366 id
367 }
368
369 pub fn finish(self) {
371 let entry = ServiceEntry {
372 id: self.service_id,
373 name: self.service_name,
374 doc: self.service_doc,
375 methods: self.methods,
376 };
377 self.registry
378 .services_by_name
379 .insert(self.service_name, entry);
380 }
381}
382
383#[cfg(test)]
384mod tests {
385 use super::*;
386 use facet::Facet;
387
388 #[derive(Facet)]
389 struct AddRequest {
390 a: i32,
391 b: i32,
392 }
393
394 #[derive(Facet)]
395 struct AddResponse {
396 result: i32,
397 }
398
399 #[derive(Facet)]
400 struct RangeRequest {
401 n: u32,
402 }
403
404 #[derive(Facet)]
405 struct RangeItem {
406 value: u32,
407 }
408
409 #[test]
410 fn test_register_service() {
411 let mut registry = ServiceRegistry::new();
412
413 let mut builder = registry.register_service("Adder", "A simple adder service.");
414 let add_id = builder.add_method(
415 "add",
416 "Add two numbers together.",
417 vec![
418 ArgInfo {
419 name: "a",
420 type_name: "i32",
421 },
422 ArgInfo {
423 name: "b",
424 type_name: "i32",
425 },
426 ],
427 <AddRequest as Facet>::SHAPE,
428 <AddResponse as Facet>::SHAPE,
429 );
430 builder.finish();
431
432 assert_eq!(registry.service_count(), 1);
433 assert_eq!(registry.method_count(), 1);
434
435 let service = registry.service("Adder").unwrap();
436 assert_eq!(service.name, "Adder");
437 assert_eq!(service.doc, "A simple adder service.");
438 assert_eq!(service.id.0, 0);
439
440 let method = service.method("add").unwrap();
441 assert_eq!(method.id, add_id);
442 assert_eq!(method.name, "add");
443 assert_eq!(method.full_name, "Adder.add");
444 assert_eq!(method.doc, "Add two numbers together.");
445 assert!(!method.is_streaming);
446 assert_eq!(method.args.len(), 2);
447 assert_eq!(method.args[0].name, "a");
448 assert_eq!(method.args[1].name, "b");
449 }
450
451 #[test]
452 fn test_register_multiple_services() {
453 let mut registry = ServiceRegistry::new();
454
455 let mut builder = registry.register_service("Adder", "");
457 let add_id = builder.add_method(
458 "add",
459 "",
460 vec![
461 ArgInfo {
462 name: "a",
463 type_name: "i32",
464 },
465 ArgInfo {
466 name: "b",
467 type_name: "i32",
468 },
469 ],
470 <AddRequest as Facet>::SHAPE,
471 <AddResponse as Facet>::SHAPE,
472 );
473 builder.finish();
474
475 let mut builder = registry.register_service("RangeService", "");
477 let range_id = builder.add_streaming_method(
478 "range",
479 "",
480 vec![ArgInfo {
481 name: "n",
482 type_name: "u32",
483 }],
484 <RangeRequest as Facet>::SHAPE,
485 <RangeItem as Facet>::SHAPE,
486 );
487 builder.finish();
488
489 assert_eq!(registry.service_count(), 2);
490 assert_eq!(registry.method_count(), 2);
491
492 assert_ne!(add_id, range_id);
494 assert_eq!(add_id, compute_method_id("Adder", "add"));
495 assert_eq!(range_id, compute_method_id("RangeService", "range"));
496 assert_ne!(add_id, MethodId::CONTROL);
497 assert_ne!(range_id, MethodId::CONTROL);
498
499 let method = registry.lookup_method("RangeService", "range").unwrap();
501 assert!(method.is_streaming);
502
503 let method = registry.method_by_id(range_id).unwrap();
505 assert_eq!(method.full_name, "RangeService.range");
506 }
507
508 #[test]
509 fn test_resolve_method_id() {
510 let mut registry = ServiceRegistry::new();
511
512 let mut builder = registry.register_service("Adder", "");
513 builder.add_method(
514 "add",
515 "",
516 vec![
517 ArgInfo {
518 name: "a",
519 type_name: "i32",
520 },
521 ArgInfo {
522 name: "b",
523 type_name: "i32",
524 },
525 ],
526 <AddRequest as Facet>::SHAPE,
527 <AddResponse as Facet>::SHAPE,
528 );
529 builder.finish();
530
531 let id = registry.resolve_method_id("Adder", "add").unwrap();
532 assert_eq!(id, compute_method_id("Adder", "add"));
533 assert_ne!(id, MethodId::CONTROL);
534
535 assert!(registry.resolve_method_id("Adder", "subtract").is_none());
537 assert!(registry.resolve_method_id("Calculator", "add").is_none());
538 }
539
540 #[test]
541 fn test_method_id_zero_reserved() {
542 assert_eq!(MethodId::CONTROL.0, 0);
543
544 let mut registry = ServiceRegistry::new();
545 let mut builder = registry.register_service("Test", "");
546 let first_method_id = builder.add_method(
547 "test",
548 "",
549 vec![],
550 <AddRequest as Facet>::SHAPE,
551 <AddResponse as Facet>::SHAPE,
552 );
553 builder.finish();
554
555 assert_eq!(first_method_id, compute_method_id("Test", "test"));
556 assert_ne!(first_method_id, MethodId::CONTROL);
557 }
558
559 #[test]
560 fn test_encoding_support() {
561 let mut registry = ServiceRegistry::new();
562
563 let mut builder = registry.register_service("Adder", "");
564 builder.add_method(
565 "add",
566 "",
567 vec![
568 ArgInfo {
569 name: "a",
570 type_name: "i32",
571 },
572 ArgInfo {
573 name: "b",
574 type_name: "i32",
575 },
576 ],
577 <AddRequest as Facet>::SHAPE,
578 <AddResponse as Facet>::SHAPE,
579 );
580 builder.finish();
581
582 let method = registry.lookup_method("Adder", "add").unwrap();
583
584 assert!(method.supports_encoding(Encoding::Postcard));
586 assert!(!method.supports_encoding(Encoding::Json));
587 }
588
589 #[test]
590 fn test_shapes_are_present() {
591 let mut registry = ServiceRegistry::new();
592
593 let mut builder = registry.register_service("Adder", "");
594 builder.add_method(
595 "add",
596 "",
597 vec![
598 ArgInfo {
599 name: "a",
600 type_name: "i32",
601 },
602 ArgInfo {
603 name: "b",
604 type_name: "i32",
605 },
606 ],
607 <AddRequest as Facet>::SHAPE,
608 <AddResponse as Facet>::SHAPE,
609 );
610 builder.finish();
611
612 let method = registry.lookup_method("Adder", "add").unwrap();
613
614 assert!(!method.request_shape.type_identifier.is_empty());
616 assert!(!method.response_shape.type_identifier.is_empty());
617 }
618
619 #[test]
620 fn test_docs_captured() {
621 let mut registry = ServiceRegistry::new();
622
623 let service_doc = "This is the service documentation.\nIt can span multiple lines.";
624 let method_doc = "This method adds two numbers.\n\n# Arguments\n* `a` - First number\n* `b` - Second number";
625
626 let mut builder = registry.register_service("Calculator", service_doc);
627 builder.add_method(
628 "add",
629 method_doc,
630 vec![
631 ArgInfo {
632 name: "a",
633 type_name: "i32",
634 },
635 ArgInfo {
636 name: "b",
637 type_name: "i32",
638 },
639 ],
640 <AddRequest as Facet>::SHAPE,
641 <AddResponse as Facet>::SHAPE,
642 );
643 builder.finish();
644
645 let service = registry.service("Calculator").unwrap();
646 assert_eq!(service.doc, service_doc);
647
648 let method = service.method("add").unwrap();
649 assert_eq!(method.doc, method_doc);
650 }
651
652 #[test]
653 fn test_global_registry() {
654 ServiceRegistry::with_global_mut(|registry| {
656 let mut builder = registry.register_service("GlobalTestService", "Test service");
657 builder.add_method(
658 "test_method",
659 "Test method",
660 vec![],
661 <AddRequest as Facet>::SHAPE,
662 <AddResponse as Facet>::SHAPE,
663 );
664 builder.finish();
665 });
666
667 ServiceRegistry::with_global(|registry| {
669 let service = registry.service("GlobalTestService").unwrap();
670 assert_eq!(service.name, "GlobalTestService");
671 assert_eq!(service.doc, "Test service");
672
673 let method = service.method("test_method").unwrap();
674 assert_eq!(method.name, "test_method");
675 });
676 }
677
678 #[test]
679 fn test_global_registry_method_by_id() {
680 let method_id = ServiceRegistry::with_global_mut(|registry| {
682 let mut builder = registry.register_service("MethodIdTest", "");
683 let id = builder.add_method(
684 "lookup_test",
685 "",
686 vec![],
687 <AddRequest as Facet>::SHAPE,
688 <AddResponse as Facet>::SHAPE,
689 );
690 builder.finish();
691 id
692 });
693
694 ServiceRegistry::with_global(|registry| {
696 let method = registry.method_by_id(method_id).unwrap();
697 assert_eq!(method.full_name, "MethodIdTest.lookup_test");
698 });
699 }
700}