Skip to main content

oag_node_client/
type_mapper.rs

1use oag_core::ir::IrType;
2
3/// Map an `IrType` to its TypeScript type string representation.
4pub fn ir_type_to_ts(ir_type: &IrType) -> String {
5    match ir_type {
6        IrType::String => "string".to_string(),
7        IrType::StringLiteral(s) => format!("\"{s}\""),
8        IrType::Number => "number".to_string(),
9        IrType::Integer => "number".to_string(),
10        IrType::Boolean => "boolean".to_string(),
11        IrType::Null => "null".to_string(),
12        IrType::DateTime => "string".to_string(),
13        IrType::Binary => "Blob".to_string(),
14        IrType::Any => "unknown".to_string(),
15        IrType::Void => "void".to_string(),
16        IrType::Ref(name) => name.clone(),
17        IrType::Array(inner) => {
18            let inner_ts = ir_type_to_ts(inner);
19            if inner_ts.contains('|') {
20                format!("({inner_ts})[]")
21            } else {
22                format!("{inner_ts}[]")
23            }
24        }
25        IrType::Map(value_type) => {
26            let value_ts = ir_type_to_ts(value_type);
27            format!("Record<string, {value_ts}>")
28        }
29        IrType::Object(fields) => {
30            if fields.is_empty() {
31                return "Record<string, unknown>".to_string();
32            }
33            let field_strs: Vec<String> = fields
34                .iter()
35                .map(|(name, ty, required)| {
36                    let ts_type = ir_type_to_ts(ty);
37                    if *required {
38                        format!("{name}: {ts_type}")
39                    } else {
40                        format!("{name}?: {ts_type}")
41                    }
42                })
43                .collect();
44            format!("{{ {} }}", field_strs.join("; "))
45        }
46        IrType::Union(variants) => {
47            let variant_strs: Vec<String> = variants.iter().map(ir_type_to_ts).collect();
48            variant_strs.join(" | ")
49        }
50        IrType::Intersection(parts) => {
51            let part_strs: Vec<String> = parts.iter().map(ir_type_to_ts).collect();
52            part_strs.join(" & ")
53        }
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    #[test]
62    fn test_primitives() {
63        assert_eq!(ir_type_to_ts(&IrType::String), "string");
64        assert_eq!(ir_type_to_ts(&IrType::Number), "number");
65        assert_eq!(ir_type_to_ts(&IrType::Integer), "number");
66        assert_eq!(ir_type_to_ts(&IrType::Boolean), "boolean");
67        assert_eq!(ir_type_to_ts(&IrType::Null), "null");
68        assert_eq!(ir_type_to_ts(&IrType::Any), "unknown");
69        assert_eq!(ir_type_to_ts(&IrType::Void), "void");
70    }
71
72    #[test]
73    fn test_array() {
74        assert_eq!(
75            ir_type_to_ts(&IrType::Array(Box::new(IrType::String))),
76            "string[]"
77        );
78        assert_eq!(
79            ir_type_to_ts(&IrType::Array(Box::new(IrType::Union(vec![
80                IrType::String,
81                IrType::Number,
82            ])))),
83            "(string | number)[]"
84        );
85    }
86
87    #[test]
88    fn test_map() {
89        assert_eq!(
90            ir_type_to_ts(&IrType::Map(Box::new(IrType::String))),
91            "Record<string, string>"
92        );
93    }
94
95    #[test]
96    fn test_ref() {
97        assert_eq!(ir_type_to_ts(&IrType::Ref("Pet".to_string())), "Pet");
98    }
99
100    #[test]
101    fn test_union() {
102        assert_eq!(
103            ir_type_to_ts(&IrType::Union(vec![IrType::String, IrType::Number])),
104            "string | number"
105        );
106    }
107}