mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 18:17:13 +08:00 
			
		
		
		
	feat(framework): Added smithy, smithy-core and smithy-generator crates (#9249)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
		
							
								
								
									
										33
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										33
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -7902,6 +7902,39 @@ dependencies = [ | ||||
|  "serde", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "smithy" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "smithy-core", | ||||
|  "syn 2.0.101", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "smithy-core" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "serde", | ||||
|  "serde_json", | ||||
|  "syn 2.0.101", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "smithy-generator" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "api_models", | ||||
|  "common_enums", | ||||
|  "common_types", | ||||
|  "common_utils", | ||||
|  "regex", | ||||
|  "router_env", | ||||
|  "smithy-core", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "snap" | ||||
| version = "1.1.1" | ||||
|  | ||||
							
								
								
									
										15
									
								
								crates/smithy-core/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								crates/smithy-core/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| [package] | ||||
| name = "smithy-core" | ||||
| version = "0.1.0" | ||||
| edition.workspace = true | ||||
| rust-version.workspace = true | ||||
| license.workspace = true | ||||
|  | ||||
| [dependencies] | ||||
| serde = { version = "1.0", features = ["derive"] } | ||||
| serde_json = "1.0" | ||||
| syn = { version = "2.0", features = ["full"] } | ||||
| proc-macro2 = "1.0" | ||||
|  | ||||
| [lints] | ||||
| workspace = true | ||||
							
								
								
									
										296
									
								
								crates/smithy-core/src/generator.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										296
									
								
								crates/smithy-core/src/generator.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,296 @@ | ||||
| // crates/smithy-core/generator.rs | ||||
|  | ||||
| use std::{collections::HashMap, fs, path::Path}; | ||||
|  | ||||
| use crate::types::{self as types, SmithyModel}; | ||||
|  | ||||
| /// Generator for creating Smithy IDL files from models | ||||
| pub struct SmithyGenerator { | ||||
|     models: Vec<SmithyModel>, | ||||
| } | ||||
|  | ||||
| impl SmithyGenerator { | ||||
|     pub fn new() -> Self { | ||||
|         Self { models: Vec::new() } | ||||
|     } | ||||
|  | ||||
|     pub fn add_model(&mut self, model: SmithyModel) { | ||||
|         self.models.push(model); | ||||
|     } | ||||
|  | ||||
|     pub fn generate_idl(&self, output_dir: &Path) -> Result<(), Box<dyn std::error::Error>> { | ||||
|         fs::create_dir_all(output_dir)?; | ||||
|  | ||||
|         let mut namespace_models: HashMap<String, Vec<&SmithyModel>> = HashMap::new(); | ||||
|         let mut shape_to_namespace: HashMap<String, String> = HashMap::new(); | ||||
|  | ||||
|         // First, build a map of all shape names to their namespaces | ||||
|         for model in &self.models { | ||||
|             for shape_name in model.shapes.keys() { | ||||
|                 shape_to_namespace.insert(shape_name.clone(), model.namespace.clone()); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Group models by namespace for file generation | ||||
|         for model in &self.models { | ||||
|             namespace_models | ||||
|                 .entry(model.namespace.clone()) | ||||
|                 .or_default() | ||||
|                 .push(model); | ||||
|         } | ||||
|  | ||||
|         for (namespace, models) in namespace_models { | ||||
|             let filename = format!("{}.smithy", namespace.replace('.', "_")); | ||||
|             let filepath = output_dir.join(filename); | ||||
|  | ||||
|             let mut content = String::new(); | ||||
|             content.push_str("$version: \"2\"\n\n"); | ||||
|             content.push_str(&format!("namespace {}\n\n", namespace)); | ||||
|  | ||||
|             // Collect all unique shape definitions for the current namespace | ||||
|             let mut shapes_in_namespace = HashMap::new(); | ||||
|             for model in models { | ||||
|                 for (shape_name, shape) in &model.shapes { | ||||
|                     shapes_in_namespace.insert(shape_name.clone(), shape.clone()); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Generate definitions for each shape in the namespace | ||||
|             for (shape_name, shape) in &shapes_in_namespace { | ||||
|                 content.push_str(&self.generate_shape_definition( | ||||
|                     shape_name, | ||||
|                     shape, | ||||
|                     &namespace, | ||||
|                     &shape_to_namespace, | ||||
|                 )); | ||||
|                 content.push_str("\n\n"); | ||||
|             } | ||||
|  | ||||
|             fs::write(filepath, content)?; | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn generate_shape_definition( | ||||
|         &self, | ||||
|         name: &str, | ||||
|         shape: &types::SmithyShape, | ||||
|         current_namespace: &str, | ||||
|         shape_to_namespace: &HashMap<String, String>, | ||||
|     ) -> String { | ||||
|         let resolve_target = | ||||
|             |target: &str| self.resolve_type(target, current_namespace, shape_to_namespace); | ||||
|  | ||||
|         match shape { | ||||
|             types::SmithyShape::Structure { | ||||
|                 members, | ||||
|                 documentation, | ||||
|                 traits, | ||||
|             } => { | ||||
|                 let mut def = String::new(); | ||||
|  | ||||
|                 if let Some(doc) = documentation { | ||||
|                     def.push_str(&format!("/// {}\n", doc)); | ||||
|                 } | ||||
|  | ||||
|                 for smithy_trait in traits { | ||||
|                     def.push_str(&format!("@{}\n", self.trait_to_string(smithy_trait))); | ||||
|                 } | ||||
|  | ||||
|                 def.push_str(&format!("structure {} {{\n", name)); | ||||
|  | ||||
|                 for (member_name, member) in members { | ||||
|                     if let Some(doc) = &member.documentation { | ||||
|                         def.push_str(&format!("    /// {}\n", doc)); | ||||
|                     } | ||||
|  | ||||
|                     for smithy_trait in &member.traits { | ||||
|                         def.push_str(&format!("    @{}\n", self.trait_to_string(smithy_trait))); | ||||
|                     } | ||||
|  | ||||
|                     let resolved_target = resolve_target(&member.target); | ||||
|                     def.push_str(&format!("    {}: {}\n", member_name, resolved_target)); | ||||
|                 } | ||||
|  | ||||
|                 def.push('}'); | ||||
|                 def | ||||
|             } | ||||
|             types::SmithyShape::Union { | ||||
|                 members, | ||||
|                 documentation, | ||||
|                 traits, | ||||
|             } => { | ||||
|                 let mut def = String::new(); | ||||
|  | ||||
|                 if let Some(doc) = documentation { | ||||
|                     def.push_str(&format!("/// {}\n", doc)); | ||||
|                 } | ||||
|  | ||||
|                 for smithy_trait in traits { | ||||
|                     def.push_str(&format!("@{}\n", self.trait_to_string(smithy_trait))); | ||||
|                 } | ||||
|  | ||||
|                 def.push_str(&format!("union {} {{\n", name)); | ||||
|  | ||||
|                 for (member_name, member) in members { | ||||
|                     if let Some(doc) = &member.documentation { | ||||
|                         def.push_str(&format!("    /// {}\n", doc)); | ||||
|                     } | ||||
|  | ||||
|                     for smithy_trait in &member.traits { | ||||
|                         def.push_str(&format!("    @{}\n", self.trait_to_string(smithy_trait))); | ||||
|                     } | ||||
|  | ||||
|                     let resolved_target = resolve_target(&member.target); | ||||
|                     def.push_str(&format!("    {}: {}\n", member_name, resolved_target)); | ||||
|                 } | ||||
|  | ||||
|                 def.push('}'); | ||||
|                 def | ||||
|             } | ||||
|             types::SmithyShape::Enum { | ||||
|                 values, | ||||
|                 documentation, | ||||
|                 traits, | ||||
|             } => { | ||||
|                 let mut def = String::new(); | ||||
|  | ||||
|                 if let Some(doc) = documentation { | ||||
|                     def.push_str(&format!("/// {}\n", doc)); | ||||
|                 } | ||||
|  | ||||
|                 for smithy_trait in traits { | ||||
|                     def.push_str(&format!("@{}\n", self.trait_to_string(smithy_trait))); | ||||
|                 } | ||||
|  | ||||
|                 def.push_str(&format!("enum {} {{\n", name)); | ||||
|  | ||||
|                 for (value_name, enum_value) in values { | ||||
|                     if let Some(doc) = &enum_value.documentation { | ||||
|                         def.push_str(&format!("    /// {}\n", doc)); | ||||
|                     } | ||||
|                     def.push_str(&format!("    {}\n", value_name)); | ||||
|                 } | ||||
|  | ||||
|                 def.push('}'); | ||||
|                 def | ||||
|             } | ||||
|             types::SmithyShape::String { traits } => { | ||||
|                 let mut def = String::new(); | ||||
|  | ||||
|                 for smithy_trait in traits { | ||||
|                     def.push_str(&format!("@{}\n", self.trait_to_string(smithy_trait))); | ||||
|                 } | ||||
|  | ||||
|                 def.push_str(&format!("string {}", name)); | ||||
|                 def | ||||
|             } | ||||
|             types::SmithyShape::Integer { traits } => { | ||||
|                 let mut def = String::new(); | ||||
|  | ||||
|                 for smithy_trait in traits { | ||||
|                     def.push_str(&format!("@{}\n", self.trait_to_string(smithy_trait))); | ||||
|                 } | ||||
|  | ||||
|                 def.push_str(&format!("integer {}", name)); | ||||
|                 def | ||||
|             } | ||||
|             types::SmithyShape::Long { traits } => { | ||||
|                 let mut def = String::new(); | ||||
|  | ||||
|                 for smithy_trait in traits { | ||||
|                     def.push_str(&format!("@{}\n", self.trait_to_string(smithy_trait))); | ||||
|                 } | ||||
|  | ||||
|                 def.push_str(&format!("long {}", name)); | ||||
|                 def | ||||
|             } | ||||
|             types::SmithyShape::Boolean { traits } => { | ||||
|                 let mut def = String::new(); | ||||
|  | ||||
|                 for smithy_trait in traits { | ||||
|                     def.push_str(&format!("@{}\n", self.trait_to_string(smithy_trait))); | ||||
|                 } | ||||
|  | ||||
|                 def.push_str(&format!("boolean {}", name)); | ||||
|                 def | ||||
|             } | ||||
|             types::SmithyShape::List { member, traits } => { | ||||
|                 let mut def = String::new(); | ||||
|  | ||||
|                 for smithy_trait in traits { | ||||
|                     def.push_str(&format!("@{}\n", self.trait_to_string(smithy_trait))); | ||||
|                 } | ||||
|  | ||||
|                 def.push_str(&format!("list {} {{\n", name)); | ||||
|                 let resolved_target = resolve_target(&member.target); | ||||
|                 def.push_str(&format!("    member: {}\n", resolved_target)); | ||||
|                 def.push('}'); | ||||
|                 def | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn resolve_type( | ||||
|         &self, | ||||
|         target: &str, | ||||
|         current_namespace: &str, | ||||
|         shape_to_namespace: &HashMap<String, String>, | ||||
|     ) -> String { | ||||
|         // If the target is a primitive or a fully qualified Smithy type, return it as is | ||||
|         if target.starts_with("smithy.api#") { | ||||
|             return target.to_string(); | ||||
|         } | ||||
|  | ||||
|         // If the target is a custom type, resolve its namespace | ||||
|         if let Some(target_namespace) = shape_to_namespace.get(target) { | ||||
|             if target_namespace == current_namespace { | ||||
|                 // The type is in the same namespace, so no qualification is needed | ||||
|                 target.to_string() | ||||
|             } else { | ||||
|                 // The type is in a different namespace, so it needs to be fully qualified | ||||
|                 format!("{}#{}", target_namespace, target) | ||||
|             } | ||||
|         } else { | ||||
|             // If the type is not found in the shape map, it might be a primitive | ||||
|             // or an unresolved type. For now, return it as is. | ||||
|             target.to_string() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn trait_to_string(&self, smithy_trait: &types::SmithyTrait) -> String { | ||||
|         match smithy_trait { | ||||
|             types::SmithyTrait::Pattern { pattern } => { | ||||
|                 format!("pattern(\"{}\")", pattern) | ||||
|             } | ||||
|             types::SmithyTrait::Range { min, max } => match (min, max) { | ||||
|                 (Some(min), Some(max)) => format!("range(min: {}, max: {})", min, max), | ||||
|                 (Some(min), None) => format!("range(min: {})", min), | ||||
|                 (None, Some(max)) => format!("range(max: {})", max), | ||||
|                 (None, None) => "range".to_string(), | ||||
|             }, | ||||
|             types::SmithyTrait::Required => "required".to_string(), | ||||
|             types::SmithyTrait::Documentation { documentation } => { | ||||
|                 format!("documentation(\"{}\")", documentation) | ||||
|             } | ||||
|             types::SmithyTrait::Length { min, max } => match (min, max) { | ||||
|                 (Some(min), Some(max)) => format!("length(min: {}, max: {})", min, max), | ||||
|                 (Some(min), None) => format!("length(min: {})", min), | ||||
|                 (None, Some(max)) => format!("length(max: {})", max), | ||||
|                 (None, None) => "length".to_string(), | ||||
|             }, | ||||
|             types::SmithyTrait::HttpLabel => "httpLabel".to_string(), | ||||
|             types::SmithyTrait::HttpQuery { name } => { | ||||
|                 format!("httpQuery(\"{}\")", name) | ||||
|             } | ||||
|             types::SmithyTrait::Mixin => "mixin".to_string(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for SmithyGenerator { | ||||
|     fn default() -> Self { | ||||
|         Self::new() | ||||
|     } | ||||
| } | ||||
							
								
								
									
										7
									
								
								crates/smithy-core/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								crates/smithy-core/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| // // crates/smithy-core/lib.rs | ||||
|  | ||||
| pub mod generator; | ||||
| pub mod types; | ||||
|  | ||||
| pub use generator::SmithyGenerator; | ||||
| pub use types::*; | ||||
							
								
								
									
										331
									
								
								crates/smithy-core/src/types.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										331
									
								
								crates/smithy-core/src/types.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,331 @@ | ||||
| // crates/smithy-core/types.rs | ||||
|  | ||||
| use std::collections::HashMap; | ||||
|  | ||||
| use serde::{Deserialize, Serialize}; | ||||
|  | ||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | ||||
| pub struct SmithyModel { | ||||
|     pub namespace: String, | ||||
|     pub shapes: HashMap<String, SmithyShape>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | ||||
| #[serde(tag = "type")] | ||||
| pub enum SmithyShape { | ||||
|     #[serde(rename = "structure")] | ||||
|     Structure { | ||||
|         members: HashMap<String, SmithyMember>, | ||||
|         #[serde(skip_serializing_if = "Option::is_none")] | ||||
|         documentation: Option<String>, | ||||
|         #[serde(skip_serializing_if = "Vec::is_empty")] | ||||
|         traits: Vec<SmithyTrait>, | ||||
|     }, | ||||
|     #[serde(rename = "string")] | ||||
|     String { | ||||
|         #[serde(skip_serializing_if = "Vec::is_empty")] | ||||
|         traits: Vec<SmithyTrait>, | ||||
|     }, | ||||
|     #[serde(rename = "integer")] | ||||
|     Integer { | ||||
|         #[serde(skip_serializing_if = "Vec::is_empty")] | ||||
|         traits: Vec<SmithyTrait>, | ||||
|     }, | ||||
|     #[serde(rename = "long")] | ||||
|     Long { | ||||
|         #[serde(skip_serializing_if = "Vec::is_empty")] | ||||
|         traits: Vec<SmithyTrait>, | ||||
|     }, | ||||
|     #[serde(rename = "boolean")] | ||||
|     Boolean { | ||||
|         #[serde(skip_serializing_if = "Vec::is_empty")] | ||||
|         traits: Vec<SmithyTrait>, | ||||
|     }, | ||||
|     #[serde(rename = "list")] | ||||
|     List { | ||||
|         member: Box<SmithyMember>, | ||||
|         #[serde(skip_serializing_if = "Vec::is_empty")] | ||||
|         traits: Vec<SmithyTrait>, | ||||
|     }, | ||||
|     #[serde(rename = "union")] | ||||
|     Union { | ||||
|         members: HashMap<String, SmithyMember>, | ||||
|         #[serde(skip_serializing_if = "Option::is_none")] | ||||
|         documentation: Option<String>, | ||||
|         #[serde(skip_serializing_if = "Vec::is_empty")] | ||||
|         traits: Vec<SmithyTrait>, | ||||
|     }, | ||||
|     #[serde(rename = "enum")] | ||||
|     Enum { | ||||
|         values: HashMap<String, SmithyEnumValue>, | ||||
|         #[serde(skip_serializing_if = "Option::is_none")] | ||||
|         documentation: Option<String>, | ||||
|         #[serde(skip_serializing_if = "Vec::is_empty")] | ||||
|         traits: Vec<SmithyTrait>, | ||||
|     }, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | ||||
| pub struct SmithyMember { | ||||
|     pub target: String, | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub documentation: Option<String>, | ||||
|     #[serde(skip_serializing_if = "Vec::is_empty")] | ||||
|     pub traits: Vec<SmithyTrait>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | ||||
| pub struct SmithyEnumValue { | ||||
|     pub name: String, | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub documentation: Option<String>, | ||||
|     pub is_default: bool, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | ||||
| #[serde(tag = "trait")] | ||||
| pub enum SmithyTrait { | ||||
|     #[serde(rename = "smithy.api#pattern")] | ||||
|     Pattern { pattern: String }, | ||||
|     #[serde(rename = "smithy.api#range")] | ||||
|     Range { min: Option<i64>, max: Option<i64> }, | ||||
|     #[serde(rename = "smithy.api#required")] | ||||
|     Required, | ||||
|     #[serde(rename = "smithy.api#documentation")] | ||||
|     Documentation { documentation: String }, | ||||
|     #[serde(rename = "smithy.api#length")] | ||||
|     Length { min: Option<u64>, max: Option<u64> }, | ||||
|     #[serde(rename = "smithy.api#httpLabel")] | ||||
|     HttpLabel, | ||||
|     #[serde(rename = "smithy.api#httpQuery")] | ||||
|     HttpQuery { name: String }, | ||||
|     #[serde(rename = "smithy.api#mixin")] | ||||
|     Mixin, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| pub struct SmithyField { | ||||
|     pub name: String, | ||||
|     pub value_type: String, | ||||
|     pub constraints: Vec<SmithyConstraint>, | ||||
|     pub documentation: Option<String>, | ||||
|     pub optional: bool, | ||||
|     pub flatten: bool, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| pub struct SmithyEnumVariant { | ||||
|     pub name: String, | ||||
|     pub fields: Vec<SmithyField>, | ||||
|     pub constraints: Vec<SmithyConstraint>, | ||||
|     pub documentation: Option<String>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| pub enum SmithyConstraint { | ||||
|     Pattern(String), | ||||
|     Range(Option<i64>, Option<i64>), | ||||
|     Length(Option<u64>, Option<u64>), | ||||
|     Required, | ||||
|     HttpLabel, | ||||
|     HttpQuery(String), | ||||
| } | ||||
|  | ||||
| pub trait SmithyModelGenerator { | ||||
|     fn generate_smithy_model() -> SmithyModel; | ||||
| } | ||||
|  | ||||
| // Helper functions moved from the proc-macro crate to be accessible by it. | ||||
|  | ||||
| pub fn resolve_type_and_generate_shapes( | ||||
|     value_type: &str, | ||||
|     shapes: &mut HashMap<String, SmithyShape>, | ||||
| ) -> Result<(String, HashMap<String, SmithyShape>), syn::Error> { | ||||
|     let value_type = value_type.trim(); | ||||
|     let value_type_span = proc_macro2::Span::call_site(); | ||||
|     let mut generated_shapes = HashMap::new(); | ||||
|  | ||||
|     let target_type = match value_type { | ||||
|         "String" | "str" => "smithy.api#String".to_string(), | ||||
|         "i8" | "i16" | "i32" | "u8" | "u16" | "u32" => "smithy.api#Integer".to_string(), | ||||
|         "i64" | "u64" | "isize" | "usize" => "smithy.api#Long".to_string(), | ||||
|         "f32" => "smithy.api#Float".to_string(), | ||||
|         "f64" => "smithy.api#Double".to_string(), | ||||
|         "bool" => "smithy.api#Boolean".to_string(), | ||||
|         "PrimitiveDateTime" | "time::PrimitiveDateTime" => "smithy.api#Timestamp".to_string(), | ||||
|         "Amount" | "MinorUnit" => "smithy.api#Long".to_string(), | ||||
|         "serde_json::Value" | "Value" | "Object" => "smithy.api#Document".to_string(), | ||||
|         "Url" | "url::Url" => "smithy.api#String".to_string(), | ||||
|  | ||||
|         vt if vt.starts_with("Option<") && vt.ends_with('>') => { | ||||
|             let inner_type = extract_generic_inner_type(vt, "Option") | ||||
|                 .map_err(|e| syn::Error::new(value_type_span, e))?; | ||||
|             let (resolved_type, new_shapes) = resolve_type_and_generate_shapes(inner_type, shapes)?; | ||||
|             generated_shapes.extend(new_shapes); | ||||
|             resolved_type | ||||
|         } | ||||
|  | ||||
|         vt if vt.starts_with("Vec<") && vt.ends_with('>') => { | ||||
|             let inner_type = extract_generic_inner_type(vt, "Vec") | ||||
|                 .map_err(|e| syn::Error::new(value_type_span, e))?; | ||||
|             let (inner_smithy_type, new_shapes) = | ||||
|                 resolve_type_and_generate_shapes(inner_type, shapes)?; | ||||
|             generated_shapes.extend(new_shapes); | ||||
|  | ||||
|             let list_shape_name = format!( | ||||
|                 "{}List", | ||||
|                 inner_smithy_type | ||||
|                     .split("::") | ||||
|                     .last() | ||||
|                     .unwrap_or(&inner_smithy_type) | ||||
|                     .split('#') | ||||
|                     .next_back() | ||||
|                     .unwrap_or(&inner_smithy_type) | ||||
|             ); | ||||
|             if !shapes.contains_key(&list_shape_name) | ||||
|                 && !generated_shapes.contains_key(&list_shape_name) | ||||
|             { | ||||
|                 let list_shape = SmithyShape::List { | ||||
|                     member: Box::new(SmithyMember { | ||||
|                         target: inner_smithy_type, | ||||
|                         documentation: None, | ||||
|                         traits: vec![], | ||||
|                     }), | ||||
|                     traits: vec![], | ||||
|                 }; | ||||
|                 generated_shapes.insert(list_shape_name.clone(), list_shape); | ||||
|             } | ||||
|             list_shape_name | ||||
|         } | ||||
|  | ||||
|         vt if vt.starts_with("Box<") && vt.ends_with('>') => { | ||||
|             let inner_type = extract_generic_inner_type(vt, "Box") | ||||
|                 .map_err(|e| syn::Error::new(value_type_span, e))?; | ||||
|             let (resolved_type, new_shapes) = resolve_type_and_generate_shapes(inner_type, shapes)?; | ||||
|             generated_shapes.extend(new_shapes); | ||||
|             resolved_type | ||||
|         } | ||||
|  | ||||
|         vt if vt.starts_with("Secret<") && vt.ends_with('>') => { | ||||
|             let inner_type = extract_generic_inner_type(vt, "Secret") | ||||
|                 .map_err(|e| syn::Error::new(value_type_span, e))?; | ||||
|             let (resolved_type, new_shapes) = resolve_type_and_generate_shapes(inner_type, shapes)?; | ||||
|             generated_shapes.extend(new_shapes); | ||||
|             resolved_type | ||||
|         } | ||||
|  | ||||
|         vt if vt.starts_with("HashMap<") && vt.ends_with('>') => { | ||||
|             let inner_types = extract_generic_inner_type(vt, "HashMap") | ||||
|                 .map_err(|e| syn::Error::new(value_type_span, e))?; | ||||
|             let (key_type, value_type) = | ||||
|                 parse_map_types(inner_types).map_err(|e| syn::Error::new(value_type_span, e))?; | ||||
|             let (key_smithy_type, key_shapes) = resolve_type_and_generate_shapes(key_type, shapes)?; | ||||
|             generated_shapes.extend(key_shapes); | ||||
|             let (value_smithy_type, value_shapes) = | ||||
|                 resolve_type_and_generate_shapes(value_type, shapes)?; | ||||
|             generated_shapes.extend(value_shapes); | ||||
|             format!( | ||||
|                 "smithy.api#Map<key: {}, value: {}>", | ||||
|                 key_smithy_type, value_smithy_type | ||||
|             ) | ||||
|         } | ||||
|  | ||||
|         vt if vt.starts_with("BTreeMap<") && vt.ends_with('>') => { | ||||
|             let inner_types = extract_generic_inner_type(vt, "BTreeMap") | ||||
|                 .map_err(|e| syn::Error::new(value_type_span, e))?; | ||||
|             let (key_type, value_type) = | ||||
|                 parse_map_types(inner_types).map_err(|e| syn::Error::new(value_type_span, e))?; | ||||
|             let (key_smithy_type, key_shapes) = resolve_type_and_generate_shapes(key_type, shapes)?; | ||||
|             generated_shapes.extend(key_shapes); | ||||
|             let (value_smithy_type, value_shapes) = | ||||
|                 resolve_type_and_generate_shapes(value_type, shapes)?; | ||||
|             generated_shapes.extend(value_shapes); | ||||
|             format!( | ||||
|                 "smithy.api#Map<key: {}, value: {}>", | ||||
|                 key_smithy_type, value_smithy_type | ||||
|             ) | ||||
|         } | ||||
|  | ||||
|         _ => { | ||||
|             if value_type.contains("::") { | ||||
|                 value_type.replace("::", ".") | ||||
|             } else { | ||||
|                 value_type.to_string() | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     Ok((target_type, generated_shapes)) | ||||
| } | ||||
|  | ||||
| fn extract_generic_inner_type<'a>(full_type: &'a str, wrapper: &str) -> Result<&'a str, String> { | ||||
|     let expected_start = format!("{}<", wrapper); | ||||
|  | ||||
|     if !full_type.starts_with(&expected_start) || !full_type.ends_with('>') { | ||||
|         return Err(format!("Invalid {} type format: {}", wrapper, full_type)); | ||||
|     } | ||||
|  | ||||
|     let start_idx = expected_start.len(); | ||||
|     let end_idx = full_type.len() - 1; | ||||
|  | ||||
|     if start_idx >= end_idx { | ||||
|         return Err(format!("Empty {} type: {}", wrapper, full_type)); | ||||
|     } | ||||
|  | ||||
|     if start_idx >= full_type.len() || end_idx > full_type.len() { | ||||
|         return Err(format!( | ||||
|             "Invalid index bounds for {} type: {}", | ||||
|             wrapper, full_type | ||||
|         )); | ||||
|     } | ||||
|  | ||||
|     Ok(full_type | ||||
|         .get(start_idx..end_idx) | ||||
|         .ok_or_else(|| { | ||||
|             format!( | ||||
|                 "Failed to extract inner type from {}: {}", | ||||
|                 wrapper, full_type | ||||
|             ) | ||||
|         })? | ||||
|         .trim()) | ||||
| } | ||||
|  | ||||
| fn parse_map_types(inner_types: &str) -> Result<(&str, &str), String> { | ||||
|     // Handle nested generics by counting angle brackets | ||||
|     let mut bracket_count = 0; | ||||
|     let mut comma_pos = None; | ||||
|  | ||||
|     for (i, ch) in inner_types.char_indices() { | ||||
|         match ch { | ||||
|             '<' => bracket_count += 1, | ||||
|             '>' => bracket_count -= 1, | ||||
|             ',' if bracket_count == 0 => { | ||||
|                 comma_pos = Some(i); | ||||
|                 break; | ||||
|             } | ||||
|             _ => {} | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if let Some(pos) = comma_pos { | ||||
|         let key_type = inner_types | ||||
|             .get(..pos) | ||||
|             .ok_or_else(|| format!("Invalid key type bounds in map: {}", inner_types))? | ||||
|             .trim(); | ||||
|         let value_type = inner_types | ||||
|             .get(pos + 1..) | ||||
|             .ok_or_else(|| format!("Invalid value type bounds in map: {}", inner_types))? | ||||
|             .trim(); | ||||
|  | ||||
|         if key_type.is_empty() || value_type.is_empty() { | ||||
|             return Err(format!("Invalid map type format: {}", inner_types)); | ||||
|         } | ||||
|  | ||||
|         Ok((key_type, value_type)) | ||||
|     } else { | ||||
|         Err(format!( | ||||
|             "Invalid map type format, missing comma: {}", | ||||
|             inner_types | ||||
|         )) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										26
									
								
								crates/smithy-generator/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								crates/smithy-generator/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| [package] | ||||
| name = "smithy-generator" | ||||
| version = "0.1.0" | ||||
| edition.workspace = true | ||||
| rust-version.workspace = true | ||||
| license.workspace = true | ||||
| build = "build.rs" | ||||
|  | ||||
| [features] | ||||
| default = ["v1"] | ||||
| v1 = ["api_models/v1", "common_utils/v1"] | ||||
| v2 = ["api_models/v2", "common_utils/v2"] | ||||
|  | ||||
| [dependencies] | ||||
| smithy-core = { path = "../smithy-core" } | ||||
| api_models = { version = "0.1.0", path = "../api_models" } | ||||
| common_enums = { version = "0.1.0", path = "../common_enums" } | ||||
| common_types = { version = "0.1.0", path = "../common_types" } | ||||
| common_utils = { version = "0.1.0", path = "../common_utils" } | ||||
| router_env = { version = "0.1.0", path = "../router_env" } | ||||
|  | ||||
| [build-dependencies] | ||||
| regex = "1" | ||||
|  | ||||
| [lints] | ||||
| workspace = true | ||||
							
								
								
									
										306
									
								
								crates/smithy-generator/build.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										306
									
								
								crates/smithy-generator/build.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,306 @@ | ||||
| // crates/smithy-generator/build.rs | ||||
|  | ||||
| use std::{fs, path::Path}; | ||||
|  | ||||
| use regex::Regex; | ||||
|  | ||||
| fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
|     println!("cargo:rerun-if-changed=../"); | ||||
|     run_build() | ||||
| } | ||||
|  | ||||
| fn run_build() -> Result<(), Box<dyn std::error::Error>> { | ||||
|     let workspace_root = get_workspace_root()?; | ||||
|  | ||||
|     let mut smithy_models = Vec::new(); | ||||
|  | ||||
|     // Scan all crates in the workspace for SmithyModel derives | ||||
|     let crates_dir = workspace_root.join("crates"); | ||||
|     if let Ok(entries) = fs::read_dir(&crates_dir) { | ||||
|         for entry in entries.flatten() { | ||||
|             if entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false) { | ||||
|                 let crate_path = entry.path(); | ||||
|                 let crate_name = match crate_path.file_name() { | ||||
|                     Some(name) => name.to_string_lossy(), | ||||
|                     None => { | ||||
|                         println!( | ||||
|                             "cargo:warning=Skipping crate with invalid path: {}", | ||||
|                             crate_path.display() | ||||
|                         ); | ||||
|                         continue; | ||||
|                     } | ||||
|                 }; | ||||
|  | ||||
|                 // Skip the smithy crate itself to avoid self-dependency | ||||
|                 if crate_name == "smithy" | ||||
|                     || crate_name == "smithy-core" | ||||
|                     || crate_name == "smithy-generator" | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 if let Err(e) = | ||||
|                     scan_crate_for_smithy_models(&crate_path, &crate_name, &mut smithy_models) | ||||
|                 { | ||||
|                     println!("cargo:warning=Failed to scan crate {}: {}", crate_name, e); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Generate the registry file | ||||
|     generate_model_registry(&smithy_models)?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| fn get_workspace_root() -> Result<std::path::PathBuf, Box<dyn std::error::Error>> { | ||||
|     let manifest_dir = std::env::var("CARGO_MANIFEST_DIR") | ||||
|         .map_err(|_| "CARGO_MANIFEST_DIR environment variable not set")?; | ||||
|  | ||||
|     let manifest_path = Path::new(&manifest_dir); | ||||
|  | ||||
|     let parent1 = manifest_path | ||||
|         .parent() | ||||
|         .ok_or("Cannot get parent directory of CARGO_MANIFEST_DIR")?; | ||||
|  | ||||
|     let workspace_root = parent1 | ||||
|         .parent() | ||||
|         .ok_or("Cannot get workspace root directory")?; | ||||
|  | ||||
|     Ok(workspace_root.to_path_buf()) | ||||
| } | ||||
|  | ||||
| fn scan_crate_for_smithy_models( | ||||
|     crate_path: &Path, | ||||
|     crate_name: &str, | ||||
|     models: &mut Vec<SmithyModelInfo>, | ||||
| ) -> Result<(), Box<dyn std::error::Error>> { | ||||
|     let src_path = crate_path.join("src"); | ||||
|     if !src_path.exists() { | ||||
|         return Ok(()); | ||||
|     } | ||||
|  | ||||
|     scan_directory(&src_path, crate_name, "", models)?; | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| fn scan_directory( | ||||
|     dir: &Path, | ||||
|     crate_name: &str, | ||||
|     module_path: &str, | ||||
|     models: &mut Vec<SmithyModelInfo>, | ||||
| ) -> Result<(), Box<dyn std::error::Error>> { | ||||
|     if let Ok(entries) = fs::read_dir(dir) { | ||||
|         for entry in entries.flatten() { | ||||
|             let path = entry.path(); | ||||
|             if path.is_dir() { | ||||
|                 let dir_name = match path.file_name() { | ||||
|                     Some(name) => name.to_string_lossy(), | ||||
|                     None => { | ||||
|                         println!( | ||||
|                             "cargo:warning=Skipping directory with invalid name: {}", | ||||
|                             path.display() | ||||
|                         ); | ||||
|                         continue; | ||||
|                     } | ||||
|                 }; | ||||
|                 let new_module_path = if module_path.is_empty() { | ||||
|                     dir_name.to_string() | ||||
|                 } else { | ||||
|                     format!("{}::{}", module_path, dir_name) | ||||
|                 }; | ||||
|                 scan_directory(&path, crate_name, &new_module_path, models)?; | ||||
|             } else if path.extension().map(|ext| ext == "rs").unwrap_or(false) { | ||||
|                 if let Err(e) = scan_rust_file(&path, crate_name, module_path, models) { | ||||
|                     println!( | ||||
|                         "cargo:warning=Failed to scan Rust file {}: {}", | ||||
|                         path.display(), | ||||
|                         e | ||||
|                     ); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| fn scan_rust_file( | ||||
|     file_path: &Path, | ||||
|     crate_name: &str, | ||||
|     module_path: &str, | ||||
|     models: &mut Vec<SmithyModelInfo>, | ||||
| ) -> Result<(), Box<dyn std::error::Error>> { | ||||
|     if let Ok(content) = fs::read_to_string(file_path) { | ||||
|         // Enhanced regex that handles comments, doc comments, and multiple attributes | ||||
|         // between derive and struct/enum declarations | ||||
|         let re = Regex::new(r"(?ms)^#\[derive\(([^)]*(?:\([^)]*\))*[^)]*)\)\]\s*(?:(?:#\[[^\]]*\]\s*)|(?://[^\r\n]*\s*)|(?:///[^\r\n]*\s*)|(?:/\*.*?\*/\s*))*(?:pub\s+)?(?:struct|enum)\s+([A-Z][A-Za-z0-9_]*)\s*[<\{\(]") | ||||
|             .map_err(|e| format!("Failed to compile regex: {}", e))?; | ||||
|  | ||||
|         for captures in re.captures_iter(&content) { | ||||
|             let derive_content = match captures.get(1) { | ||||
|                 Some(capture) => capture.as_str(), | ||||
|                 None => { | ||||
|                     println!( | ||||
|                         "cargo:warning=Missing derive content in regex capture for {}", | ||||
|                         file_path.display() | ||||
|                     ); | ||||
|                     continue; | ||||
|                 } | ||||
|             }; | ||||
|             let item_name = match captures.get(2) { | ||||
|                 Some(capture) => capture.as_str(), | ||||
|                 None => { | ||||
|                     println!( | ||||
|                         "cargo:warning=Missing item name in regex capture for {}", | ||||
|                         file_path.display() | ||||
|                     ); | ||||
|                     continue; | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|             // Check if "SmithyModel" is present in the derive macro's content. | ||||
|             if derive_content.contains("SmithyModel") { | ||||
|                 // Validate that the item name is a valid Rust identifier | ||||
|                 if is_valid_rust_identifier(item_name) { | ||||
|                     let full_module_path = create_module_path(file_path, crate_name, module_path)?; | ||||
|  | ||||
|                     models.push(SmithyModelInfo { | ||||
|                         struct_name: item_name.to_string(), | ||||
|                         module_path: full_module_path, | ||||
|                     }); | ||||
|                 } else { | ||||
|                     println!( | ||||
|                         "cargo:warning=Skipping invalid identifier: {} in {}", | ||||
|                         item_name, | ||||
|                         file_path.display() | ||||
|                     ); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| fn is_valid_rust_identifier(name: &str) -> bool { | ||||
|     if name.is_empty() { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     // Rust identifiers must start with a letter or underscore | ||||
|     let first_char = match name.chars().next() { | ||||
|         Some(ch) => ch, | ||||
|         None => return false, // This shouldn't happen since we checked is_empty above, but being safe | ||||
|     }; | ||||
|     if !first_char.is_ascii_alphabetic() && first_char != '_' { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     // Must not be a Rust keyword | ||||
|     let keywords = [ | ||||
|         "as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn", | ||||
|         "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref", | ||||
|         "return", "self", "Self", "static", "struct", "super", "trait", "true", "type", "unsafe", | ||||
|         "use", "where", "while", "async", "await", "dyn", "is", "abstract", "become", "box", "do", | ||||
|         "final", "macro", "override", "priv", "typeof", "unsized", "virtual", "yield", "try", | ||||
|     ]; | ||||
|  | ||||
|     if keywords.contains(&name) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     // All other characters must be alphanumeric or underscore | ||||
|     name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') | ||||
| } | ||||
|  | ||||
| fn create_module_path( | ||||
|     file_path: &Path, | ||||
|     crate_name: &str, | ||||
|     module_path: &str, | ||||
| ) -> Result<String, Box<dyn std::error::Error>> { | ||||
|     let file_name = file_path | ||||
|         .file_stem() | ||||
|         .and_then(|s| s.to_str()) | ||||
|         .ok_or_else(|| { | ||||
|             format!( | ||||
|                 "Cannot extract file name from path: {}", | ||||
|                 file_path.display() | ||||
|             ) | ||||
|         })?; | ||||
|  | ||||
|     let crate_name_normalized = crate_name.replace('-', "_"); | ||||
|  | ||||
|     let result = if file_name == "lib" || file_name == "mod" { | ||||
|         if module_path.is_empty() { | ||||
|             crate_name_normalized | ||||
|         } else { | ||||
|             format!("{}::{}", crate_name_normalized, module_path) | ||||
|         } | ||||
|     } else if module_path.is_empty() { | ||||
|         format!("{}::{}", crate_name_normalized, file_name) | ||||
|     } else { | ||||
|         format!("{}::{}::{}", crate_name_normalized, module_path, file_name) | ||||
|     }; | ||||
|  | ||||
|     Ok(result) | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| struct SmithyModelInfo { | ||||
|     struct_name: String, | ||||
|     module_path: String, | ||||
| } | ||||
|  | ||||
| fn generate_model_registry(models: &[SmithyModelInfo]) -> Result<(), Box<dyn std::error::Error>> { | ||||
|     let out_dir = std::env::var("OUT_DIR").map_err(|_| "OUT_DIR environment variable not set")?; | ||||
|     let registry_path = Path::new(&out_dir).join("model_registry.rs"); | ||||
|  | ||||
|     let mut content = String::new(); | ||||
|     content.push_str("// Auto-generated model registry\n"); | ||||
|     content.push_str("// DO NOT EDIT - This file is generated by build.rs\n\n"); | ||||
|  | ||||
|     if !models.is_empty() { | ||||
|         content.push_str("use smithy_core::{SmithyModel, SmithyModelGenerator};\n\n"); | ||||
|  | ||||
|         // Generate imports | ||||
|         for model in models { | ||||
|             content.push_str(&format!( | ||||
|                 "use {}::{};\n", | ||||
|                 model.module_path, model.struct_name | ||||
|             )); | ||||
|         } | ||||
|  | ||||
|         content.push_str("\npub fn discover_smithy_models() -> Vec<SmithyModel> {\n"); | ||||
|         content.push_str("    let mut models = Vec::new();\n\n"); | ||||
|  | ||||
|         // Generate model collection calls | ||||
|         for model in models { | ||||
|             content.push_str(&format!( | ||||
|                 "    models.push({}::generate_smithy_model());\n", | ||||
|                 model.struct_name | ||||
|             )); | ||||
|         } | ||||
|  | ||||
|         content.push_str("\n    models\n"); | ||||
|         content.push_str("}\n"); | ||||
|     } else { | ||||
|         // Generate empty function if no models found | ||||
|         content.push_str("use smithy_core::SmithyModel;\n\n"); | ||||
|         content.push_str("pub fn discover_smithy_models() -> Vec<SmithyModel> {\n"); | ||||
|         content.push_str( | ||||
|             "    router_env::logger::info!(\"No SmithyModel structs found in workspace\");\n", | ||||
|         ); | ||||
|         content.push_str("    Vec::new()\n"); | ||||
|         content.push_str("}\n"); | ||||
|     } | ||||
|  | ||||
|     fs::write(®istry_path, content).map_err(|e| { | ||||
|         format!( | ||||
|             "Failed to write model registry to {}: {}", | ||||
|             registry_path.display(), | ||||
|             e | ||||
|         ) | ||||
|     })?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
							
								
								
									
										59
									
								
								crates/smithy-generator/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								crates/smithy-generator/src/main.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | ||||
| // crates/smithy-generator/main.rs | ||||
|  | ||||
| use std::path::Path; | ||||
|  | ||||
| use router_env::logger; | ||||
| use smithy_core::SmithyGenerator; | ||||
|  | ||||
| // Include the auto-generated model registry | ||||
| include!(concat!(env!("OUT_DIR"), "/model_registry.rs")); | ||||
|  | ||||
| fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
|     let mut generator = SmithyGenerator::new(); | ||||
|  | ||||
|     logger::info!("Discovering Smithy models from workspace..."); | ||||
|  | ||||
|     // Automatically discover and add all models | ||||
|     let models = discover_smithy_models(); | ||||
|     logger::info!("Found {} Smithy models", models.len()); | ||||
|  | ||||
|     if models.is_empty() { | ||||
|         logger::info!("No SmithyModel structs found. Make sure your structs:"); | ||||
|         logger::info!("  1. Derive SmithyModel: #[derive(SmithyModel)]"); | ||||
|         logger::info!("  2. Are in a crate that smithy can access"); | ||||
|         logger::info!("  3. Have the correct smithy attributes"); | ||||
|         return Ok(()); | ||||
|     } | ||||
|  | ||||
|     for model in models { | ||||
|         logger::info!("  Processing namespace: {}", model.namespace); | ||||
|         let shape_names: Vec<_> = model.shapes.keys().collect(); | ||||
|         logger::info!("    Shapes: {:?}", shape_names); | ||||
|         generator.add_model(model); | ||||
|     } | ||||
|  | ||||
|     logger::info!("Generating Smithy IDL files..."); | ||||
|  | ||||
|     // Generate IDL files | ||||
|     let output_dir = Path::new("smithy/models"); | ||||
|     let absolute_output_dir = std::env::current_dir()?.join(output_dir); | ||||
|  | ||||
|     logger::info!("Output directory: {}", absolute_output_dir.display()); | ||||
|  | ||||
|     generator.generate_idl(output_dir)?; | ||||
|  | ||||
|     logger::info!("✅ Smithy models generated successfully!"); | ||||
|     logger::info!("Files written to: {}", absolute_output_dir.display()); | ||||
|  | ||||
|     // List generated files | ||||
|     if let Ok(entries) = std::fs::read_dir(output_dir) { | ||||
|         logger::info!("Generated files:"); | ||||
|         for entry in entries.flatten() { | ||||
|             if entry.file_type().map(|ft| ft.is_file()).unwrap_or(false) { | ||||
|                 logger::info!("  - {}", entry.file_name().to_string_lossy()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
							
								
								
									
										18
									
								
								crates/smithy/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								crates/smithy/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| [package] | ||||
| name = "smithy" | ||||
| version = "0.1.0" | ||||
| edition.workspace = true | ||||
| rust-version.workspace = true | ||||
| license.workspace = true | ||||
|  | ||||
| [features] | ||||
| default = [] | ||||
|  | ||||
| [lib] | ||||
| proc-macro = true | ||||
|  | ||||
| [dependencies] | ||||
| proc-macro2 = "1.0" | ||||
| quote = "1.0" | ||||
| syn = { version = "2.0", features = ["full", "extra-traits"] } | ||||
| smithy-core = { path = "../smithy-core" } | ||||
							
								
								
									
										1002
									
								
								crates/smithy/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1002
									
								
								crates/smithy/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user
	 Debarshi Gupta
					Debarshi Gupta