1use anyhow::{Context, Result};
4use heck::ToSnakeCase;
5use std::collections::HashMap;
6use std::fs;
7use std::path::Path;
8use tera::Context as TeraContext;
9
10use crate::generator::TEMPLATES;
11use crate::python::generator::{
12 generate_enums_init, generate_errors_init, generate_internal_init, generate_models_init,
13 generate_package_init, generate_transaction_init,
14};
15
16pub fn write_rust_code(
28 output_path: &str,
29 models: &HashMap<String, String>,
30 enums_code: Option<String>,
31 composite_types_code: Option<String>,
32 schema_source: &str,
33 standalone: bool,
34) -> Result<()> {
35 let output_dir = Path::new(output_path);
36
37 clear_output_dir(output_path)?;
38
39 fs::create_dir_all(output_dir)
40 .with_context(|| format!("Failed to create directory: {}", output_dir.display()))?;
41
42 let src_dir = output_dir.join("src");
43 fs::create_dir_all(&src_dir)
44 .with_context(|| format!("Failed to create src directory: {}", src_dir.display()))?;
45
46 for (model_name, code) in models {
47 let file_name = format!("{}.rs", model_name.to_snake_case());
48 let file_path = src_dir.join(&file_name);
49
50 fs::write(&file_path, code)
51 .with_context(|| format!("Failed to write file: {}", file_path.display()))?;
52 }
53
54 let has_enums = enums_code.is_some();
55 if let Some(enums_code) = enums_code {
56 let enums_path = src_dir.join("enums.rs");
57 fs::write(&enums_path, enums_code)
58 .with_context(|| format!("Failed to write enums file: {}", enums_path.display()))?;
59 }
60
61 let has_composite_types = composite_types_code.is_some();
62 if let Some(types_code) = composite_types_code {
63 let types_path = src_dir.join("types.rs");
64 fs::write(&types_path, types_code)
65 .with_context(|| format!("Failed to write types file: {}", types_path.display()))?;
66 }
67
68 let lib_content = generate_lib_rs(models, has_enums, has_composite_types, schema_source)?;
69 let lib_path = src_dir.join("lib.rs");
70 fs::write(&lib_path, lib_content)
71 .with_context(|| format!("Failed to write lib.rs: {}", lib_path.display()))?;
72
73 let runtime_path = src_dir.join("runtime.rs");
74 fs::write(
75 &runtime_path,
76 include_str!("../templates/rust/runtime.rs.tpl"),
77 )
78 .with_context(|| format!("Failed to write runtime.rs: {}", runtime_path.display()))?;
79
80 if standalone {
81 let abs_output = if output_dir.is_absolute() {
85 output_dir.to_path_buf()
86 } else {
87 std::env::current_dir()
88 .context("Failed to get current directory")?
89 .join(output_dir)
90 };
91 let workspace_root_path = {
92 let workspace_toml = crate::find_workspace_cargo_toml(&abs_output);
93 match workspace_toml {
94 Some(toml_path) => {
95 let workspace_dir = toml_path.parent().unwrap();
96 let mut up = std::path::PathBuf::new();
97 let mut candidate = abs_output.clone();
98 loop {
99 if candidate == workspace_dir {
100 break;
101 }
102 up.push("..");
103 match candidate.parent() {
104 Some(p) => candidate = p.to_path_buf(),
105 None => break,
106 }
107 }
108 up.to_string_lossy().replace('\\', "/")
109 }
110 None => {
111 let mut up = std::path::PathBuf::new();
113 let mut candidate = abs_output.clone();
114 loop {
115 candidate = match candidate.parent() {
116 Some(p) => p.to_path_buf(),
117 None => break,
118 };
119 up.push("..");
120 let cargo_toml = candidate.join("Cargo.toml");
121 let Ok(txt) = fs::read_to_string(&cargo_toml) else {
122 continue;
123 };
124 if txt.contains("[workspace]") {
125 break;
126 }
127 }
128 up.to_string_lossy().replace('\\', "/")
129 }
130 }
131 };
132 let cargo_toml_content = generate_rust_cargo_toml(&workspace_root_path);
133 let cargo_toml_path = output_dir.join("Cargo.toml");
134 fs::write(&cargo_toml_path, cargo_toml_content).with_context(|| {
135 format!("Failed to write Cargo.toml: {}", cargo_toml_path.display())
136 })?;
137 }
138
139 Ok(())
140}
141
142fn generate_rust_cargo_toml(workspace_root_path: &str) -> String {
148 include_str!("../templates/rust/Cargo.toml.tpl")
149 .replace("{{ workspace_root_path }}", workspace_root_path)
150}
151
152fn generate_lib_rs(
154 models: &HashMap<String, String>,
155 has_enums: bool,
156 has_composite_types: bool,
157 schema_source: &str,
158) -> Result<String> {
159 let mut model_names: Vec<_> = models.keys().cloned().collect();
160 model_names.sort();
161
162 let model_modules: Vec<String> = model_names
163 .iter()
164 .map(|model_name| model_name.to_snake_case())
165 .collect();
166
167 let mut context = TeraContext::new();
168 context.insert("has_enums", &has_enums);
169 context.insert("has_composite_types", &has_composite_types);
170 context.insert("model_modules", &model_modules);
171 context.insert("schema_source_literal", &format!("{:?}", schema_source));
172
173 TEMPLATES
174 .render("lib_rs.tera", &context)
175 .context("Failed to render lib.rs template")
176}
177
178pub fn write_python_code(
192 output_path: &str,
193 models: &[(String, String)],
194 enums_code: Option<String>,
195 composite_types_code: Option<String>,
196 client_code: Option<String>,
197 runtime_files: &[(&str, &str)],
198) -> Result<()> {
199 let output_dir = Path::new(output_path);
200
201 clear_output_dir(output_path)?;
202
203 fs::create_dir_all(output_dir)
204 .with_context(|| format!("Failed to create directory: {}", output_dir.display()))?;
205
206 let models_dir = output_dir.join("models");
207 fs::create_dir_all(&models_dir).with_context(|| {
208 format!(
209 "Failed to create models directory: {}",
210 models_dir.display()
211 )
212 })?;
213
214 let enums_dir = output_dir.join("enums");
215 fs::create_dir_all(&enums_dir)
216 .with_context(|| format!("Failed to create enums directory: {}", enums_dir.display()))?;
217
218 let errors_dir = output_dir.join("errors");
219 fs::create_dir_all(&errors_dir).with_context(|| {
220 format!(
221 "Failed to create errors directory: {}",
222 errors_dir.display()
223 )
224 })?;
225
226 let internal_dir = output_dir.join("_internal");
227 fs::create_dir_all(&internal_dir).with_context(|| {
228 format!(
229 "Failed to create _internal directory: {}",
230 internal_dir.display()
231 )
232 })?;
233
234 for (file_name, code) in models {
235 let file_path = models_dir.join(file_name);
236
237 fs::write(&file_path, code)
238 .with_context(|| format!("Failed to write file: {}", file_path.display()))?;
239 }
240
241 let models_init = generate_models_init(models);
242 let models_init_path = models_dir.join("__init__.py");
243 fs::write(&models_init_path, models_init)
244 .with_context(|| "Failed to write models/__init__.py")?;
245
246 if let Some(types_code) = composite_types_code {
247 let types_dir = output_dir.join("types");
248 fs::create_dir_all(&types_dir).with_context(|| {
249 format!("Failed to create types directory: {}", types_dir.display())
250 })?;
251
252 let types_path = types_dir.join("types.py");
253 fs::write(&types_path, types_code)
254 .with_context(|| format!("Failed to write types file: {}", types_path.display()))?;
255
256 let types_init = "from .types import * # noqa: F401, F403\n";
257 let types_init_path = types_dir.join("__init__.py");
258 fs::write(&types_init_path, types_init)
259 .with_context(|| "Failed to write types/__init__.py")?;
260 }
261
262 let has_enums = enums_code.is_some();
263 if let Some(enums_code) = enums_code {
264 let enums_path = enums_dir.join("enums.py");
265 fs::write(&enums_path, enums_code)
266 .with_context(|| format!("Failed to write enums file: {}", enums_path.display()))?;
267 }
268
269 let enums_init = generate_enums_init(has_enums);
270 let enums_init_path = enums_dir.join("__init__.py");
271 fs::write(&enums_init_path, enums_init).with_context(|| "Failed to write enums/__init__.py")?;
272
273 for (file_name, content) in runtime_files {
274 let (target_dir, new_name) = match *file_name {
275 "_errors.py" => (&errors_dir, "errors.py"),
276 _ => (&internal_dir, file_name.trim_start_matches('_')),
277 };
278
279 let file_path = target_dir.join(new_name);
280 fs::write(&file_path, content)
281 .with_context(|| format!("Failed to write runtime file: {}", file_path.display()))?;
282 }
283
284 let errors_init = generate_errors_init();
285 let errors_init_path = errors_dir.join("__init__.py");
286 fs::write(&errors_init_path, errors_init)
287 .with_context(|| "Failed to write errors/__init__.py")?;
288
289 let internal_init = generate_internal_init();
290 let internal_init_path = internal_dir.join("__init__.py");
291 fs::write(&internal_init_path, internal_init)
292 .with_context(|| "Failed to write _internal/__init__.py")?;
293
294 if let Some(client_code) = client_code {
295 let client_path = output_dir.join("client.py");
296 fs::write(&client_path, client_code)
297 .with_context(|| format!("Failed to write client.py: {}", client_path.display()))?;
298 }
299
300 let transaction_content = generate_transaction_init();
301 let transaction_path = output_dir.join("transaction.py");
302 fs::write(&transaction_path, transaction_content).with_context(|| {
303 format!(
304 "Failed to write transaction.py: {}",
305 transaction_path.display()
306 )
307 })?;
308
309 let init_content = generate_package_init(has_enums);
310 let init_path = output_dir.join("__init__.py");
311 fs::write(&init_path, init_content)
312 .with_context(|| format!("Failed to write __init__.py: {}", init_path.display()))?;
313
314 let py_typed_path = output_dir.join("py.typed");
315 fs::write(&py_typed_path, "")
316 .with_context(|| format!("Failed to write py.typed: {}", py_typed_path.display()))?;
317
318 Ok(())
319}
320
321#[allow(clippy::too_many_arguments)]
336pub fn write_js_code(
337 output_path: &str,
338 js_models: &[(String, String)],
339 dts_models: &[(String, String)],
340 js_enums: Option<String>,
341 dts_enums: Option<String>,
342 dts_composite_types: Option<String>,
343 js_client: Option<String>,
344 dts_client: Option<String>,
345 js_models_index: Option<String>,
346 dts_models_index: Option<String>,
347 runtime_files: &[(&str, &str)],
348) -> Result<()> {
349 let output_dir = Path::new(output_path);
350
351 clear_output_dir(output_path)?;
352
353 fs::create_dir_all(output_dir)
354 .with_context(|| format!("Failed to create directory: {}", output_dir.display()))?;
355
356 let models_dir = output_dir.join("models");
357 fs::create_dir_all(&models_dir)?;
358
359 let internal_dir = output_dir.join("_internal");
360 fs::create_dir_all(&internal_dir)?;
361
362 for (file_name, code) in js_models {
363 let file_path = models_dir.join(file_name);
364 fs::write(&file_path, code)
365 .with_context(|| format!("Failed to write file: {}", file_path.display()))?;
366 }
367
368 for (file_name, code) in dts_models {
369 let file_path = models_dir.join(file_name);
370 fs::write(&file_path, code)
371 .with_context(|| format!("Failed to write file: {}", file_path.display()))?;
372 }
373
374 if let Some(index_js) = js_models_index {
375 let path = models_dir.join("index.js");
376 fs::write(&path, index_js).with_context(|| "Failed to write models/index.js")?;
377 }
378 if let Some(index_dts) = dts_models_index {
379 let path = models_dir.join("index.d.ts");
380 fs::write(&path, index_dts).with_context(|| "Failed to write models/index.d.ts")?;
381 }
382
383 if let Some(enums_js) = js_enums {
384 let path = output_dir.join("enums.js");
385 fs::write(&path, enums_js)
386 .with_context(|| format!("Failed to write enums.js: {}", output_dir.display()))?;
387 }
388 if let Some(enums_dts) = dts_enums {
389 let path = output_dir.join("enums.d.ts");
390 fs::write(&path, enums_dts)
391 .with_context(|| format!("Failed to write enums.d.ts: {}", output_dir.display()))?;
392 }
393
394 if let Some(types_dts) = dts_composite_types {
396 let path = output_dir.join("types.d.ts");
397 fs::write(&path, types_dts)
398 .with_context(|| format!("Failed to write types.d.ts: {}", output_dir.display()))?;
399 }
400
401 for (file_name, content) in runtime_files {
402 let file_path = internal_dir.join(file_name);
403 fs::write(&file_path, content)
404 .with_context(|| format!("Failed to write runtime file: {}", file_path.display()))?;
405 }
406
407 if let Some(client_js) = js_client {
408 let path = output_dir.join("index.js");
409 fs::write(&path, client_js)
410 .with_context(|| format!("Failed to write index.js: {}", output_dir.display()))?;
411 }
412 if let Some(client_dts) = dts_client {
413 let path = output_dir.join("index.d.ts");
414 fs::write(&path, client_dts)
415 .with_context(|| format!("Failed to write index.d.ts: {}", output_dir.display()))?;
416 }
417
418 Ok(())
419}
420
421fn clear_output_dir(output_path: &str) -> Result<()> {
422 let output_dir = Path::new(output_path);
423 if output_dir.exists() {
424 fs::remove_dir_all(output_dir).with_context(|| {
425 format!("Failed to clean output directory: {}", output_dir.display())
426 })?;
427 }
428 Ok(())
429}