mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-31 01:57:45 +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",
|
"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]]
|
[[package]]
|
||||||
name = "snap"
|
name = "snap"
|
||||||
version = "1.1.1"
|
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