55use proc_macro2:: TokenStream ;
66use quote:: quote;
77use serde_json;
8- use syn:: Type ;
8+ use syn:: { GenericArgument , PathArguments , Type } ;
99
1010/// Primitive type names shared across the crate.
1111/// Used by both `is_primitive_type()` (parser) and `is_parseable_type()` (schema_macro).
@@ -154,6 +154,79 @@ pub fn is_primitive_or_known_type(name: &str) -> bool {
154154 )
155155}
156156
157+ fn resolve_public_type_path ( name : & str ) -> Option < TokenStream > {
158+ match name {
159+ // SeaORM re-exports `serde_json::Value` as `Json`; emit a stable public path.
160+ "Json" | "Value" => Some ( quote ! { vespera:: serde_json:: Value } ) ,
161+ _ => None ,
162+ }
163+ }
164+
165+ fn normalize_known_type_in_generic ( ty : & Type , source_module_path : & [ String ] ) -> TokenStream {
166+ let Type :: Path ( type_path) = ty else {
167+ return quote ! { #ty } ;
168+ } ;
169+
170+ let Some ( segment) = type_path. path . segments . last ( ) else {
171+ return quote ! { #ty } ;
172+ } ;
173+
174+ let ident_str = segment. ident . to_string ( ) ;
175+
176+ if let Some ( public_path) = resolve_public_type_path ( & ident_str) {
177+ return quote ! { #public_path } ;
178+ }
179+
180+ if type_path. path . segments . len ( ) > 1 {
181+ let rendered_segments: Vec < _ > = type_path
182+ . path
183+ . segments
184+ . iter ( )
185+ . map ( |segment| {
186+ let ident = & segment. ident ;
187+ let args = render_path_arguments ( & segment. arguments , source_module_path) ;
188+ quote ! { #ident #args }
189+ } )
190+ . collect ( ) ;
191+
192+ if type_path. path . leading_colon . is_some ( ) {
193+ return quote ! { :: #( #rendered_segments) :: * } ;
194+ }
195+
196+ return quote ! { #( #rendered_segments) :: * } ;
197+ }
198+
199+ if is_primitive_or_known_type ( & ident_str) {
200+ let ident = & segment. ident ;
201+ let args = render_path_arguments ( & segment. arguments , source_module_path) ;
202+ return quote ! { #ident #args } ;
203+ }
204+
205+ quote ! { #ty }
206+ }
207+
208+ fn render_path_arguments ( args : & PathArguments , source_module_path : & [ String ] ) -> TokenStream {
209+ match args {
210+ PathArguments :: None => quote ! { } ,
211+ PathArguments :: AngleBracketed ( angle_args) => {
212+ let rendered_args: Vec < _ > = angle_args
213+ . args
214+ . iter ( )
215+ . map ( |arg| match arg {
216+ GenericArgument :: Type ( inner_ty) => {
217+ let resolved = normalize_known_type_in_generic ( inner_ty, source_module_path) ;
218+ quote ! { #resolved }
219+ }
220+ _ => quote ! { #arg } ,
221+ } )
222+ . collect ( ) ;
223+
224+ quote ! { <#( #rendered_args) , * > }
225+ }
226+ PathArguments :: Parenthesized ( _) => quote ! { #args } ,
227+ }
228+ }
229+
157230/// Resolve a simple type to an absolute path using the source module path.
158231///
159232/// For example, if `source_module_path` is `["crate", "models", "memo"]` and
@@ -166,9 +239,28 @@ pub fn resolve_type_to_absolute_path(ty: &Type, source_module_path: &[String]) -
166239 return quote ! { #ty } ;
167240 } ;
168241
242+ if type_path. path . segments . is_empty ( ) {
243+ return quote ! { #ty } ;
244+ }
245+
169246 // If path has multiple segments (already qualified like `crate::foo::Bar`), return as-is
170247 if type_path. path . segments . len ( ) > 1 {
171- return quote ! { #ty } ;
248+ let rendered_segments: Vec < _ > = type_path
249+ . path
250+ . segments
251+ . iter ( )
252+ . map ( |segment| {
253+ let ident = & segment. ident ;
254+ let args = render_path_arguments ( & segment. arguments , source_module_path) ;
255+ quote ! { #ident #args }
256+ } )
257+ . collect ( ) ;
258+
259+ if type_path. path . leading_colon . is_some ( ) {
260+ return quote ! { :: #( #rendered_segments) :: * } ;
261+ }
262+
263+ return quote ! { #( #rendered_segments) :: * } ;
172264 }
173265
174266 // Get the single segment
@@ -177,15 +269,22 @@ pub fn resolve_type_to_absolute_path(ty: &Type, source_module_path: &[String]) -
177269 } ;
178270
179271 let ident_str = segment. ident . to_string ( ) ;
272+ let args = render_path_arguments ( & segment. arguments , source_module_path) ;
273+
274+ if let Some ( public_path) = resolve_public_type_path ( & ident_str) {
275+ return quote ! { #public_path } ;
276+ }
180277
181278 // If it's a primitive or known type, return as-is
182279 if is_primitive_or_known_type ( & ident_str) {
183- return quote ! { #ty } ;
280+ let type_ident = & segment. ident ;
281+ return quote ! { #type_ident #args } ;
184282 }
185283
186284 // If no source module path, return as-is
187285 if source_module_path. is_empty ( ) {
188- return quote ! { #ty } ;
286+ let type_ident = & segment. ident ;
287+ return quote ! { #type_ident #args } ;
189288 }
190289
191290 // Build absolute path: source_module_path + type_name
@@ -194,7 +293,6 @@ pub fn resolve_type_to_absolute_path(ty: &Type, source_module_path: &[String]) -
194293 . map ( |s| syn:: Ident :: new ( s, proc_macro2:: Span :: call_site ( ) ) )
195294 . collect ( ) ;
196295 let type_ident = & segment. ident ;
197- let args = & segment. arguments ;
198296
199297 quote ! { #( #path_idents) :: * :: #type_ident #args }
200298}
@@ -559,6 +657,33 @@ mod tests {
559657 assert_eq ! ( output. trim( ) , "Decimal" ) ;
560658 }
561659
660+ #[ test]
661+ fn test_resolve_type_to_absolute_path_json_alias_uses_public_path ( ) {
662+ let ty: syn:: Type = syn:: parse_str ( "Json" ) . unwrap ( ) ;
663+ let module_path = vec ! [
664+ "crate" . to_string( ) ,
665+ "models" . to_string( ) ,
666+ "json_case" . to_string( ) ,
667+ ] ;
668+ let tokens = resolve_type_to_absolute_path ( & ty, & module_path) ;
669+ let output = tokens. to_string ( ) ;
670+ assert_eq ! ( output. trim( ) , "vespera :: serde_json :: Value" ) ;
671+ }
672+
673+ #[ test]
674+ fn test_resolve_type_to_absolute_path_known_container_normalizes_inner_json_alias ( ) {
675+ let ty: syn:: Type = syn:: parse_str ( "HashMap<String, Json>" ) . unwrap ( ) ;
676+ let module_path = vec ! [
677+ "crate" . to_string( ) ,
678+ "models" . to_string( ) ,
679+ "json_case" . to_string( ) ,
680+ ] ;
681+ let tokens = resolve_type_to_absolute_path ( & ty, & module_path) ;
682+ let output = tokens. to_string ( ) ;
683+ assert ! ( output. contains( "HashMap < String , vespera :: serde_json :: Value >" ) ) ;
684+ assert ! ( !output. contains( "crate :: models :: json_case :: Json" ) ) ;
685+ }
686+
562687 #[ test]
563688 fn test_resolve_type_to_absolute_path_custom_type ( ) {
564689 let ty: syn:: Type = syn:: parse_str ( "MemoStatus" ) . unwrap ( ) ;
0 commit comments