vox_types/
method_identity.rs1use std::collections::HashSet;
2
3use facet::Facet;
4use facet_core::{Def, Shape, Type, UserType};
5use heck::ToKebabCase;
6
7use crate::{ArgDescriptor, MethodDescriptor, MethodId, RetryPolicy, is_rx, is_tx};
8
9pub fn method_id_name_only(service_name: &str, method_name: &str) -> MethodId {
16 let mut input = Vec::new();
17 input.extend_from_slice(service_name.to_kebab_case().as_bytes());
18 input.push(b'.');
19 input.extend_from_slice(method_name.to_kebab_case().as_bytes());
20 let h = blake3::hash(&input);
21 let first8: [u8; 8] = h.as_bytes()[0..8].try_into().expect("slice len");
22 MethodId(u64::from_le_bytes(first8))
23}
24
25pub fn method_descriptor<'a, 'r, A: Facet<'a>, R: Facet<'r>>(
27 service_name: &'static str,
28 method_name: &'static str,
29 arg_names: &[&'static str],
30 doc: Option<&'static str>,
31) -> &'static MethodDescriptor {
32 method_descriptor_with_retry::<A, R>(
33 service_name,
34 method_name,
35 arg_names,
36 doc,
37 RetryPolicy::VOLATILE,
38 )
39}
40
41pub fn method_descriptor_with_retry<'a, 'r, A: Facet<'a>, R: Facet<'r>>(
43 service_name: &'static str,
44 method_name: &'static str,
45 arg_names: &[&'static str],
46 doc: Option<&'static str>,
47 retry: RetryPolicy,
48) -> &'static MethodDescriptor {
49 assert!(
50 !shape_contains_channel(R::SHAPE),
51 "channels are not allowed in return types: {service_name}.{method_name}"
52 );
53 assert!(
54 !(retry.persist && shape_contains_channel(A::SHAPE)),
55 "persist methods cannot carry channels: {service_name}.{method_name}"
56 );
57
58 let id = method_id_name_only(service_name, method_name);
59
60 let arg_shapes: &[&'static Shape] = match A::SHAPE.ty {
61 Type::User(UserType::Struct(s)) => {
62 let fields: Vec<&'static Shape> = s.fields.iter().map(|f| f.shape()).collect();
63 Box::leak(fields.into_boxed_slice())
64 }
65 _ => &[],
66 };
67
68 assert_eq!(
69 arg_names.len(),
70 arg_shapes.len(),
71 "arg_names length mismatch for {service_name}.{method_name}"
72 );
73
74 let args: &'static [ArgDescriptor] = Box::leak(
75 arg_names
76 .iter()
77 .zip(arg_shapes.iter())
78 .map(|(&name, &shape)| ArgDescriptor { name, shape })
79 .collect::<Vec<_>>()
80 .into_boxed_slice(),
81 );
82
83 Box::leak(Box::new(MethodDescriptor {
84 id,
85 service_name,
86 method_name,
87 args_shape: A::SHAPE,
88 args,
89 return_shape: R::SHAPE,
90 retry,
91 doc,
92 }))
93}
94
95pub fn shape_contains_channel(shape: &'static Shape) -> bool {
96 fn visit(shape: &'static Shape, seen: &mut HashSet<&'static Shape>) -> bool {
97 if is_tx(shape) || is_rx(shape) {
98 return true;
99 }
100
101 if !seen.insert(shape) {
102 return false;
103 }
104
105 if let Some(inner) = shape.inner
106 && visit(inner, seen)
107 {
108 return true;
109 }
110
111 if shape.type_params.iter().any(|t| visit(t.shape, seen)) {
112 return true;
113 }
114
115 match shape.def {
116 Def::List(list_def) => visit(list_def.t(), seen),
117 Def::Array(array_def) => visit(array_def.t(), seen),
118 Def::Slice(slice_def) => visit(slice_def.t(), seen),
119 Def::Map(map_def) => visit(map_def.k(), seen) || visit(map_def.v(), seen),
120 Def::Set(set_def) => visit(set_def.t(), seen),
121 Def::Option(opt_def) => visit(opt_def.t(), seen),
122 Def::Result(result_def) => visit(result_def.t(), seen) || visit(result_def.e(), seen),
123 Def::Pointer(ptr_def) => ptr_def.pointee.is_some_and(|p| visit(p, seen)),
124 _ => match shape.ty {
125 Type::User(UserType::Struct(s)) => s.fields.iter().any(|f| visit(f.shape(), seen)),
126 Type::User(UserType::Enum(e)) => e
127 .variants
128 .iter()
129 .any(|v| v.data.fields.iter().any(|f| visit(f.shape(), seen))),
130 _ => false,
131 },
132 }
133 }
134
135 let mut seen = HashSet::new();
136 visit(shape, &mut seen)
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn method_id_name_only_is_stable_across_case_variations() {
145 let a = method_id_name_only("MyService", "DoThingFast");
146 let b = method_id_name_only("my-service", "do-thing-fast");
147 let c = method_id_name_only("MY_SERVICE", "DO_THING_FAST");
148 assert_eq!(a, b);
149 assert_eq!(b, c);
150 }
151
152 #[test]
153 fn method_id_name_only_different_methods_produce_different_ids() {
154 let a = method_id_name_only("Svc", "alpha");
155 let b = method_id_name_only("Svc", "beta");
156 assert_ne!(a, b);
157 }
158}