vexil_codegen_go/
backend.rs1use std::collections::{BTreeMap, HashMap, HashSet};
2use std::path::PathBuf;
3
4use vexil_lang::codegen::{CodegenBackend, CodegenError};
5use vexil_lang::ir::{CompiledSchema, ResolvedType, TypeDef, TypeId};
6use vexil_lang::project::ProjectResult;
7
8#[derive(Debug, Clone, Copy)]
14pub struct GoBackend;
15
16impl CodegenBackend for GoBackend {
17 fn name(&self) -> &str {
18 "go"
19 }
20
21 fn file_extension(&self) -> &str {
22 "go"
23 }
24
25 fn generate(&self, compiled: &CompiledSchema) -> Result<String, CodegenError> {
26 crate::generate(compiled).map_err(|e| CodegenError::BackendSpecific(Box::new(e)))
27 }
28
29 fn generate_project(
30 &self,
31 result: &ProjectResult,
32 ) -> Result<BTreeMap<PathBuf, String>, CodegenError> {
33 let mut files = BTreeMap::new();
34
35 let mut global_type_map: HashMap<String, String> = HashMap::new();
37 for (ns, compiled) in &result.schemas {
38 let segments: Vec<&str> = ns.split('.').collect();
39 let go_pkg = segments.join("/");
40 for &type_id in &compiled.declarations {
41 if let Some(typedef) = compiled.registry.get(type_id) {
42 let name = crate::type_name_of(typedef);
43 global_type_map.insert(name.to_string(), go_pkg.clone());
44 }
45 }
46 }
47
48 for (ns, compiled) in &result.schemas {
49 let segments: Vec<&str> = ns.split('.').collect();
50 if segments.is_empty() {
51 continue;
52 }
53 let file_name = segments[segments.len() - 1];
54 let dir_segments = &segments[..segments.len() - 1];
55
56 let declared_ids: HashSet<TypeId> = compiled.declarations.iter().copied().collect();
58
59 let mut import_types: HashMap<String, String> = HashMap::new();
60 for &type_id in &compiled.declarations {
61 if let Some(typedef) = compiled.registry.get(type_id) {
62 collect_named_ids_from_typedef(typedef, &declared_ids, |imported_id| {
63 if let Some(imported_def) = compiled.registry.get(imported_id) {
64 let name = crate::type_name_of(imported_def);
65 if let Some(go_path) = global_type_map.get(name) {
66 import_types.insert(name.to_string(), go_path.clone());
67 }
68 }
69 });
70 }
71 }
72
73 let imports = if import_types.is_empty() {
75 None
76 } else {
77 Some(&import_types)
78 };
79 let code = crate::generate_with_imports(compiled, imports)
80 .map_err(|e| CodegenError::BackendSpecific(Box::new(e)))?;
81
82 let mut file_path = PathBuf::new();
83 for seg in dir_segments {
84 file_path.push(seg);
85 }
86 file_path.push(format!("{file_name}.go"));
87 files.insert(file_path, code);
88 }
89
90 Ok(files)
93 }
94}
95
96fn collect_named_ids_from_typedef(
99 typedef: &TypeDef,
100 declared: &HashSet<TypeId>,
101 mut on_import: impl FnMut(TypeId),
102) {
103 match typedef {
104 TypeDef::Message(msg) => {
105 for f in &msg.fields {
106 collect_named_ids_from_resolved(&f.resolved_type, declared, &mut on_import);
107 }
108 }
109 TypeDef::Union(un) => {
110 for v in &un.variants {
111 for f in &v.fields {
112 collect_named_ids_from_resolved(&f.resolved_type, declared, &mut on_import);
113 }
114 }
115 }
116 TypeDef::Newtype(nt) => {
117 collect_named_ids_from_resolved(&nt.inner_type, declared, &mut on_import);
118 }
119 TypeDef::Config(cfg) => {
120 for f in &cfg.fields {
121 collect_named_ids_from_resolved(&f.resolved_type, declared, &mut on_import);
122 }
123 }
124 _ => {}
125 }
126}
127
128fn collect_named_ids_from_resolved(
129 ty: &ResolvedType,
130 declared: &HashSet<TypeId>,
131 on_import: &mut impl FnMut(TypeId),
132) {
133 match ty {
134 ResolvedType::Named(id) => {
135 if !declared.contains(id) {
136 on_import(*id);
137 }
138 }
139 ResolvedType::Optional(inner) | ResolvedType::Array(inner) => {
140 collect_named_ids_from_resolved(inner, declared, on_import);
141 }
142 ResolvedType::Map(k, v) | ResolvedType::Result(k, v) => {
143 collect_named_ids_from_resolved(k, declared, on_import);
144 collect_named_ids_from_resolved(v, declared, on_import);
145 }
146 _ => {}
147 }
148}