From 70cb0f343bff9f5def8e5f472c2b390aa5bba580 Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Fri, 6 Feb 2026 02:24:40 +0900 Subject: [PATCH 1/2] Fix enum issue --- .../changepack_log_PR7xcm5PHokMNm_YBIdNP.json | 1 + Cargo.lock | 6 ++-- .../src/schema_macro/inline_types.rs | 30 +++++++++++++++++-- .../src/schema_macro/type_utils.rs | 26 ++++++++++++++++ 4 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 .changepacks/changepack_log_PR7xcm5PHokMNm_YBIdNP.json diff --git a/.changepacks/changepack_log_PR7xcm5PHokMNm_YBIdNP.json b/.changepacks/changepack_log_PR7xcm5PHokMNm_YBIdNP.json new file mode 100644 index 0000000..e93638a --- /dev/null +++ b/.changepacks/changepack_log_PR7xcm5PHokMNm_YBIdNP.json @@ -0,0 +1 @@ +{"changes":{"crates/vespera_core/Cargo.toml":"Patch","crates/vespera_macro/Cargo.toml":"Patch","crates/vespera/Cargo.toml":"Patch"},"note":"Fix enum issue","date":"2026-02-05T17:24:37.031358300Z"} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 18b4a9d..14e72ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3114,7 +3114,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "vespera" -version = "0.1.29" +version = "0.1.31" dependencies = [ "axum", "axum-extra", @@ -3128,7 +3128,7 @@ dependencies = [ [[package]] name = "vespera_core" -version = "0.1.29" +version = "0.1.31" dependencies = [ "rstest", "serde", @@ -3137,7 +3137,7 @@ dependencies = [ [[package]] name = "vespera_macro" -version = "0.1.29" +version = "0.1.31" dependencies = [ "insta", "proc-macro2", diff --git a/crates/vespera_macro/src/schema_macro/inline_types.rs b/crates/vespera_macro/src/schema_macro/inline_types.rs index f5c1e62..bd84d44 100644 --- a/crates/vespera_macro/src/schema_macro/inline_types.rs +++ b/crates/vespera_macro/src/schema_macro/inline_types.rs @@ -10,7 +10,9 @@ use super::{ circular::detect_circular_fields, file_lookup::find_model_from_schema_path, seaorm::{RelationFieldInfo, convert_type_with_chrono}, - type_utils::{is_seaorm_relation_type, snake_to_pascal_case}, + type_utils::{ + extract_module_path_from_schema_path, is_seaorm_relation_type, snake_to_pascal_case, + }, }; use crate::parser::{extract_rename_all, extract_skip}; @@ -69,6 +71,16 @@ pub fn generate_inline_relation_type_from_def( // Parse the model struct let parsed_model: syn::ItemStruct = syn::parse_str(model_def).ok()?; + // IMPORTANT: Use the TARGET model's module path for type resolution, not the parent's. + // This ensures enum types like `AuthProvider` are resolved to `crate::models::user::AuthProvider` + // instead of incorrectly using the parent module path. + let target_module_path = extract_module_path_from_schema_path(&rel_info.schema_path); + let effective_module_path = if target_module_path.is_empty() { + source_module_path + } else { + &target_module_path + }; + // Detect circular fields let circular_fields = detect_circular_fields("", source_module_path, model_def); @@ -125,7 +137,8 @@ pub fn generate_inline_relation_type_from_def( // Convert SeaORM datetime types to chrono equivalents // This prevents users from needing to import sea_orm::prelude::DateTimeWithTimeZone - let converted_ty = convert_type_with_chrono(&field.ty, source_module_path); + // Use the target model's module path to correctly resolve enum types + let converted_ty = convert_type_with_chrono(&field.ty, effective_module_path); fields.push(InlineField { name: field_ident.clone(), ty: converted_ty, @@ -180,6 +193,16 @@ pub fn generate_inline_relation_type_no_relations_from_def( // Parse the model struct let parsed_model: syn::ItemStruct = syn::parse_str(model_def).ok()?; + // IMPORTANT: Use the TARGET model's module path for type resolution, not the parent's. + // This ensures enum types like `StoryStatus` are resolved to `crate::models::story::StoryStatus` + // instead of incorrectly using the parent module path. + let target_module_path = extract_module_path_from_schema_path(&rel_info.schema_path); + let effective_module_path = if target_module_path.is_empty() { + source_module_path + } else { + &target_module_path + }; + // Get rename_all from model (or default to camelCase) let rename_all = extract_rename_all(&parsed_model.attrs).unwrap_or_else(|| "camelCase".to_string()); @@ -221,7 +244,8 @@ pub fn generate_inline_relation_type_no_relations_from_def( // Convert SeaORM datetime types to chrono equivalents // This prevents users from needing to import sea_orm::prelude::DateTimeWithTimeZone - let converted_ty = convert_type_with_chrono(&field.ty, source_module_path); + // Use the target model's module path to correctly resolve enum types + let converted_ty = convert_type_with_chrono(&field.ty, effective_module_path); fields.push(InlineField { name: field_ident.clone(), ty: converted_ty, diff --git a/crates/vespera_macro/src/schema_macro/type_utils.rs b/crates/vespera_macro/src/schema_macro/type_utils.rs index d5297b1..0489f53 100644 --- a/crates/vespera_macro/src/schema_macro/type_utils.rs +++ b/crates/vespera_macro/src/schema_macro/type_utils.rs @@ -121,6 +121,8 @@ pub fn is_primitive_or_known_type(name: &str) -> bool { | "DateTimeWithTimeZone" | "DateTimeUtc" | "DateTimeLocal" + | "Date" // SeaORM re-export of chrono::NaiveDate + | "Time" // SeaORM re-export of chrono::NaiveTime // UUID | "Uuid" // Serde JSON @@ -175,6 +177,30 @@ pub fn resolve_type_to_absolute_path(ty: &Type, source_module_path: &[String]) - quote! { #(#path_idents)::* :: #type_ident #args } } +/// Extract module path from a schema path TokenStream. +/// +/// The schema_path is something like `crate::models::user::Schema`. +/// This returns `["crate", "models", "user"]` (excluding the final type name). +pub fn extract_module_path_from_schema_path(schema_path: &proc_macro2::TokenStream) -> Vec { + let path_str = schema_path.to_string(); + // Parse segments: "crate :: models :: user :: Schema" -> ["crate", "models", "user", "Schema"] + let segments: Vec<&str> = path_str + .split("::") + .map(|s| s.trim()) + .filter(|s| !s.is_empty()) + .collect(); + + // Return all but the last segment (which is "Schema" or "Entity") + if segments.len() > 1 { + segments[..segments.len() - 1] + .iter() + .map(|s| s.to_string()) + .collect() + } else { + vec![] + } +} + /// Extract the module path from a type (excluding the type name itself). /// e.g., `crate::models::memo::Model` -> ["crate", "models", "memo"] pub fn extract_module_path(ty: &Type) -> Vec { From 9b580a0a5196a30dd0fe4209ddbdc26338586b10 Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Fri, 6 Feb 2026 02:34:37 +0900 Subject: [PATCH 2/2] Add testcode --- .../src/schema_macro/type_utils.rs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/crates/vespera_macro/src/schema_macro/type_utils.rs b/crates/vespera_macro/src/schema_macro/type_utils.rs index 0489f53..62bd9ae 100644 --- a/crates/vespera_macro/src/schema_macro/type_utils.rs +++ b/crates/vespera_macro/src/schema_macro/type_utils.rs @@ -705,4 +705,44 @@ mod tests { let ty: syn::Type = syn::parse_str("Vec>").unwrap(); assert!(is_primitive_like(&ty)); } + + // Tests for extract_module_path_from_schema_path + + #[rstest] + #[case("crate :: models :: user :: Schema", vec!["crate", "models", "user"])] + #[case("crate :: models :: nested :: deep :: Model", vec!["crate", "models", "nested", "deep"])] + #[case("super :: user :: Entity", vec!["super", "user"])] + #[case("super :: Model", vec!["super"])] + #[case("Schema", vec![])] + #[case("Model", vec![])] + fn test_extract_module_path_from_schema_path( + #[case] path_str: &str, + #[case] expected: Vec<&str>, + ) { + let tokens: proc_macro2::TokenStream = path_str.parse().unwrap(); + let result = extract_module_path_from_schema_path(&tokens); + let expected: Vec = expected.into_iter().map(|s| s.to_string()).collect(); + assert_eq!(result, expected); + } + + #[test] + fn test_extract_module_path_from_schema_path_empty() { + let tokens = proc_macro2::TokenStream::new(); + let result = extract_module_path_from_schema_path(&tokens); + assert!(result.is_empty()); + } + + #[test] + fn test_extract_module_path_from_schema_path_with_generics() { + // Even with generics, should extract module path correctly + let tokens: proc_macro2::TokenStream = + "crate :: models :: user :: Schema < T >".parse().unwrap(); + let result = extract_module_path_from_schema_path(&tokens); + // Note: The current implementation splits by "::" which may include generics in last segment + // This test documents current behavior + assert!(!result.is_empty()); + assert_eq!(result[0], "crate"); + assert_eq!(result[1], "models"); + assert_eq!(result[2], "user"); + } }