Skip to content

Commit 7b3b35e

Browse files
authored
Merge pull request #71 from dev-five-git/fix-enum-issue
Fix enum issue
2 parents 6a051a8 + 9b580a0 commit 7b3b35e

4 files changed

Lines changed: 97 additions & 6 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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"}

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/vespera_macro/src/schema_macro/inline_types.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ use super::{
1010
circular::detect_circular_fields,
1111
file_lookup::find_model_from_schema_path,
1212
seaorm::{RelationFieldInfo, convert_type_with_chrono},
13-
type_utils::{is_seaorm_relation_type, snake_to_pascal_case},
13+
type_utils::{
14+
extract_module_path_from_schema_path, is_seaorm_relation_type, snake_to_pascal_case,
15+
},
1416
};
1517
use crate::parser::{extract_rename_all, extract_skip};
1618

@@ -69,6 +71,16 @@ pub fn generate_inline_relation_type_from_def(
6971
// Parse the model struct
7072
let parsed_model: syn::ItemStruct = syn::parse_str(model_def).ok()?;
7173

74+
// IMPORTANT: Use the TARGET model's module path for type resolution, not the parent's.
75+
// This ensures enum types like `AuthProvider` are resolved to `crate::models::user::AuthProvider`
76+
// instead of incorrectly using the parent module path.
77+
let target_module_path = extract_module_path_from_schema_path(&rel_info.schema_path);
78+
let effective_module_path = if target_module_path.is_empty() {
79+
source_module_path
80+
} else {
81+
&target_module_path
82+
};
83+
7284
// Detect circular fields
7385
let circular_fields = detect_circular_fields("", source_module_path, model_def);
7486

@@ -125,7 +137,8 @@ pub fn generate_inline_relation_type_from_def(
125137

126138
// Convert SeaORM datetime types to chrono equivalents
127139
// This prevents users from needing to import sea_orm::prelude::DateTimeWithTimeZone
128-
let converted_ty = convert_type_with_chrono(&field.ty, source_module_path);
140+
// Use the target model's module path to correctly resolve enum types
141+
let converted_ty = convert_type_with_chrono(&field.ty, effective_module_path);
129142
fields.push(InlineField {
130143
name: field_ident.clone(),
131144
ty: converted_ty,
@@ -180,6 +193,16 @@ pub fn generate_inline_relation_type_no_relations_from_def(
180193
// Parse the model struct
181194
let parsed_model: syn::ItemStruct = syn::parse_str(model_def).ok()?;
182195

196+
// IMPORTANT: Use the TARGET model's module path for type resolution, not the parent's.
197+
// This ensures enum types like `StoryStatus` are resolved to `crate::models::story::StoryStatus`
198+
// instead of incorrectly using the parent module path.
199+
let target_module_path = extract_module_path_from_schema_path(&rel_info.schema_path);
200+
let effective_module_path = if target_module_path.is_empty() {
201+
source_module_path
202+
} else {
203+
&target_module_path
204+
};
205+
183206
// Get rename_all from model (or default to camelCase)
184207
let rename_all =
185208
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(
221244

222245
// Convert SeaORM datetime types to chrono equivalents
223246
// This prevents users from needing to import sea_orm::prelude::DateTimeWithTimeZone
224-
let converted_ty = convert_type_with_chrono(&field.ty, source_module_path);
247+
// Use the target model's module path to correctly resolve enum types
248+
let converted_ty = convert_type_with_chrono(&field.ty, effective_module_path);
225249
fields.push(InlineField {
226250
name: field_ident.clone(),
227251
ty: converted_ty,

crates/vespera_macro/src/schema_macro/type_utils.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ pub fn is_primitive_or_known_type(name: &str) -> bool {
121121
| "DateTimeWithTimeZone"
122122
| "DateTimeUtc"
123123
| "DateTimeLocal"
124+
| "Date" // SeaORM re-export of chrono::NaiveDate
125+
| "Time" // SeaORM re-export of chrono::NaiveTime
124126
// UUID
125127
| "Uuid"
126128
// Serde JSON
@@ -175,6 +177,30 @@ pub fn resolve_type_to_absolute_path(ty: &Type, source_module_path: &[String]) -
175177
quote! { #(#path_idents)::* :: #type_ident #args }
176178
}
177179

180+
/// Extract module path from a schema path TokenStream.
181+
///
182+
/// The schema_path is something like `crate::models::user::Schema`.
183+
/// This returns `["crate", "models", "user"]` (excluding the final type name).
184+
pub fn extract_module_path_from_schema_path(schema_path: &proc_macro2::TokenStream) -> Vec<String> {
185+
let path_str = schema_path.to_string();
186+
// Parse segments: "crate :: models :: user :: Schema" -> ["crate", "models", "user", "Schema"]
187+
let segments: Vec<&str> = path_str
188+
.split("::")
189+
.map(|s| s.trim())
190+
.filter(|s| !s.is_empty())
191+
.collect();
192+
193+
// Return all but the last segment (which is "Schema" or "Entity")
194+
if segments.len() > 1 {
195+
segments[..segments.len() - 1]
196+
.iter()
197+
.map(|s| s.to_string())
198+
.collect()
199+
} else {
200+
vec![]
201+
}
202+
}
203+
178204
/// Extract the module path from a type (excluding the type name itself).
179205
/// e.g., `crate::models::memo::Model` -> ["crate", "models", "memo"]
180206
pub fn extract_module_path(ty: &Type) -> Vec<String> {
@@ -679,4 +705,44 @@ mod tests {
679705
let ty: syn::Type = syn::parse_str("Vec<DateTime<Utc>>").unwrap();
680706
assert!(is_primitive_like(&ty));
681707
}
708+
709+
// Tests for extract_module_path_from_schema_path
710+
711+
#[rstest]
712+
#[case("crate :: models :: user :: Schema", vec!["crate", "models", "user"])]
713+
#[case("crate :: models :: nested :: deep :: Model", vec!["crate", "models", "nested", "deep"])]
714+
#[case("super :: user :: Entity", vec!["super", "user"])]
715+
#[case("super :: Model", vec!["super"])]
716+
#[case("Schema", vec![])]
717+
#[case("Model", vec![])]
718+
fn test_extract_module_path_from_schema_path(
719+
#[case] path_str: &str,
720+
#[case] expected: Vec<&str>,
721+
) {
722+
let tokens: proc_macro2::TokenStream = path_str.parse().unwrap();
723+
let result = extract_module_path_from_schema_path(&tokens);
724+
let expected: Vec<String> = expected.into_iter().map(|s| s.to_string()).collect();
725+
assert_eq!(result, expected);
726+
}
727+
728+
#[test]
729+
fn test_extract_module_path_from_schema_path_empty() {
730+
let tokens = proc_macro2::TokenStream::new();
731+
let result = extract_module_path_from_schema_path(&tokens);
732+
assert!(result.is_empty());
733+
}
734+
735+
#[test]
736+
fn test_extract_module_path_from_schema_path_with_generics() {
737+
// Even with generics, should extract module path correctly
738+
let tokens: proc_macro2::TokenStream =
739+
"crate :: models :: user :: Schema < T >".parse().unwrap();
740+
let result = extract_module_path_from_schema_path(&tokens);
741+
// Note: The current implementation splits by "::" which may include generics in last segment
742+
// This test documents current behavior
743+
assert!(!result.is_empty());
744+
assert_eq!(result[0], "crate");
745+
assert_eq!(result[1], "models");
746+
assert_eq!(result[2], "user");
747+
}
682748
}

0 commit comments

Comments
 (0)