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 let args_have_channels = shape_contains_channel(A::SHAPE);
54 assert!(
55 !(retry.persist && args_have_channels),
56 "persist methods cannot carry channels: {service_name}.{method_name}"
57 );
58
59 let id = method_id_name_only(service_name, method_name);
60
61 let arg_shapes: &[&'static Shape] = match A::SHAPE.ty {
62 Type::User(UserType::Struct(s)) => {
63 let fields: Vec<&'static Shape> = s.fields.iter().map(|f| f.shape()).collect();
64 Box::leak(fields.into_boxed_slice())
65 }
66 _ => &[],
67 };
68
69 assert_eq!(
70 arg_names.len(),
71 arg_shapes.len(),
72 "arg_names length mismatch for {service_name}.{method_name}"
73 );
74
75 let args: &'static [ArgDescriptor] = Box::leak(
76 arg_names
77 .iter()
78 .zip(arg_shapes.iter())
79 .map(|(&name, &shape)| ArgDescriptor { name, shape })
80 .collect::<Vec<_>>()
81 .into_boxed_slice(),
82 );
83
84 Box::leak(Box::new(MethodDescriptor {
85 id,
86 service_name,
87 method_name,
88 args_shape: A::SHAPE,
89 args,
90 return_shape: R::SHAPE,
91 args_have_channels,
92 retry,
93 doc,
94 }))
95}
96
97pub fn shape_contains_channel(shape: &'static Shape) -> bool {
98 fn visit(shape: &'static Shape, seen: &mut HashSet<&'static Shape>) -> bool {
99 if is_tx(shape) || is_rx(shape) {
100 return true;
101 }
102
103 if !seen.insert(shape) {
104 return false;
105 }
106
107 if let Some(inner) = shape.inner
108 && visit(inner, seen)
109 {
110 return true;
111 }
112
113 if shape.type_params.iter().any(|t| visit(t.shape, seen)) {
114 return true;
115 }
116
117 match shape.def {
118 Def::List(list_def) => visit(list_def.t(), seen),
119 Def::Array(array_def) => visit(array_def.t(), seen),
120 Def::Slice(slice_def) => visit(slice_def.t(), seen),
121 Def::Map(map_def) => visit(map_def.k(), seen) || visit(map_def.v(), seen),
122 Def::Set(set_def) => visit(set_def.t(), seen),
123 Def::Option(opt_def) => visit(opt_def.t(), seen),
124 Def::Result(result_def) => visit(result_def.t(), seen) || visit(result_def.e(), seen),
125 Def::Pointer(ptr_def) => ptr_def.pointee.is_some_and(|p| visit(p, seen)),
126 _ => match shape.ty {
127 Type::User(UserType::Struct(s)) => s.fields.iter().any(|f| visit(f.shape(), seen)),
128 Type::User(UserType::Enum(e)) => e
129 .variants
130 .iter()
131 .any(|v| v.data.fields.iter().any(|f| visit(f.shape(), seen))),
132 _ => false,
133 },
134 }
135 }
136
137 let mut seen = HashSet::new();
138 visit(shape, &mut seen)
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn method_id_name_only_is_stable_across_case_variations() {
147 let a = method_id_name_only("MyService", "DoThingFast");
148 let b = method_id_name_only("my-service", "do-thing-fast");
149 let c = method_id_name_only("MY_SERVICE", "DO_THING_FAST");
150 assert_eq!(a, b);
151 assert_eq!(b, c);
152 }
153
154 #[test]
155 fn method_id_name_only_different_methods_produce_different_ids() {
156 let a = method_id_name_only("Svc", "alpha");
157 let b = method_id_name_only("Svc", "beta");
158 assert_ne!(a, b);
159 }
160}