Skip to main content

oag_node_client/emitters/
types.rs

1use minijinja::{Environment, context};
2use oag_core::ir::{IrObjectSchema, IrReturnType, IrSchema, IrSpec};
3
4use crate::type_mapper::ir_type_to_ts;
5
6/// Escape `*/` sequences that would prematurely close JSDoc comment blocks.
7fn escape_jsdoc(value: String) -> String {
8    value.replace("*/", "*\\/")
9}
10
11/// Emit `types.ts` containing all interfaces, enums, aliases, and SSE event union types.
12pub fn emit_types(ir: &IrSpec) -> String {
13    let mut env = Environment::new();
14    env.add_filter("escape_jsdoc", escape_jsdoc);
15    env.add_template("types.ts.j2", include_str!("../../templates/types.ts.j2"))
16        .expect("template should be valid");
17    let tmpl = env.get_template("types.ts.j2").unwrap();
18
19    let schemas: Vec<_> = ir.schemas.iter().map(schema_to_ctx).collect();
20    let sse_event_types = collect_sse_event_types(ir);
21
22    tmpl.render(context! {
23        schemas => schemas,
24        sse_event_types => sse_event_types,
25    })
26    .expect("render should succeed")
27}
28
29fn schema_to_ctx(schema: &IrSchema) -> minijinja::Value {
30    match schema {
31        IrSchema::Object(obj) => object_to_ctx(obj),
32        IrSchema::Enum(e) => {
33            let variants: Vec<String> = e.variants.iter().map(|v| format!("\"{v}\"")).collect();
34            context! {
35                kind => "enum",
36                name => e.name.pascal_case.clone(),
37                description => e.description.clone(),
38                variants => variants,
39            }
40        }
41        IrSchema::Alias(a) => {
42            context! {
43                kind => "alias",
44                name => a.name.pascal_case.clone(),
45                description => a.description.clone(),
46                target => ir_type_to_ts(&a.target),
47            }
48        }
49        IrSchema::Union(u) => {
50            let variants: Vec<String> = u.variants.iter().map(ir_type_to_ts).collect();
51            context! {
52                kind => "union",
53                name => u.name.pascal_case.clone(),
54                description => u.description.clone(),
55                variants => variants,
56            }
57        }
58    }
59}
60
61fn object_to_ctx(obj: &IrObjectSchema) -> minijinja::Value {
62    let fields: Vec<minijinja::Value> = obj
63        .fields
64        .iter()
65        .map(|f| {
66            context! {
67                name => f.name.camel_case.clone(),
68                original_name => f.original_name.clone(),
69                type => ir_type_to_ts(&f.field_type),
70                required => f.required,
71                description => f.description.clone(),
72            }
73        })
74        .collect();
75
76    let additional = obj.additional_properties.as_ref().map(ir_type_to_ts);
77
78    context! {
79        kind => "object",
80        name => obj.name.pascal_case.clone(),
81        description => obj.description.clone(),
82        fields => fields,
83        additional_properties => additional,
84    }
85}
86
87fn collect_sse_event_types(ir: &IrSpec) -> Vec<minijinja::Value> {
88    let mut event_types = Vec::new();
89    for op in &ir.operations {
90        if let IrReturnType::Sse(sse) = &op.return_type
91            && let Some(ref event_name) = sse.event_type_name
92        {
93            let variants: Vec<String> = sse.variants.iter().map(ir_type_to_ts).collect();
94            if !variants.is_empty() {
95                event_types.push(context! {
96                    name => event_name.clone(),
97                    variants => variants,
98                });
99            }
100        }
101    }
102    event_types
103}