1use std::collections::HashSet;
9
10use facet_core::{Def, ScalarType, Shape, StructKind, Type, UserType};
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)?;
21 let inner = shape.inner?;
22 Some((name, inner))
23}
24
25fn enum_discriminator_field(variants: &[facet_core::Variant]) -> &'static str {
26 if variants
27 .iter()
28 .any(|variant| match classify_variant(variant) {
29 VariantKind::Tuple { fields } | VariantKind::Struct { fields } => {
30 fields.iter().any(|field| field.name == "tag")
31 }
32 VariantKind::Unit | VariantKind::Newtype { .. } => false,
33 })
34 {
35 "$tag"
36 } else {
37 "tag"
38 }
39}
40
41fn extract_type_name(shape: &'static Shape) -> Option<&'static str> {
48 let anonymous = matches!(shape.def, Def::Array(_) | Def::Slice(_))
49 || matches!(shape.ty, Type::User(UserType::Struct(s)) if s.kind == StructKind::Tuple);
50
51 if anonymous || shape.type_identifier.is_empty() {
52 return None;
53 }
54 Some(shape.type_identifier)
55}
56
57pub fn ts_field_access(expr: &str, field_name: &str) -> String {
60 if field_name
61 .chars()
62 .next()
63 .is_some_and(|c| c.is_ascii_digit())
64 {
65 format!("{expr}[{field_name}]")
66 } else {
67 format!("{expr}.{field_name}")
68 }
69}
70
71pub fn collect_named_types(service: &ServiceDescriptor) -> Vec<(String, &'static Shape)> {
74 let mut seen = HashSet::new();
75 let mut types = Vec::new();
76
77 fn visit(
78 shape: &'static Shape,
79 seen: &mut HashSet<String>,
80 types: &mut Vec<(String, &'static Shape)>,
81 ) {
82 if is_dynamic_value(shape) {
83 return;
84 }
85
86 if let Some((name, inner)) = transparent_named_alias(shape) {
87 if !seen.contains(name) {
88 seen.insert(name.to_string());
89 visit(inner, seen, types);
90 types.push((name.to_string(), shape));
91 }
92 return;
93 }
94
95 match classify_shape(shape) {
96 ShapeKind::Struct(StructInfo {
97 name: Some(name),
98 fields,
99 ..
100 }) if seen.insert(name.to_string()) => {
101 for field in fields {
103 visit(field.shape(), seen, types);
104 }
105 types.push((name.to_string(), shape));
106 }
107 ShapeKind::Enum(EnumInfo {
108 name: Some(name),
109 variants,
110 }) if seen.insert(name.to_string()) => {
111 for variant in variants {
113 match classify_variant(variant) {
114 VariantKind::Newtype { inner } => visit(inner, seen, types),
115 VariantKind::Struct { fields } | VariantKind::Tuple { fields } => {
116 for field in fields {
117 visit(field.shape(), seen, types);
118 }
119 }
120 VariantKind::Unit => {}
121 }
122 }
123 types.push((name.to_string(), shape));
124 }
125 ShapeKind::List { element } => visit(element, seen, types),
126 ShapeKind::Option { inner } => visit(inner, seen, types),
127 ShapeKind::Array { element, .. } => visit(element, seen, types),
128 ShapeKind::Map { key, value } => {
129 visit(key, seen, types);
130 visit(value, seen, types);
131 }
132 ShapeKind::Set { element } => visit(element, seen, types),
133 ShapeKind::Tuple { elements } => {
134 for param in elements {
135 visit(param.shape, seen, types);
136 }
137 }
138 ShapeKind::TupleStruct { fields } => {
139 for field in fields {
140 visit(field.shape(), seen, types);
141 }
142 }
143 ShapeKind::Tx { inner } | ShapeKind::Rx { inner } => visit(inner, seen, types),
144 ShapeKind::Pointer { pointee } => visit(pointee, seen, types),
145 ShapeKind::Result { ok, err } => {
146 visit(ok, seen, types);
147 visit(err, seen, types);
148 }
149 _ => {}
151 }
152 }
153
154 for method in service.methods {
155 for arg in method.args {
156 visit(arg.shape, &mut seen, &mut types);
157 if let Some(element) = arg.channel_element {
158 visit(element, &mut seen, &mut types);
159 }
160 }
161 visit(method.return_shape, &mut seen, &mut types);
162 }
163
164 types
165}
166
167pub fn generate_named_types(named_types: &[(String, &'static Shape)]) -> String {
169 let mut out = String::new();
170
171 if named_types.is_empty() {
172 return out;
173 }
174
175 out.push_str("// Named type definitions\n");
176
177 for (name, shape) in named_types {
178 if let Some((_, inner)) = transparent_named_alias(shape) {
179 out.push_str(&format!(
180 "export type {name} = {};\n\n",
181 ts_type_base_named(inner)
182 ));
183 continue;
184 }
185
186 match classify_shape(shape) {
187 ShapeKind::Struct(StructInfo { fields, .. }) => {
188 out.push_str(&format!("export interface {} {{\n", name));
189 for field in fields {
190 out.push_str(&format!(
191 " {}: {};\n",
192 field.name,
193 ts_type_base_named(field.shape())
194 ));
195 }
196 out.push_str("}\n\n");
197 }
198 ShapeKind::Enum(EnumInfo { variants, .. }) => {
199 let discriminator = enum_discriminator_field(variants);
200 out.push_str(&format!("export type {} =\n", name));
201 for (i, variant) in variants.iter().enumerate() {
202 let variant_type = match classify_variant(variant) {
203 VariantKind::Unit => {
204 format!("{{ {discriminator}: '{}' }}", variant.name)
205 }
206 VariantKind::Newtype { inner } => {
207 format!(
208 "{{ {discriminator}: '{}'; value: {} }}",
209 variant.name,
210 ts_type_base_named(inner)
211 )
212 }
213 VariantKind::Tuple { fields } | VariantKind::Struct { fields } => {
214 if fields.len() > 4 {
215 let mut variant_type =
216 format!("{{\n {discriminator}: '{}';\n", variant.name);
217 for field in fields {
218 variant_type.push_str(&format!(
219 " {}: {};\n",
220 field.name,
221 ts_type_base_named(field.shape())
222 ));
223 }
224 variant_type.push_str(" }");
225 variant_type
226 } else {
227 let field_strs = fields
228 .iter()
229 .map(|f| {
230 format!("{}: {}", f.name, ts_type_base_named(f.shape()))
231 })
232 .collect::<Vec<_>>()
233 .join("; ");
234 format!("{{ {discriminator}: '{}'; {} }}", variant.name, field_strs)
235 }
236 }
237 };
238 let prefix = if i == 0 { " " } else { " | " };
239 let sep = if i < variants.len() - 1 { "" } else { ";" };
240 out.push_str(&format!("{prefix}{variant_type}{sep}\n"));
241 }
242 out.push('\n');
243 }
244 _ => {}
245 }
246 }
247
248 out
249}
250
251pub fn ts_type_base_named(shape: &'static Shape) -> String {
254 if is_dynamic_value(shape) {
255 return "Value".into();
256 }
257
258 if let Some((name, _)) = transparent_named_alias(shape) {
259 return name.to_string();
260 }
261
262 match classify_shape(shape) {
263 ShapeKind::Struct(StructInfo {
265 name: Some(name), ..
266 }) => name.to_string(),
267 ShapeKind::Enum(EnumInfo {
268 name: Some(name), ..
269 }) => name.to_string(),
270
271 ShapeKind::List { element } => {
273 if is_bytes(shape) {
275 return "Uint8Array".into();
276 }
277 if matches!(
279 classify_shape(element),
280 ShapeKind::Enum(EnumInfo { name: None, .. })
281 ) {
282 format!("({})[]", ts_type_base_named(element))
283 } else {
284 format!("{}[]", ts_type_base_named(element))
285 }
286 }
287 ShapeKind::Option { inner } => format!("{} | null", ts_type_base_named(inner)),
288 ShapeKind::Array { element, len } => format!("[{}; {}]", ts_type_base_named(element), len),
289 ShapeKind::Map { key, value } => {
290 format!(
291 "Map<{}, {}>",
292 ts_type_base_named(key),
293 ts_type_base_named(value)
294 )
295 }
296 ShapeKind::Set { element } => format!("Set<{}>", ts_type_base_named(element)),
297 ShapeKind::Tuple { elements } => {
298 let inner = elements
299 .iter()
300 .map(|p| ts_type_base_named(p.shape))
301 .collect::<Vec<_>>()
302 .join(", ");
303 format!("[{inner}]")
304 }
305 ShapeKind::Tx { inner } => format!("Tx<{}>", ts_type_base_named(inner)),
306 ShapeKind::Rx { inner } => format!("Rx<{}>", ts_type_base_named(inner)),
307
308 ShapeKind::Struct(StructInfo {
310 name: None, fields, ..
311 }) => {
312 let inner = fields
313 .iter()
314 .map(|f| format!("{}: {}", f.name, ts_type_base_named(f.shape())))
315 .collect::<Vec<_>>()
316 .join("; ");
317 format!("{{ {inner} }}")
318 }
319
320 ShapeKind::Enum(EnumInfo {
322 name: None,
323 variants,
324 }) => {
325 let discriminator = enum_discriminator_field(variants);
326 variants
327 .iter()
328 .map(|v| match classify_variant(v) {
329 VariantKind::Unit => format!("{{ {discriminator}: '{}' }}", v.name),
330 VariantKind::Newtype { inner } => {
331 format!(
332 "{{ {discriminator}: '{}'; value: {} }}",
333 v.name,
334 ts_type_base_named(inner)
335 )
336 }
337 VariantKind::Tuple { fields } | VariantKind::Struct { fields } => {
338 let field_strs = fields
339 .iter()
340 .map(|f| format!("{}: {}", f.name, ts_type_base_named(f.shape())))
341 .collect::<Vec<_>>()
342 .join("; ");
343 format!("{{ {discriminator}: '{}'; {} }}", v.name, field_strs)
344 }
345 })
346 .collect::<Vec<_>>()
347 .join(" | ")
348 }
349
350 ShapeKind::Scalar(scalar) => ts_scalar_type(scalar),
352 ShapeKind::Slice { element } => format!("{}[]", ts_type_base_named(element)),
353 ShapeKind::Pointer { pointee } => ts_type_base_named(pointee),
354 ShapeKind::Result { ok, err } => {
355 format!(
356 "{{ ok: true; value: {} }} | {{ ok: false; error: {} }}",
357 ts_type_base_named(ok),
358 ts_type_base_named(err)
359 )
360 }
361 ShapeKind::TupleStruct { fields } => {
362 let inner = fields
363 .iter()
364 .map(|f| ts_type_base_named(f.shape()))
365 .collect::<Vec<_>>()
366 .join(", ");
367 format!("[{inner}]")
368 }
369 ShapeKind::Opaque => "unknown".into(),
370 }
371}
372
373pub fn is_dynamic_value(shape: &'static Shape) -> bool {
374 matches!(shape.def, facet_core::Def::DynamicValue(_))
375}
376
377pub fn ts_scalar_type(scalar: ScalarType) -> String {
379 match scalar {
380 ScalarType::Bool => "boolean".into(),
381 ScalarType::U8
382 | ScalarType::U16
383 | ScalarType::U32
384 | ScalarType::I8
385 | ScalarType::I16
386 | ScalarType::I32
387 | ScalarType::F32
388 | ScalarType::F64 => "number".into(),
389 ScalarType::U64
390 | ScalarType::U128
391 | ScalarType::I64
392 | ScalarType::I128
393 | ScalarType::USize
394 | ScalarType::ISize => "bigint".into(),
395 ScalarType::Char | ScalarType::Str | ScalarType::String | ScalarType::CowStr => {
396 "string".into()
397 }
398 ScalarType::Unit => "void".into(),
399 _ => "unknown".into(),
400 }
401}
402
403pub fn ts_type_client_arg(shape: &'static Shape) -> String {
407 match classify_shape(shape) {
408 ShapeKind::Tx { inner } => format!("Tx<{}>", ts_type_client_arg(inner)),
409 ShapeKind::Rx { inner } => format!("Rx<{}>", ts_type_client_arg(inner)),
410 _ => ts_type_base_named(shape),
411 }
412}
413
414pub fn ts_type_client_return(shape: &'static Shape) -> String {
417 crate::targets::swift::types::assert_no_channels_in_return_shape(shape);
418 ts_type_base_named(shape)
419}
420
421pub fn ts_type_server_arg(shape: &'static Shape) -> String {
425 match classify_shape(shape) {
426 ShapeKind::Tx { inner } => format!("Tx<{}>", ts_type_server_arg(inner)),
427 ShapeKind::Rx { inner } => format!("Rx<{}>", ts_type_server_arg(inner)),
428 _ => ts_type_base_named(shape),
429 }
430}
431
432pub fn ts_type_server_return(shape: &'static Shape) -> String {
434 crate::targets::swift::types::assert_no_channels_in_return_shape(shape);
435 ts_type_base_named(shape)
436}
437
438pub fn ts_type(shape: &'static Shape) -> String {
441 ts_type_base_named(shape)
442}
443
444pub fn is_fully_supported(shape: &'static Shape) -> bool {
447 if is_dynamic_value(shape) {
448 return true;
449 }
450
451 match classify_shape(shape) {
452 ShapeKind::Tx { inner } | ShapeKind::Rx { inner } => is_fully_supported(inner),
454 ShapeKind::List { element }
455 | ShapeKind::Option { inner: element }
456 | ShapeKind::Set { element }
457 | ShapeKind::Array { element, .. }
458 | ShapeKind::Slice { element } => is_fully_supported(element),
459 ShapeKind::Map { key, value } => is_fully_supported(key) && is_fully_supported(value),
460 ShapeKind::Tuple { elements } => elements.iter().all(|p| is_fully_supported(p.shape)),
461 ShapeKind::TupleStruct { fields } => fields.iter().all(|f| is_fully_supported(f.shape())),
462 ShapeKind::Struct(StructInfo { fields, .. }) => {
463 fields.iter().all(|f| is_fully_supported(f.shape()))
464 }
465 ShapeKind::Enum(EnumInfo { variants, .. }) => {
466 variants.iter().all(|v| match classify_variant(v) {
467 VariantKind::Unit => true,
468 VariantKind::Newtype { inner } => is_fully_supported(inner),
469 VariantKind::Tuple { fields } | VariantKind::Struct { fields } => {
470 fields.iter().all(|f| is_fully_supported(f.shape()))
471 }
472 })
473 }
474 ShapeKind::Pointer { pointee } => is_fully_supported(pointee),
475 ShapeKind::Scalar(_) => true,
476 ShapeKind::Result { ok, err } => is_fully_supported(ok) && is_fully_supported(err),
477 ShapeKind::Opaque => false,
478 }
479}