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,81 @@ 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| {
216+ if let GenericArgument :: Type ( inner_ty) = arg {
217+ let resolved =
218+ normalize_known_type_in_generic ( inner_ty, source_module_path) ;
219+ quote ! { #resolved }
220+ } else {
221+ quote ! { #arg }
222+ }
223+ } )
224+ . collect ( ) ;
225+
226+ quote ! { <#( #rendered_args) , * > }
227+ }
228+ PathArguments :: Parenthesized ( _) => quote ! { #args } ,
229+ }
230+ }
231+
157232/// Resolve a simple type to an absolute path using the source module path.
158233///
159234/// For example, if `source_module_path` is `["crate", "models", "memo"]` and
@@ -166,9 +241,28 @@ pub fn resolve_type_to_absolute_path(ty: &Type, source_module_path: &[String]) -
166241 return quote ! { #ty } ;
167242 } ;
168243
244+ if type_path. path . segments . is_empty ( ) {
245+ return quote ! { #ty } ;
246+ }
247+
169248 // If path has multiple segments (already qualified like `crate::foo::Bar`), return as-is
170249 if type_path. path . segments . len ( ) > 1 {
171- return quote ! { #ty } ;
250+ let rendered_segments: Vec < _ > = type_path
251+ . path
252+ . segments
253+ . iter ( )
254+ . map ( |segment| {
255+ let ident = & segment. ident ;
256+ let args = render_path_arguments ( & segment. arguments , source_module_path) ;
257+ quote ! { #ident #args }
258+ } )
259+ . collect ( ) ;
260+
261+ if type_path. path . leading_colon . is_some ( ) {
262+ return quote ! { :: #( #rendered_segments) :: * } ;
263+ }
264+
265+ return quote ! { #( #rendered_segments) :: * } ;
172266 }
173267
174268 // Get the single segment
@@ -177,15 +271,22 @@ pub fn resolve_type_to_absolute_path(ty: &Type, source_module_path: &[String]) -
177271 } ;
178272
179273 let ident_str = segment. ident . to_string ( ) ;
274+ let args = render_path_arguments ( & segment. arguments , source_module_path) ;
275+
276+ if let Some ( public_path) = resolve_public_type_path ( & ident_str) {
277+ return quote ! { #public_path } ;
278+ }
180279
181280 // If it's a primitive or known type, return as-is
182281 if is_primitive_or_known_type ( & ident_str) {
183- return quote ! { #ty } ;
282+ let type_ident = & segment. ident ;
283+ return quote ! { #type_ident #args } ;
184284 }
185285
186286 // If no source module path, return as-is
187287 if source_module_path. is_empty ( ) {
188- return quote ! { #ty } ;
288+ let type_ident = & segment. ident ;
289+ return quote ! { #type_ident #args } ;
189290 }
190291
191292 // Build absolute path: source_module_path + type_name
@@ -194,7 +295,6 @@ pub fn resolve_type_to_absolute_path(ty: &Type, source_module_path: &[String]) -
194295 . map ( |s| syn:: Ident :: new ( s, proc_macro2:: Span :: call_site ( ) ) )
195296 . collect ( ) ;
196297 let type_ident = & segment. ident ;
197- let args = & segment. arguments ;
198298
199299 quote ! { #( #path_idents) :: * :: #type_ident #args }
200300}
@@ -559,6 +659,33 @@ mod tests {
559659 assert_eq ! ( output. trim( ) , "Decimal" ) ;
560660 }
561661
662+ #[ test]
663+ fn test_resolve_type_to_absolute_path_json_alias_uses_public_path ( ) {
664+ let ty: syn:: Type = syn:: parse_str ( "Json" ) . unwrap ( ) ;
665+ let module_path = vec ! [
666+ "crate" . to_string( ) ,
667+ "models" . to_string( ) ,
668+ "json_case" . to_string( ) ,
669+ ] ;
670+ let tokens = resolve_type_to_absolute_path ( & ty, & module_path) ;
671+ let output = tokens. to_string ( ) ;
672+ assert_eq ! ( output. trim( ) , "vespera :: serde_json :: Value" ) ;
673+ }
674+
675+ #[ test]
676+ fn test_resolve_type_to_absolute_path_known_container_normalizes_inner_json_alias ( ) {
677+ let ty: syn:: Type = syn:: parse_str ( "HashMap<String, Json>" ) . unwrap ( ) ;
678+ let module_path = vec ! [
679+ "crate" . to_string( ) ,
680+ "models" . to_string( ) ,
681+ "json_case" . to_string( ) ,
682+ ] ;
683+ let tokens = resolve_type_to_absolute_path ( & ty, & module_path) ;
684+ let output = tokens. to_string ( ) ;
685+ assert ! ( output. contains( "HashMap < String , vespera :: serde_json :: Value >" ) ) ;
686+ assert ! ( !output. contains( "crate :: models :: json_case :: Json" ) ) ;
687+ }
688+
562689 #[ test]
563690 fn test_resolve_type_to_absolute_path_custom_type ( ) {
564691 let ty: syn:: Type = syn:: parse_str ( "MemoStatus" ) . unwrap ( ) ;
0 commit comments