@@ -29,7 +29,7 @@ use proc_macro2::TokenStream;
2929use quote:: quote;
3030use seaorm:: {
3131 RelationFieldInfo , convert_relation_type_to_schema_with_info, convert_type_with_chrono,
32- extract_sea_orm_default_value, is_sql_function_default,
32+ extract_sea_orm_default_value, has_sea_orm_primary_key , is_sql_function_default,
3333} ;
3434use transformation:: {
3535 build_omit_set, build_partial_config, build_pick_set, build_rename_map, determine_rename_all,
@@ -408,8 +408,8 @@ pub fn generate_schema_type_code(
408408 // that may have ORM-specific attributes we don't want in the generated struct
409409 let serde_field_attrs = extract_field_serde_attrs ( & field. attrs ) ;
410410
411- // Generate serde default + schema(default) from sea_orm(default_value)
412- // Only for non-partial, non-Option fields with literal (non-SQL-function) defaults
411+ // Generate serde default + schema(default) from sea_orm(default_value) or primary_key
412+ // Handles literal defaults, SQL function defaults, and implicit auto-increment
413413 let ( serde_default_attr, schema_default_attr) = generate_sea_orm_default_attrs (
414414 & field. attrs ,
415415 new_type_name,
@@ -622,20 +622,26 @@ pub fn generate_schema_type_code(
622622}
623623
624624/// Generate `#[serde(default = "...")]` and `#[schema(default = "...")]` attributes
625- /// from `#[sea_orm(default_value = ...)]` on source fields.
625+ /// from `#[sea_orm(default_value = ...)]` or `#[sea_orm(primary_key)]` on source fields.
626626///
627627/// Returns `(serde_default_attr, schema_default_attr)` as `TokenStream`s.
628628/// - `serde_default_attr`: `#[serde(default = "default_structname_field")]` for deserialization
629629/// - `schema_default_attr`: `#[schema(default = "value")]` for OpenAPI default value
630630///
631631/// Also generates a companion default function and appends it to `default_functions`.
632632///
633+ /// Handles three categories of defaults:
634+ /// 1. **Literal defaults** (`default_value = "42"`, `"draft"`, `0.7`):
635+ /// Generates parse-based default function + schema default.
636+ /// 2. **SQL function defaults** (`default_value = "NOW()"`, `"gen_random_uuid()"`):
637+ /// Generates type-specific default function + schema default with type's zero value.
638+ /// 3. **Primary key** (implicit auto-increment):
639+ /// Treated as having an implicit default — generates type-specific default.
640+ ///
633641/// Skips serde default generation when:
634- /// - The field type doesn't implement `FromStr` (enums, custom types)
635- /// - The field already has `#[serde(default)]`
636642/// - The field is wrapped in `Option` (partial mode or already optional)
637- ///
638- /// Always generates `#[schema(default)]` for OpenAPI when a literal default exists.
643+ /// - The field already has `#[serde(default)]`
644+ /// - For literal defaults: the field type doesn't implement `FromStr`
639645fn generate_sea_orm_default_attrs (
640646 original_attrs : & [ syn:: Attribute ] ,
641647 struct_name : & syn:: Ident ,
@@ -650,46 +656,129 @@ fn generate_sea_orm_default_attrs(
650656 return ( quote ! { } , quote ! { } ) ;
651657 }
652658
653- // Check for sea_orm(default_value)
654- let Some ( default_value) = extract_sea_orm_default_value ( original_attrs) else {
655- return ( quote ! { } , quote ! { } ) ;
656- } ;
659+ // Check for sea_orm(default_value) and sea_orm(primary_key)
660+ let default_value = extract_sea_orm_default_value ( original_attrs) ;
661+ let has_pk = has_sea_orm_primary_key ( original_attrs) ;
657662
658- // SQL functions like NOW(), CURRENT_TIMESTAMP(), gen_random_uuid()
659- // can't be expressed as concrete JSON defaults — skip entirely.
660- if is_sql_function_default ( & default_value) {
663+ // No default source found
664+ if default_value. is_none ( ) && !has_pk {
661665 return ( quote ! { } , quote ! { } ) ;
662666 }
663667
664- // Generate #[schema(default = "value")] for OpenAPI (always, regardless of type support)
665- let schema_default_attr = quote ! { #[ schema( default = #default_value) ] } ;
666-
667- // Check if field already has serde(default)
668668 let has_existing_serde_default = extract_default ( original_attrs) . is_some ( ) ;
669- if has_existing_serde_default {
670- return ( quote ! { } , schema_default_attr) ;
671- }
672669
673- // Only generate serde default function for types known to implement FromStr
674- if !is_parseable_type ( original_ty) {
675- return ( quote ! { } , schema_default_attr) ;
676- }
670+ match & default_value {
671+ // Literal default (e.g., "42", "draft", "0.7")
672+ Some ( value) if !is_sql_function_default ( value) => {
673+ let schema_default_attr = quote ! { #[ schema( default = #value) ] } ;
674+
675+ if has_existing_serde_default {
676+ return ( quote ! { } , schema_default_attr) ;
677+ }
677678
678- // Generate default function with struct-specific name to avoid collisions:
679- // fn default_{StructName}_{field_name}() -> Type { "value".parse().unwrap() }
680- let fn_name = format ! ( "default_{struct_name}_{field_name}" ) ;
681- let fn_ident = syn:: Ident :: new ( & fn_name, proc_macro2:: Span :: call_site ( ) ) ;
679+ if !is_parseable_type ( original_ty) {
680+ return ( quote ! { } , schema_default_attr) ;
681+ }
682+
683+ let fn_name = format ! ( "default_{struct_name}_{field_name}" ) ;
684+ let fn_ident = syn:: Ident :: new ( & fn_name, proc_macro2:: Span :: call_site ( ) ) ;
685+
686+ default_functions. push ( quote ! {
687+ #[ allow( non_snake_case) ]
688+ fn #fn_ident( ) -> #field_ty {
689+ #value. parse( ) . unwrap( )
690+ }
691+ } ) ;
682692
683- default_functions. push ( quote ! {
684- #[ allow( non_snake_case) ]
685- fn #fn_ident( ) -> #field_ty {
686- #default_value. parse( ) . unwrap( )
693+ let serde_default_attr = quote ! { #[ serde( default = #fn_name) ] } ;
694+ ( serde_default_attr, schema_default_attr)
687695 }
688- } ) ;
696+ // SQL function default (NOW(), gen_random_uuid(), etc.) or primary_key auto-increment
697+ _ => {
698+ let Some ( ( default_expr, schema_default_str) ) =
699+ sql_function_default_for_type ( original_ty)
700+ else {
701+ return ( quote ! { } , quote ! { } ) ;
702+ } ;
703+
704+ let schema_default_attr = quote ! { #[ schema( default = #schema_default_str) ] } ;
705+
706+ if has_existing_serde_default {
707+ return ( quote ! { } , schema_default_attr) ;
708+ }
709+
710+ let fn_name = format ! ( "default_{struct_name}_{field_name}" ) ;
711+ let fn_ident = syn:: Ident :: new ( & fn_name, proc_macro2:: Span :: call_site ( ) ) ;
689712
690- let serde_default_attr = quote ! { #[ serde( default = #fn_name) ] } ;
713+ default_functions. push ( quote ! {
714+ #[ allow( non_snake_case) ]
715+ fn #fn_ident( ) -> #field_ty {
716+ #default_expr
717+ }
718+ } ) ;
719+
720+ let serde_default_attr = quote ! { #[ serde( default = #fn_name) ] } ;
721+ ( serde_default_attr, schema_default_attr)
722+ }
723+ }
724+ }
691725
692- ( serde_default_attr, schema_default_attr)
726+ /// Return a type-appropriate (Rust default expression, OpenAPI default string) pair
727+ /// for fields with SQL function defaults or implicit auto-increment.
728+ ///
729+ /// The Rust expression is used in the generated `#[serde(default = "fn")]` function body.
730+ /// The OpenAPI string is used in `#[schema(default = "value")]`.
731+ fn sql_function_default_for_type ( original_ty : & syn:: Type ) -> Option < ( TokenStream , String ) > {
732+ let syn:: Type :: Path ( type_path) = original_ty else {
733+ return None ;
734+ } ;
735+ let segment = type_path. path . segments . last ( ) ?;
736+ let type_name = segment. ident . to_string ( ) ;
737+
738+ match type_name. as_str ( ) {
739+ "DateTimeWithTimeZone" | "DateTimeUtc" => {
740+ let expr = quote ! {
741+ vespera:: chrono:: DateTime :: <vespera:: chrono:: Utc >:: UNIX_EPOCH . fixed_offset( )
742+ } ;
743+ Some ( ( expr, "1970-01-01T00:00:00+00:00" . to_string ( ) ) )
744+ }
745+ "DateTime" => {
746+ // Could be chrono::DateTime<Tz> — use UTC epoch
747+ let expr = quote ! {
748+ vespera:: chrono:: DateTime :: <vespera:: chrono:: Utc >:: UNIX_EPOCH . fixed_offset( )
749+ } ;
750+ Some ( ( expr, "1970-01-01T00:00:00+00:00" . to_string ( ) ) )
751+ }
752+ "NaiveDateTime" => {
753+ let expr = quote ! {
754+ vespera:: chrono:: NaiveDateTime :: UNIX_EPOCH
755+ } ;
756+ Some ( ( expr, "1970-01-01T00:00:00" . to_string ( ) ) )
757+ }
758+ "NaiveDate" => {
759+ let expr = quote ! {
760+ vespera:: chrono:: NaiveDate :: default ( )
761+ } ;
762+ Some ( ( expr, "1970-01-01" . to_string ( ) ) )
763+ }
764+ "NaiveTime" | "Time" => {
765+ let expr = quote ! {
766+ vespera:: chrono:: NaiveTime :: from_hms_opt( 0 , 0 , 0 ) . unwrap( )
767+ } ;
768+ Some ( ( expr, "00:00:00" . to_string ( ) ) )
769+ }
770+ "Uuid" => Some ( (
771+ quote ! { Default :: default ( ) } ,
772+ "00000000-0000-0000-0000-000000000000" . to_string ( ) ,
773+ ) ) ,
774+ "i8" | "i16" | "i32" | "i64" | "i128" | "isize" | "u8" | "u16" | "u32" | "u64"
775+ | "u128" | "usize" | "f32" | "f64" | "Decimal" => {
776+ Some ( ( quote ! { Default :: default ( ) } , "0" . to_string ( ) ) )
777+ }
778+ "bool" => Some ( ( quote ! { Default :: default ( ) } , "false" . to_string ( ) ) ) ,
779+ "String" => Some ( ( quote ! { Default :: default ( ) } , String :: new ( ) ) ) ,
780+ _ => None ,
781+ }
693782}
694783
695784/// Check if a type is known to implement `FromStr` and can use `.parse().unwrap()`.
0 commit comments