1mod templates;
9
10use hemmer_provider_generator_common::{
11 GeneratorError, ProviderDefinition, Result, ServiceDefinition,
12};
13use std::fs;
14use std::path::Path;
15use tera::Tera;
16
17pub struct ProviderGenerator {
27 service_def: ServiceDefinition,
28 tera: Tera,
29}
30
31impl ProviderGenerator {
32 pub fn new(service_def: ServiceDefinition) -> Result<Self> {
34 let tera = templates::load_templates()?;
35 Ok(Self { service_def, tera })
36 }
37
38 pub fn generate_to_directory(&self, output_dir: &Path) -> Result<()> {
40 fs::create_dir_all(output_dir).map_err(|e| {
42 GeneratorError::Generation(format!("Failed to create output directory: {}", e))
43 })?;
44
45 let src_dir = output_dir.join("src");
46 fs::create_dir_all(&src_dir).map_err(|e| {
47 GeneratorError::Generation(format!("Failed to create src directory: {}", e))
48 })?;
49
50 let resources_dir = src_dir.join("resources");
51 fs::create_dir_all(&resources_dir).map_err(|e| {
52 GeneratorError::Generation(format!("Failed to create resources directory: {}", e))
53 })?;
54
55 self.generate_provider_jcf(output_dir)?;
57 self.generate_cargo_toml(output_dir)?;
58 self.generate_main_rs(&src_dir)?;
59 self.generate_lib_rs(&src_dir)?;
60 self.generate_resources(&resources_dir)?;
61 self.generate_readme(output_dir)?;
62
63 Ok(())
64 }
65
66 fn generate_provider_jcf(&self, output_dir: &Path) -> Result<()> {
68 let context = self.create_context();
69 let rendered = self
70 .tera
71 .render("provider.jcf", &context)
72 .map_err(|e| GeneratorError::Generation(format!("Template error: {:?}", e)))?;
73
74 let output_path = output_dir.join("provider.jcf");
75 fs::write(output_path, rendered).map_err(|e| {
76 GeneratorError::Generation(format!("Failed to write provider.jcf: {}", e))
77 })?;
78
79 Ok(())
80 }
81
82 fn generate_main_rs(&self, src_dir: &Path) -> Result<()> {
84 let context = self.create_context();
85 let rendered = self
86 .tera
87 .render("main.rs", &context)
88 .map_err(|e| GeneratorError::Generation(format!("Template error: {:?}", e)))?;
89
90 let output_path = src_dir.join("main.rs");
91 fs::write(output_path, rendered)
92 .map_err(|e| GeneratorError::Generation(format!("Failed to write main.rs: {}", e)))?;
93
94 Ok(())
95 }
96
97 fn generate_cargo_toml(&self, output_dir: &Path) -> Result<()> {
99 let context = self.create_context();
100 let rendered = self
101 .tera
102 .render("Cargo.toml", &context)
103 .map_err(|e| GeneratorError::Generation(format!("Template error: {}", e)))?;
104
105 let output_path = output_dir.join("Cargo.toml");
106 fs::write(output_path, rendered).map_err(|e| {
107 GeneratorError::Generation(format!("Failed to write Cargo.toml: {}", e))
108 })?;
109
110 Ok(())
111 }
112
113 fn generate_lib_rs(&self, src_dir: &Path) -> Result<()> {
115 let context = self.create_context();
116 let rendered = self
117 .tera
118 .render("lib.rs", &context)
119 .map_err(|e| GeneratorError::Generation(format!("Template error: {}", e)))?;
120
121 let output_path = src_dir.join("lib.rs");
122 fs::write(output_path, rendered)
123 .map_err(|e| GeneratorError::Generation(format!("Failed to write lib.rs: {}", e)))?;
124
125 Ok(())
126 }
127
128 fn generate_resources(&self, resources_dir: &Path) -> Result<()> {
130 for resource in &self.service_def.resources {
131 let mut context = self.create_context();
132 context.insert("resource", resource);
133
134 let rendered = self
135 .tera
136 .render("resource.rs", &context)
137 .map_err(|e| GeneratorError::Generation(format!("Template error: {}", e)))?;
138
139 let output_path = resources_dir.join(format!("{}.rs", resource.name));
140 fs::write(output_path, rendered).map_err(|e| {
141 GeneratorError::Generation(format!(
142 "Failed to write resource {}.rs: {}",
143 resource.name, e
144 ))
145 })?;
146 }
147
148 let mut context = self.create_context();
150 let resource_names: Vec<&str> = self
151 .service_def
152 .resources
153 .iter()
154 .map(|r| r.name.as_str())
155 .collect();
156 context.insert("resource_names", &resource_names);
157
158 let rendered = self
159 .tera
160 .render("resources_mod.rs", &context)
161 .map_err(|e| GeneratorError::Generation(format!("Template error: {}", e)))?;
162
163 let output_path = resources_dir.join("mod.rs");
164 fs::write(output_path, rendered).map_err(|e| {
165 GeneratorError::Generation(format!("Failed to write resources/mod.rs: {}", e))
166 })?;
167
168 Ok(())
169 }
170
171 fn generate_readme(&self, output_dir: &Path) -> Result<()> {
173 let context = self.create_context();
174 let rendered = self
175 .tera
176 .render("README.md", &context)
177 .map_err(|e| GeneratorError::Generation(format!("Template error: {}", e)))?;
178
179 let output_path = output_dir.join("README.md");
180 fs::write(output_path, rendered)
181 .map_err(|e| GeneratorError::Generation(format!("Failed to write README.md: {}", e)))?;
182
183 Ok(())
184 }
185
186 fn create_context(&self) -> tera::Context {
188 let mut context = tera::Context::new();
189 context.insert("service", &self.service_def);
190 context.insert("provider", &format!("{:?}", self.service_def.provider));
191 context.insert("service_name", &self.service_def.name);
192 context.insert("sdk_version", &self.service_def.sdk_version);
193 context.insert("resources", &self.service_def.resources);
194 context.insert("data_sources", &self.service_def.data_sources);
195
196 let sdk_config = self.service_def.provider.sdk_config();
198 context.insert("sdk_config", &sdk_config);
199 context.insert("config_attrs", &sdk_config.config_attrs);
200 context.insert(
201 "uses_shared_client",
202 &self.service_def.provider.uses_shared_client(),
203 );
204
205 context
206 }
207}
208
209pub fn generate_provider(service_def: ServiceDefinition, output_path: &str) -> Result<()> {
211 let generator = ProviderGenerator::new(service_def)?;
212 generator.generate_to_directory(Path::new(output_path))
213}
214
215pub struct UnifiedProviderGenerator {
226 provider_def: ProviderDefinition,
227 tera: Tera,
228}
229
230impl UnifiedProviderGenerator {
231 pub fn new(provider_def: ProviderDefinition) -> Result<Self> {
233 let tera = templates::load_unified_templates()?;
234 Ok(Self { provider_def, tera })
235 }
236
237 pub fn generate_to_directory(&self, output_dir: &Path) -> Result<()> {
239 fs::create_dir_all(output_dir).map_err(|e| {
241 GeneratorError::Generation(format!("Failed to create output directory: {}", e))
242 })?;
243
244 let src_dir = output_dir.join("src");
245 fs::create_dir_all(&src_dir).map_err(|e| {
246 GeneratorError::Generation(format!("Failed to create src directory: {}", e))
247 })?;
248
249 self.generate_unified_provider_jcf(output_dir)?;
251 self.generate_unified_cargo_toml(output_dir)?;
252 self.generate_unified_main_rs(&src_dir)?;
253 self.generate_unified_lib_rs(&src_dir)?;
254 self.generate_unified_readme(output_dir)?;
255 self.generate_release_workflow(output_dir)?;
256 self.generate_docs(output_dir)?;
257
258 for service in &self.provider_def.services {
260 let service_dir = src_dir.join(&service.name);
261 fs::create_dir_all(&service_dir).map_err(|e| {
262 GeneratorError::Generation(format!(
263 "Failed to create service directory {}: {}",
264 service.name, e
265 ))
266 })?;
267
268 let resources_dir = service_dir.join("resources");
269 fs::create_dir_all(&resources_dir).map_err(|e| {
270 GeneratorError::Generation(format!(
271 "Failed to create resources directory for {}: {}",
272 service.name, e
273 ))
274 })?;
275
276 self.generate_service_mod(&service_dir, service)?;
277 self.generate_service_resources(&resources_dir, service)?;
278 }
279
280 Ok(())
281 }
282
283 fn generate_unified_provider_jcf(&self, output_dir: &Path) -> Result<()> {
285 let context = self.create_unified_context();
286 let rendered = self
287 .tera
288 .render("unified_provider.jcf", &context)
289 .map_err(|e| GeneratorError::Generation(format!("Template error: {:?}", e)))?;
290
291 let output_path = output_dir.join("provider.jcf");
292 fs::write(output_path, rendered).map_err(|e| {
293 GeneratorError::Generation(format!("Failed to write provider.jcf: {}", e))
294 })?;
295
296 Ok(())
297 }
298
299 fn generate_unified_main_rs(&self, src_dir: &Path) -> Result<()> {
301 let context = self.create_unified_context();
302 let rendered = self
303 .tera
304 .render("unified_main.rs", &context)
305 .map_err(|e| GeneratorError::Generation(format!("Template error: {:?}", e)))?;
306
307 let output_path = src_dir.join("main.rs");
308 fs::write(output_path, rendered)
309 .map_err(|e| GeneratorError::Generation(format!("Failed to write main.rs: {}", e)))?;
310
311 Ok(())
312 }
313
314 fn generate_unified_cargo_toml(&self, output_dir: &Path) -> Result<()> {
316 let context = self.create_unified_context();
317 let rendered = self
318 .tera
319 .render("unified_Cargo.toml", &context)
320 .map_err(|e| GeneratorError::Generation(format!("Template error: {}", e)))?;
321
322 let output_path = output_dir.join("Cargo.toml");
323 fs::write(output_path, rendered).map_err(|e| {
324 GeneratorError::Generation(format!("Failed to write Cargo.toml: {}", e))
325 })?;
326
327 Ok(())
328 }
329
330 fn generate_unified_lib_rs(&self, src_dir: &Path) -> Result<()> {
332 let context = self.create_unified_context();
333 let rendered = self
334 .tera
335 .render("unified_lib.rs", &context)
336 .map_err(|e| GeneratorError::Generation(format!("Template error: {}", e)))?;
337
338 let output_path = src_dir.join("lib.rs");
339 fs::write(output_path, rendered)
340 .map_err(|e| GeneratorError::Generation(format!("Failed to write lib.rs: {}", e)))?;
341
342 Ok(())
343 }
344
345 fn generate_service_mod(&self, service_dir: &Path, service: &ServiceDefinition) -> Result<()> {
347 let mut context = self.create_unified_context();
348 context.insert("service", service);
349
350 let rendered = self
351 .tera
352 .render("unified_service.rs", &context)
353 .map_err(|e| GeneratorError::Generation(format!("Template error: {}", e)))?;
354
355 let output_path = service_dir.join("mod.rs");
356 fs::write(output_path, rendered).map_err(|e| {
357 GeneratorError::Generation(format!("Failed to write {}/mod.rs: {}", service.name, e))
358 })?;
359
360 Ok(())
361 }
362
363 fn generate_service_resources(
365 &self,
366 resources_dir: &Path,
367 service: &ServiceDefinition,
368 ) -> Result<()> {
369 for resource in &service.resources {
371 let mut context = self.create_unified_context();
372 context.insert("service", service);
373 context.insert("service_name", &service.name);
374 context.insert("resource", resource);
375
376 let rendered = self
377 .tera
378 .render("unified_resource.rs", &context)
379 .map_err(|e| GeneratorError::Generation(format!("Template error: {}", e)))?;
380
381 let output_path = resources_dir.join(format!("{}.rs", resource.name));
382 fs::write(output_path, rendered).map_err(|e| {
383 GeneratorError::Generation(format!(
384 "Failed to write resource {}.rs: {}",
385 resource.name, e
386 ))
387 })?;
388 }
389
390 let mut context = self.create_unified_context();
392 let resource_names: Vec<&str> = service.resources.iter().map(|r| r.name.as_str()).collect();
393 context.insert("resource_names", &resource_names);
394 context.insert("is_unified", &true);
395
396 let rendered = self
397 .tera
398 .render("resources_mod.rs", &context)
399 .map_err(|e| GeneratorError::Generation(format!("Template error: {}", e)))?;
400
401 let output_path = resources_dir.join("mod.rs");
402 fs::write(output_path, rendered).map_err(|e| {
403 GeneratorError::Generation(format!(
404 "Failed to write {}/resources/mod.rs: {}",
405 service.name, e
406 ))
407 })?;
408
409 Ok(())
410 }
411
412 fn generate_unified_readme(&self, output_dir: &Path) -> Result<()> {
414 let context = self.create_unified_context();
415 let rendered = self
416 .tera
417 .render("unified_README.md", &context)
418 .map_err(|e| GeneratorError::Generation(format!("Template error: {}", e)))?;
419
420 let output_path = output_dir.join("README.md");
421 fs::write(output_path, rendered)
422 .map_err(|e| GeneratorError::Generation(format!("Failed to write README.md: {}", e)))?;
423
424 Ok(())
425 }
426
427 fn generate_release_workflow(&self, output_dir: &Path) -> Result<()> {
429 let workflows_dir = output_dir.join(".github").join("workflows");
431 fs::create_dir_all(&workflows_dir).map_err(|e| {
432 GeneratorError::Generation(format!(
433 "Failed to create .github/workflows directory: {}",
434 e
435 ))
436 })?;
437
438 let context = self.create_unified_context();
439 let rendered = self
440 .tera
441 .render("release.yml", &context)
442 .map_err(|e| GeneratorError::Generation(format!("Template error: {:?}", e)))?;
443
444 let output_path = workflows_dir.join("release.yml");
445 fs::write(output_path, rendered).map_err(|e| {
446 GeneratorError::Generation(format!("Failed to write release.yml: {}", e))
447 })?;
448
449 Ok(())
450 }
451
452 fn generate_docs(&self, output_dir: &Path) -> Result<()> {
454 let docs_dir = output_dir.join("docs");
456 fs::create_dir_all(&docs_dir).map_err(|e| {
457 GeneratorError::Generation(format!("Failed to create docs directory: {}", e))
458 })?;
459
460 let services_docs_dir = docs_dir.join("services");
461 fs::create_dir_all(&services_docs_dir).map_err(|e| {
462 GeneratorError::Generation(format!("Failed to create docs/services directory: {}", e))
463 })?;
464
465 self.generate_installation_docs(&docs_dir)?;
467
468 self.generate_getting_started_docs(&docs_dir)?;
470
471 for service in &self.provider_def.services {
473 self.generate_service_docs(&services_docs_dir, service)?;
474 }
475
476 Ok(())
477 }
478
479 fn generate_installation_docs(&self, docs_dir: &Path) -> Result<()> {
481 let context = self.create_unified_context();
482 let rendered = self
483 .tera
484 .render("docs_installation.md", &context)
485 .map_err(|e| GeneratorError::Generation(format!("Template error: {:?}", e)))?;
486
487 let output_path = docs_dir.join("installation.md");
488 fs::write(output_path, rendered).map_err(|e| {
489 GeneratorError::Generation(format!("Failed to write installation.md: {}", e))
490 })?;
491
492 Ok(())
493 }
494
495 fn generate_getting_started_docs(&self, docs_dir: &Path) -> Result<()> {
497 let context = self.create_unified_context();
498 let rendered = self
499 .tera
500 .render("docs_getting_started.md", &context)
501 .map_err(|e| GeneratorError::Generation(format!("Template error: {:?}", e)))?;
502
503 let output_path = docs_dir.join("getting-started.md");
504 fs::write(output_path, rendered).map_err(|e| {
505 GeneratorError::Generation(format!("Failed to write getting-started.md: {}", e))
506 })?;
507
508 Ok(())
509 }
510
511 fn generate_service_docs(
513 &self,
514 services_docs_dir: &Path,
515 service: &ServiceDefinition,
516 ) -> Result<()> {
517 let mut context = self.create_unified_context();
518 context.insert("service", service);
519
520 let rendered = self
521 .tera
522 .render("docs_service.md", &context)
523 .map_err(|e| GeneratorError::Generation(format!("Template error: {:?}", e)))?;
524
525 let output_path = services_docs_dir.join(format!("{}.md", service.name));
526 fs::write(output_path, rendered).map_err(|e| {
527 GeneratorError::Generation(format!("Failed to write {}.md: {}", service.name, e))
528 })?;
529
530 Ok(())
531 }
532
533 fn create_unified_context(&self) -> tera::Context {
535 let mut context = tera::Context::new();
536 context.insert("provider", &format!("{:?}", self.provider_def.provider));
537 context.insert("provider_name", &self.provider_def.provider_name);
538 context.insert("sdk_version", &self.provider_def.sdk_version);
539 context.insert("services", &self.provider_def.services);
540
541 let total_resources: usize = self
543 .provider_def
544 .services
545 .iter()
546 .map(|s| s.resources.len())
547 .sum();
548 context.insert("total_resources", &total_resources);
549
550 let sdk_config = self.provider_def.provider.sdk_config();
552 context.insert("sdk_config", &sdk_config);
553 context.insert("config_attrs", &sdk_config.config_attrs);
554 context.insert(
555 "uses_shared_client",
556 &self.provider_def.provider.uses_shared_client(),
557 );
558
559 context
560 }
561}
562
563pub fn generate_unified_provider(
565 provider_def: ProviderDefinition,
566 output_path: &str,
567) -> Result<()> {
568 let generator = UnifiedProviderGenerator::new(provider_def)?;
569 generator.generate_to_directory(Path::new(output_path))
570}
571
572#[cfg(test)]
573mod tests {
574 use super::*;
575 use hemmer_provider_generator_common::Provider;
576
577 #[test]
578 fn test_generator_creation() {
579 let service_def = ServiceDefinition {
580 provider: Provider::Aws,
581 name: "s3".to_string(),
582 sdk_version: "1.0.0".to_string(),
583 resources: vec![],
584 data_sources: vec![], };
586
587 let result = ProviderGenerator::new(service_def);
588 assert!(result.is_ok());
589 }
590
591 #[test]
592 fn test_unified_generator_creation() {
593 let provider_def = ProviderDefinition {
594 provider: Provider::Aws,
595 provider_name: "aws".to_string(),
596 sdk_version: "1.0.0".to_string(),
597 services: vec![],
598 };
599
600 let result = UnifiedProviderGenerator::new(provider_def);
601 if let Err(e) = &result {
602 eprintln!("Error creating generator: {:?}", e);
603 }
604 assert!(result.is_ok());
605 }
606}