Skip to content

Commit b533c3c

Browse files
committed
Fix required issue
1 parent 0afce96 commit b533c3c

6 files changed

Lines changed: 50 additions & 263 deletions

File tree

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/mod.rs

Lines changed: 1 addition & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -656,24 +656,8 @@ fn generate_sea_orm_default_attrs(
656656
};
657657

658658
// SQL functions like NOW(), CURRENT_TIMESTAMP(), gen_random_uuid()
659-
// can't be expressed as concrete JSON defaults, but we can make the field
660-
// not-required by generating serde(default) with a dummy default value.
659+
// can't be expressed as concrete JSON defaults — skip entirely.
661660
if is_sql_function_default(&default_value) {
662-
let has_existing_serde_default = extract_default(original_attrs).is_some();
663-
if !has_existing_serde_default
664-
&& let Some(default_body) = sql_function_default_body(original_ty)
665-
{
666-
let fn_name = format!("default_{struct_name}_{field_name}");
667-
let fn_ident = syn::Ident::new(&fn_name, proc_macro2::Span::call_site());
668-
default_functions.push(quote! {
669-
#[allow(non_snake_case)]
670-
fn #fn_ident() -> #field_ty {
671-
#default_body
672-
}
673-
});
674-
let serde_default_attr = quote! { #[serde(default = #fn_name)] };
675-
return (serde_default_attr, quote! {});
676-
}
677661
return (quote! {}, quote! {});
678662
}
679663

@@ -708,52 +692,6 @@ fn generate_sea_orm_default_attrs(
708692
(serde_default_attr, schema_default_attr)
709693
}
710694

711-
/// Generate a dummy default expression for types with SQL function defaults
712-
/// (e.g., `gen_random_uuid()`, `NOW()`).
713-
///
714-
/// Returns `Some(TokenStream)` with the default expression for supported types,
715-
/// `None` for types where a dummy default cannot be generated.
716-
///
717-
/// These defaults exist solely to satisfy `serde(default)` so the field becomes
718-
/// not-required in OpenAPI. The actual value is irrelevant since the database
719-
/// provides the real default.
720-
fn sql_function_default_body(original_ty: &syn::Type) -> Option<TokenStream> {
721-
let syn::Type::Path(type_path) = original_ty else {
722-
return None;
723-
};
724-
let segment = type_path.path.segments.last()?;
725-
let type_name = segment.ident.to_string();
726-
727-
match type_name.as_str() {
728-
// Types implementing Default (returns zero/empty values)
729-
"i8" | "i16" | "i32" | "i64" | "i128" | "isize" | "u8" | "u16" | "u32" | "u64" | "u128"
730-
| "usize" | "f32" | "f64" | "bool" | "String" | "Decimal" | "Uuid" => {
731-
Some(quote! { Default::default() })
732-
}
733-
// SeaORM datetime types → chrono epoch
734-
"DateTimeWithTimeZone" => Some(quote! {
735-
vespera::chrono::DateTime::<vespera::chrono::Utc>::UNIX_EPOCH.fixed_offset()
736-
}),
737-
"DateTimeUtc" => Some(quote! {
738-
vespera::chrono::DateTime::<vespera::chrono::Utc>::UNIX_EPOCH
739-
}),
740-
"DateTimeLocal" => Some(quote! {
741-
vespera::chrono::DateTime::<vespera::chrono::Utc>::UNIX_EPOCH
742-
.with_timezone(&vespera::chrono::Local)
743-
}),
744-
"DateTime" | "NaiveDateTime" => Some(quote! {
745-
vespera::chrono::NaiveDateTime::UNIX_EPOCH
746-
}),
747-
"NaiveDate" | "Date" => Some(quote! {
748-
vespera::chrono::NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()
749-
}),
750-
"NaiveTime" | "Time" => Some(quote! {
751-
vespera::chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap()
752-
}),
753-
_ => None,
754-
}
755-
}
756-
757695
/// Check if a type is known to implement `FromStr` and can use `.parse().unwrap()`.
758696
///
759697
/// Returns true for primitive types, String, and Decimal.

crates/vespera_macro/src/schema_macro/tests.rs

Lines changed: 1 addition & 185 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ fn test_sea_orm_default_attrs_no_default_value() {
311311
}
312312

313313
#[test]
314-
fn test_sea_orm_default_attrs_sql_function_supported_type() {
314+
fn test_sea_orm_default_attrs_sql_function_skips() {
315315
let attrs: Vec<syn::Attribute> = vec![syn::parse_quote!(#[sea_orm(default_value = "NOW()")])];
316316
let struct_name = syn::Ident::new("Test", proc_macro2::Span::call_site());
317317
let ty: syn::Type = syn::parse_str("String").unwrap();
@@ -325,42 +325,8 @@ fn test_sea_orm_default_attrs_sql_function_supported_type() {
325325
false,
326326
&mut fns,
327327
);
328-
// Supported type with SQL function → generates serde(default) to mark field not-required
329-
let serde_str = serde.to_string();
330-
assert!(
331-
serde_str.contains("serde"),
332-
"should have serde default attr: {serde_str}"
333-
);
334-
assert!(
335-
serde_str.contains("default_Test_created_at"),
336-
"should reference generated default fn: {serde_str}"
337-
);
338-
// No JSON default for SQL functions (value is DB-side only)
339-
assert!(schema.is_empty());
340-
// Default function was generated
341-
assert_eq!(fns.len(), 1, "should generate one default function");
342-
}
343-
344-
#[test]
345-
fn test_sea_orm_default_attrs_sql_function_unsupported_type_skips() {
346-
let attrs: Vec<syn::Attribute> =
347-
vec![syn::parse_quote!(#[sea_orm(default_value = "MY_FUNC()")])];
348-
let struct_name = syn::Ident::new("Test", proc_macro2::Span::call_site());
349-
let ty: syn::Type = syn::parse_str("MyCustomType").unwrap();
350-
let mut fns = Vec::new();
351-
let (serde, schema) = generate_sea_orm_default_attrs(
352-
&attrs,
353-
&struct_name,
354-
"custom_field",
355-
&ty,
356-
&ty,
357-
false,
358-
&mut fns,
359-
);
360-
// Unsupported type with SQL function → still skips entirely
361328
assert!(serde.is_empty());
362329
assert!(schema.is_empty());
363-
assert!(fns.is_empty());
364330
}
365331

366332
#[test]
@@ -477,156 +443,6 @@ fn test_generate_schema_type_code_with_partial_fields() {
477443
);
478444
}
479445

480-
// --- Coverage: sql_function_default_body branches ---
481-
482-
#[test]
483-
fn test_sql_function_default_non_path_type_skips() {
484-
// Reference type (&str) is Type::Reference, not Type::Path → sql_function_default_body returns None
485-
let attrs: Vec<syn::Attribute> = vec![syn::parse_quote!(#[sea_orm(default_value = "GEN()")])];
486-
let struct_name = syn::Ident::new("Test", proc_macro2::Span::call_site());
487-
let ty: syn::Type = syn::parse_str("&str").unwrap();
488-
let mut fns = Vec::new();
489-
let (serde, schema) =
490-
generate_sea_orm_default_attrs(&attrs, &struct_name, "val", &ty, &ty, false, &mut fns);
491-
assert!(serde.is_empty());
492-
assert!(schema.is_empty());
493-
assert!(fns.is_empty());
494-
}
495-
496-
#[test]
497-
fn test_sql_function_default_datetime_with_timezone() {
498-
let attrs: Vec<syn::Attribute> = vec![syn::parse_quote!(#[sea_orm(default_value = "NOW()")])];
499-
let struct_name = syn::Ident::new("Test", proc_macro2::Span::call_site());
500-
let ty: syn::Type = syn::parse_str("DateTimeWithTimeZone").unwrap();
501-
let mut fns = Vec::new();
502-
let (serde, schema) =
503-
generate_sea_orm_default_attrs(&attrs, &struct_name, "ts", &ty, &ty, false, &mut fns);
504-
let serde_str = serde.to_string();
505-
assert!(
506-
serde_str.contains("serde"),
507-
"should have serde attr: {serde_str}"
508-
);
509-
assert!(schema.is_empty());
510-
assert_eq!(fns.len(), 1);
511-
let fn_str = fns[0].to_string();
512-
assert!(
513-
fn_str.contains("UNIX_EPOCH"),
514-
"should use epoch default: {fn_str}"
515-
);
516-
}
517-
518-
#[test]
519-
fn test_sql_function_default_datetime_utc() {
520-
let attrs: Vec<syn::Attribute> = vec![syn::parse_quote!(#[sea_orm(default_value = "NOW()")])];
521-
let struct_name = syn::Ident::new("Test", proc_macro2::Span::call_site());
522-
let ty: syn::Type = syn::parse_str("DateTimeUtc").unwrap();
523-
let mut fns = Vec::new();
524-
let (serde, schema) =
525-
generate_sea_orm_default_attrs(&attrs, &struct_name, "ts", &ty, &ty, false, &mut fns);
526-
let serde_str = serde.to_string();
527-
assert!(
528-
serde_str.contains("serde"),
529-
"should have serde attr: {serde_str}"
530-
);
531-
assert!(schema.is_empty());
532-
assert_eq!(fns.len(), 1);
533-
let fn_str = fns[0].to_string();
534-
assert!(
535-
fn_str.contains("UNIX_EPOCH"),
536-
"should use epoch default: {fn_str}"
537-
);
538-
}
539-
540-
#[test]
541-
fn test_sql_function_default_datetime_local() {
542-
let attrs: Vec<syn::Attribute> = vec![syn::parse_quote!(#[sea_orm(default_value = "NOW()")])];
543-
let struct_name = syn::Ident::new("Test", proc_macro2::Span::call_site());
544-
let ty: syn::Type = syn::parse_str("DateTimeLocal").unwrap();
545-
let mut fns = Vec::new();
546-
let (serde, schema) =
547-
generate_sea_orm_default_attrs(&attrs, &struct_name, "ts", &ty, &ty, false, &mut fns);
548-
let serde_str = serde.to_string();
549-
assert!(
550-
serde_str.contains("serde"),
551-
"should have serde attr: {serde_str}"
552-
);
553-
assert!(schema.is_empty());
554-
assert_eq!(fns.len(), 1);
555-
let fn_str = fns[0].to_string();
556-
assert!(
557-
fn_str.contains("UNIX_EPOCH"),
558-
"should use epoch default: {fn_str}"
559-
);
560-
}
561-
562-
#[test]
563-
fn test_sql_function_default_naive_datetime() {
564-
let attrs: Vec<syn::Attribute> = vec![syn::parse_quote!(#[sea_orm(default_value = "NOW()")])];
565-
let struct_name = syn::Ident::new("Test", proc_macro2::Span::call_site());
566-
let ty: syn::Type = syn::parse_str("NaiveDateTime").unwrap();
567-
let mut fns = Vec::new();
568-
let (serde, schema) =
569-
generate_sea_orm_default_attrs(&attrs, &struct_name, "ts", &ty, &ty, false, &mut fns);
570-
let serde_str = serde.to_string();
571-
assert!(
572-
serde_str.contains("serde"),
573-
"should have serde attr: {serde_str}"
574-
);
575-
assert!(schema.is_empty());
576-
assert_eq!(fns.len(), 1);
577-
let fn_str = fns[0].to_string();
578-
assert!(
579-
fn_str.contains("UNIX_EPOCH"),
580-
"should use epoch default: {fn_str}"
581-
);
582-
}
583-
584-
#[test]
585-
fn test_sql_function_default_naive_date() {
586-
let attrs: Vec<syn::Attribute> =
587-
vec![syn::parse_quote!(#[sea_orm(default_value = "CURDATE()")])];
588-
let struct_name = syn::Ident::new("Test", proc_macro2::Span::call_site());
589-
let ty: syn::Type = syn::parse_str("NaiveDate").unwrap();
590-
let mut fns = Vec::new();
591-
let (serde, schema) =
592-
generate_sea_orm_default_attrs(&attrs, &struct_name, "d", &ty, &ty, false, &mut fns);
593-
let serde_str = serde.to_string();
594-
assert!(
595-
serde_str.contains("serde"),
596-
"should have serde attr: {serde_str}"
597-
);
598-
assert!(schema.is_empty());
599-
assert_eq!(fns.len(), 1);
600-
let fn_str = fns[0].to_string();
601-
assert!(
602-
fn_str.contains("from_ymd_opt"),
603-
"should use ymd default: {fn_str}"
604-
);
605-
}
606-
607-
#[test]
608-
fn test_sql_function_default_naive_time() {
609-
let attrs: Vec<syn::Attribute> =
610-
vec![syn::parse_quote!(#[sea_orm(default_value = "CURTIME()")])];
611-
let struct_name = syn::Ident::new("Test", proc_macro2::Span::call_site());
612-
let ty: syn::Type = syn::parse_str("NaiveTime").unwrap();
613-
let mut fns = Vec::new();
614-
let (serde, schema) =
615-
generate_sea_orm_default_attrs(&attrs, &struct_name, "t", &ty, &ty, false, &mut fns);
616-
let serde_str = serde.to_string();
617-
assert!(
618-
serde_str.contains("serde"),
619-
"should have serde attr: {serde_str}"
620-
);
621-
assert!(schema.is_empty());
622-
assert_eq!(fns.len(), 1);
623-
let fn_str = fns[0].to_string();
624-
assert!(
625-
fn_str.contains("from_hms_opt"),
626-
"should use hms default: {fn_str}"
627-
);
628-
}
629-
630446
// --- Coverage: is_parseable_type empty segments ---
631447

632448
#[test]

examples/axum-example/openapi.json

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3045,6 +3045,8 @@
30453045
"userId",
30463046
"memoId",
30473047
"content",
3048+
"createdAt",
3049+
"updatedAt",
30483050
"user",
30493051
"memo"
30503052
]
@@ -3079,7 +3081,8 @@
30793081
"userId",
30803082
"title",
30813083
"content",
3082-
"status"
3084+
"status",
3085+
"createdAt"
30833086
]
30843087
},
30853088
"MemoResponseComments": {
@@ -3166,6 +3169,7 @@
31663169
"title",
31673170
"content",
31683171
"status",
3172+
"createdAt",
31693173
"user"
31703174
]
31713175
},
@@ -3207,6 +3211,8 @@
32073211
"title",
32083212
"content",
32093213
"status",
3214+
"createdAt",
3215+
"updatedAt",
32103216
"user"
32113217
]
32123218
},
@@ -3228,7 +3234,8 @@
32283234
},
32293235
"required": [
32303236
"id",
3231-
"user_id"
3237+
"user_id",
3238+
"created_at"
32323239
]
32333240
},
32343241
"MemoStatus": {
@@ -3919,7 +3926,9 @@
39193926
"required": [
39203927
"id",
39213928
"email",
3922-
"name"
3929+
"name",
3930+
"createdAt",
3931+
"updatedAt"
39233932
]
39243933
},
39253934
"UserSummary": {
@@ -3986,7 +3995,9 @@
39863995
}
39873996
},
39883997
"required": [
3989-
"name"
3998+
"id",
3999+
"name",
4000+
"createdAt"
39904001
]
39914002
}
39924003
}

0 commit comments

Comments
 (0)