1use std::collections::HashSet;
9
10use facet_core::{ScalarType, Shape};
11use vox_types::{
12 EnumInfo, ServiceDescriptor, ShapeKind, StructInfo, VariantKind, classify_shape,
13 classify_variant, is_bytes,
14};
15
16fn transparent_named_alias(shape: &'static Shape) -> Option<(&'static str, &'static Shape)> {
17 if !shape.is_transparent() {
18 return None;
19 }
20 let name = extract_type_name(shape.type_identifier)?;
21 let inner = shape.inner?;
22 Some((name, inner))
23}
24
25fn extract_type_name(type_identifier: &'static str) -> Option<&'static str> {
26 if type_identifier.is_empty()
27 || type_identifier.starts_with('(')
28 || type_identifier.starts_with('[')
29 {
30 return None;
31 }
32 Some(type_identifier)
33}
34
35pub fn ts_field_access(expr: &str, field_name: &str) -> String {
38 if field_name
39 .chars()
40 .next()
41 .is_some_and(|c| c.is_ascii_digit())
42 {
43 format!("{expr}[{field_name}]")
44 } else {
45 format!("{expr}.{field_name}")
46 }
47}
48
49pub fn collect_named_types(service: &ServiceDescriptor) -> Vec<(String, &'static Shape)> {
52 let mut seen = HashSet::new();
53 let mut types = Vec::new();
54
55 fn visit(
56 shape: &'static Shape,
57 seen: &mut HashSet<String>,
58 types: &mut Vec<(String, &'static Shape)>,
59 ) {
60 if let Some((name, inner)) = transparent_named_alias(shape) {
61 if !seen.contains(name) {
62 seen.insert(name.to_string());
63 visit(inner, seen, types);
64 types.push((name.to_string(), shape));
65 }
66 return;
67 }
68
69 match classify_shape(shape) {
70 ShapeKind::Struct(StructInfo {
71 name: Some(name),
72 fields,
73 ..
74 }) if seen.insert(name.to_string()) => {
75 for field in fields {
77 visit(field.shape(), seen, types);
78 }
79 types.push((name.to_string(), shape));
80 }
81 ShapeKind::Enum(EnumInfo {
82 name: Some(name),
83 variants,
84 }) if seen.insert(name.to_string()) => {
85 for variant in variants {
87 match classify_variant(variant) {
88 VariantKind::Newtype { inner } => visit(inner, seen, types),
89 VariantKind::Struct { fields } | VariantKind::Tuple { fields } => {
90 for field in fields {
91 visit(field.shape(), seen, types);
92 }
93 }
94 VariantKind::Unit => {}
95 }
96 }
97 types.push((name.to_string(), shape));
98 }
99 ShapeKind::List { element } => visit(element, seen, types),
100 ShapeKind::Option { inner } => visit(inner, seen, types),
101 ShapeKind::Array { element, .. } => visit(element, seen, types),
102 ShapeKind::Map { key, value } => {
103 visit(key, seen, types);
104 visit(value, seen, types);
105 }
106 ShapeKind::Set { element } => visit(element, seen, types),
107 ShapeKind::Tuple { elements } => {
108 for param in elements {
109 visit(param.shape, seen, types);
110 }
111 }
112 ShapeKind::Tx { inner } | ShapeKind::Rx { inner } => visit(inner, seen, types),
113 ShapeKind::Pointer { pointee } => visit(pointee, seen, types),
114 ShapeKind::Result { ok, err } => {
115 visit(ok, seen, types);
116 visit(err, seen, types);
117 }
118 _ => {}
120 }
121 }
122
123 for method in service.methods {
124 for arg in method.args {
125 visit(arg.shape, &mut seen, &mut types);
126 }
127 visit(method.return_shape, &mut seen, &mut types);
128 }
129
130 types
131}
132
133pub fn generate_named_types(named_types: &[(String, &'static Shape)]) -> String {
135 let mut out = String::new();
136
137 if named_types.is_empty() {
138 return out;
139 }
140
141 out.push_str("// Named type definitions\n");
142
143 for (name, shape) in named_types {
144 if let Some((_, inner)) = transparent_named_alias(shape) {
145 out.push_str(&format!(
146 "export type {name} = {};\n\n",
147 ts_type_base_named(inner)
148 ));
149 continue;
150 }
151
152 match classify_shape(shape) {
153 ShapeKind::Struct(StructInfo { fields, .. }) => {
154 out.push_str(&format!("export interface {} {{\n", name));
155 for field in fields {
156 out.push_str(&format!(
157 " {}: {};\n",
158 field.name,
159 ts_type_base_named(field.shape())
160 ));
161 }
162 out.push_str("}\n\n");
163 }
164 ShapeKind::Enum(EnumInfo { variants, .. }) => {
165 out.push_str(&format!("export type {} =\n", name));
166 for (i, variant) in variants.iter().enumerate() {
167 let variant_type = match classify_variant(variant) {
168 VariantKind::Unit => format!("{{ tag: '{}' }}", variant.name),
169 VariantKind::Newtype { inner } => {
170 format!(
171 "{{ tag: '{}'; value: {} }}",
172 variant.name,
173 ts_type_base_named(inner)
174 )
175 }
176 VariantKind::Tuple { fields } | VariantKind::Struct { fields } => {
177 let field_strs = fields
178 .iter()
179 .map(|f| format!("{}: {}", f.name, ts_type_base_named(f.shape())))
180 .collect::<Vec<_>>()
181 .join("; ");
182 format!("{{ tag: '{}'; {} }}", variant.name, field_strs)
183 }
184 };
185 let sep = if i < variants.len() - 1 { "" } else { ";" };
186 out.push_str(&format!(" | {}{}\n", variant_type, sep));
187 }
188 out.push('\n');
189 }
190 _ => {}
191 }
192 }
193
194 out
195}
196
197pub fn ts_type_base_named(shape: &'static Shape) -> String {
200 if let Some((name, _)) = transparent_named_alias(shape) {
201 return name.to_string();
202 }
203
204 match classify_shape(shape) {
205 ShapeKind::Struct(StructInfo {
207 name: Some(name), ..
208 }) => name.to_string(),
209 ShapeKind::Enum(EnumInfo {
210 name: Some(name), ..
211 }) => name.to_string(),
212
213 ShapeKind::List { element } => {
215 if is_bytes(shape) {
217 return "Uint8Array".into();
218 }
219 if matches!(
221 classify_shape(element),
222 ShapeKind::Enum(EnumInfo { name: None, .. })
223 ) {
224 format!("({})[]", ts_type_base_named(element))
225 } else {
226 format!("{}[]", ts_type_base_named(element))
227 }
228 }
229 ShapeKind::Option { inner } => format!("{} | null", ts_type_base_named(inner)),
230 ShapeKind::Array { element, len } => format!("[{}; {}]", ts_type_base_named(element), len),
231 ShapeKind::Map { key, value } => {
232 format!(
233 "Map<{}, {}>",
234 ts_type_base_named(key),
235 ts_type_base_named(value)
236 )
237 }
238 ShapeKind::Set { element } => format!("Set<{}>", ts_type_base_named(element)),
239 ShapeKind::Tuple { elements } => {
240 let inner = elements
241 .iter()
242 .map(|p| ts_type_base_named(p.shape))
243 .collect::<Vec<_>>()
244 .join(", ");
245 format!("[{inner}]")
246 }
247 ShapeKind::Tx { inner } => format!("Tx<{}>", ts_type_base_named(inner)),
248 ShapeKind::Rx { inner } => format!("Rx<{}>", ts_type_base_named(inner)),
249
250 ShapeKind::Struct(StructInfo {
252 name: None, fields, ..
253 }) => {
254 let inner = fields
255 .iter()
256 .map(|f| format!("{}: {}", f.name, ts_type_base_named(f.shape())))
257 .collect::<Vec<_>>()
258 .join("; ");
259 format!("{{ {inner} }}")
260 }
261
262 ShapeKind::Enum(EnumInfo {
264 name: None,
265 variants,
266 }) => variants
267 .iter()
268 .map(|v| match classify_variant(v) {
269 VariantKind::Unit => format!("{{ tag: '{}' }}", v.name),
270 VariantKind::Newtype { inner } => {
271 format!(
272 "{{ tag: '{}'; value: {} }}",
273 v.name,
274 ts_type_base_named(inner)
275 )
276 }
277 VariantKind::Tuple { fields } | VariantKind::Struct { fields } => {
278 let field_strs = fields
279 .iter()
280 .map(|f| format!("{}: {}", f.name, ts_type_base_named(f.shape())))
281 .collect::<Vec<_>>()
282 .join("; ");
283 format!("{{ tag: '{}'; {} }}", v.name, field_strs)
284 }
285 })
286 .collect::<Vec<_>>()
287 .join(" | "),
288
289 ShapeKind::Scalar(scalar) => ts_scalar_type(scalar),
291 ShapeKind::Slice { element } => format!("{}[]", ts_type_base_named(element)),
292 ShapeKind::Pointer { pointee } => ts_type_base_named(pointee),
293 ShapeKind::Result { ok, err } => {
294 format!(
295 "{{ ok: true; value: {} }} | {{ ok: false; error: {} }}",
296 ts_type_base_named(ok),
297 ts_type_base_named(err)
298 )
299 }
300 ShapeKind::TupleStruct { fields } => {
301 let inner = fields
302 .iter()
303 .map(|f| ts_type_base_named(f.shape()))
304 .collect::<Vec<_>>()
305 .join(", ");
306 format!("[{inner}]")
307 }
308 ShapeKind::Opaque => "unknown".into(),
309 }
310}
311
312pub fn ts_scalar_type(scalar: ScalarType) -> String {
314 match scalar {
315 ScalarType::Bool => "boolean".into(),
316 ScalarType::U8
317 | ScalarType::U16
318 | ScalarType::U32
319 | ScalarType::I8
320 | ScalarType::I16
321 | ScalarType::I32
322 | ScalarType::F32
323 | ScalarType::F64 => "number".into(),
324 ScalarType::U64
325 | ScalarType::U128
326 | ScalarType::I64
327 | ScalarType::I128
328 | ScalarType::USize
329 | ScalarType::ISize => "bigint".into(),
330 ScalarType::Char | ScalarType::Str | ScalarType::String | ScalarType::CowStr => {
331 "string".into()
332 }
333 ScalarType::Unit => "void".into(),
334 _ => "unknown".into(),
335 }
336}
337
338pub fn ts_type_client_arg(shape: &'static Shape) -> String {
342 match classify_shape(shape) {
343 ShapeKind::Tx { inner } => format!("Tx<{}>", ts_type_client_arg(inner)),
344 ShapeKind::Rx { inner } => format!("Rx<{}>", ts_type_client_arg(inner)),
345 _ => ts_type_base_named(shape),
346 }
347}
348
349pub fn ts_type_client_return(shape: &'static Shape) -> String {
352 crate::targets::swift::types::assert_no_channels_in_return_shape(shape);
353 ts_type_base_named(shape)
354}
355
356pub fn ts_type_server_arg(shape: &'static Shape) -> String {
360 match classify_shape(shape) {
361 ShapeKind::Tx { inner } => format!("Tx<{}>", ts_type_server_arg(inner)),
362 ShapeKind::Rx { inner } => format!("Rx<{}>", ts_type_server_arg(inner)),
363 _ => ts_type_base_named(shape),
364 }
365}
366
367pub fn ts_type_server_return(shape: &'static Shape) -> String {
369 crate::targets::swift::types::assert_no_channels_in_return_shape(shape);
370 ts_type_base_named(shape)
371}
372
373pub fn ts_type(shape: &'static Shape) -> String {
376 ts_type_base_named(shape)
377}
378
379pub fn is_fully_supported(shape: &'static Shape) -> bool {
382 match classify_shape(shape) {
383 ShapeKind::Tx { inner } | ShapeKind::Rx { inner } => is_fully_supported(inner),
385 ShapeKind::List { element }
386 | ShapeKind::Option { inner: element }
387 | ShapeKind::Set { element }
388 | ShapeKind::Array { element, .. }
389 | ShapeKind::Slice { element } => is_fully_supported(element),
390 ShapeKind::Map { key, value } => is_fully_supported(key) && is_fully_supported(value),
391 ShapeKind::Tuple { elements } => elements.iter().all(|p| is_fully_supported(p.shape)),
392 ShapeKind::TupleStruct { fields } => fields.iter().all(|f| is_fully_supported(f.shape())),
393 ShapeKind::Struct(StructInfo { fields, .. }) => {
394 fields.iter().all(|f| is_fully_supported(f.shape()))
395 }
396 ShapeKind::Enum(EnumInfo { variants, .. }) => {
397 variants.iter().all(|v| match classify_variant(v) {
398 VariantKind::Unit => true,
399 VariantKind::Newtype { inner } => is_fully_supported(inner),
400 VariantKind::Tuple { fields } | VariantKind::Struct { fields } => {
401 fields.iter().all(|f| is_fully_supported(f.shape()))
402 }
403 })
404 }
405 ShapeKind::Pointer { pointee } => is_fully_supported(pointee),
406 ShapeKind::Scalar(_) => true,
407 ShapeKind::Result { ok, err } => is_fully_supported(ok) && is_fully_supported(err),
408 ShapeKind::Opaque => false,
409 }
410}