Skip to main content

oag_node_client/emitters/
types.rs

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