@@ -75,10 +75,22 @@ pub(super) fn generate_sea_orm_default_attrs(
7575 let fn_name = format ! ( "default_{struct_name}_{field_name}" ) ;
7676 let fn_ident = syn:: Ident :: new ( & fn_name, proc_macro2:: Span :: call_site ( ) ) ;
7777
78+ // Validate the literal against the field's type at macro-expansion
79+ // time: a malformed `default_value` (e.g. `"abc"` on an `i32`, or
80+ // `"300"` on a `u8`) becomes a COMPILE error pointing at the field
81+ // instead of the runtime panic the generated `#value.parse().unwrap()`
82+ // would raise the first time serde fills a missing field. A valid
83+ // literal keeps the byte-identical prior `.parse().unwrap()` body, so
84+ // no currently-valid default changes behaviour.
85+ let fn_body = match validate_literal_default ( value, original_ty) {
86+ Ok ( ( ) ) => quote ! { #value. parse( ) . unwrap( ) } ,
87+ Err ( msg) => syn:: Error :: new_spanned ( original_ty, msg) . to_compile_error ( ) ,
88+ } ;
89+
7890 default_functions. push ( quote ! {
7991 #[ allow( non_snake_case) ]
8092 fn #fn_ident( ) -> #field_ty {
81- #value . parse ( ) . unwrap ( )
93+ #fn_body
8294 }
8395 } ) ;
8496
@@ -180,6 +192,64 @@ pub(super) fn is_parseable_type(ty: &syn::Type) -> bool {
180192 type_utils:: PRIMITIVE_TYPE_NAMES . contains ( & segment. ident . to_string ( ) . as_str ( ) )
181193}
182194
195+ /// Validate a literal `default_value` against the field's type **at
196+ /// macro-expansion time**, mirroring exactly the runtime `#value.parse()`
197+ /// the generated default function performs (no trimming — the generated
198+ /// code does not trim either, so this predicts the runtime result precisely).
199+ ///
200+ /// Returns `Err(msg)` when the literal cannot parse to the concrete field
201+ /// type, so the caller emits a `compile_error!` (pointing at the field)
202+ /// instead of generating a `.parse().unwrap()` that panics the first time
203+ /// serde fills a missing field. Types whose `FromStr` cannot be faithfully
204+ /// reproduced here return `Ok(())`:
205+ /// - `String` — its `FromStr` is infallible.
206+ /// - `Decimal` — needs the `rust_decimal` runtime crate; left to runtime.
207+ /// - any non-primitive / unknown type — already gated out by
208+ /// [`is_parseable_type`] before this is reached.
209+ fn validate_literal_default ( value : & str , ty : & syn:: Type ) -> Result < ( ) , String > {
210+ let syn:: Type :: Path ( type_path) = ty else {
211+ return Ok ( ( ) ) ;
212+ } ;
213+ let Some ( segment) = type_path. path . segments . last ( ) else {
214+ return Ok ( ( ) ) ;
215+ } ;
216+ let type_name = segment. ident . to_string ( ) ;
217+
218+ // Parse against the EXACT field type so a range violation (e.g. `"300"`
219+ // on a `u8`) is caught, not just a syntactic one. The message carries
220+ // the offending value and type plus the underlying `FromStr` error — the
221+ // same error the runtime `.unwrap()` would have panicked with.
222+ macro_rules! check {
223+ ( $t: ty) => {
224+ value
225+ . parse:: <$t>( )
226+ . map( |_| ( ) )
227+ . map_err( |e| format!( "invalid default_value {value:?} for `{type_name}`: {e}" ) )
228+ } ;
229+ }
230+
231+ match type_name. as_str ( ) {
232+ "i8" => check ! ( i8 ) ,
233+ "i16" => check ! ( i16 ) ,
234+ "i32" => check ! ( i32 ) ,
235+ "i64" => check ! ( i64 ) ,
236+ "i128" => check ! ( i128 ) ,
237+ "isize" => check ! ( isize ) ,
238+ "u8" => check ! ( u8 ) ,
239+ "u16" => check ! ( u16 ) ,
240+ "u32" => check ! ( u32 ) ,
241+ "u64" => check ! ( u64 ) ,
242+ "u128" => check ! ( u128 ) ,
243+ "usize" => check ! ( usize ) ,
244+ "f32" => check ! ( f32 ) ,
245+ "f64" => check ! ( f64 ) ,
246+ "bool" => check ! ( bool ) ,
247+ // `String::FromStr` is infallible; `Decimal` needs the runtime crate.
248+ // Everything else is gated out by `is_parseable_type` before this call.
249+ _ => Ok ( ( ) ) ,
250+ }
251+ }
252+
183253#[ cfg( test) ]
184254mod tests {
185255 use std:: collections:: HashMap ;
@@ -196,10 +266,102 @@ mod tests {
196266 items. into_iter ( ) . map ( |s| ( s. name . clone ( ) , s) ) . collect ( )
197267 }
198268
269+ // ======================================
270+ // validate_literal_default tests
271+ // ======================================
272+
273+ #[ test]
274+ fn validate_literal_default_accepts_valid_primitives ( ) {
275+ let i32_ty: syn:: Type = syn:: parse_str ( "i32" ) . unwrap ( ) ;
276+ assert ! ( validate_literal_default( "42" , & i32_ty) . is_ok( ) ) ;
277+ let u8_ty: syn:: Type = syn:: parse_str ( "u8" ) . unwrap ( ) ;
278+ assert ! ( validate_literal_default( "255" , & u8_ty) . is_ok( ) ) ;
279+ let f64_ty: syn:: Type = syn:: parse_str ( "f64" ) . unwrap ( ) ;
280+ assert ! ( validate_literal_default( "0.7" , & f64_ty) . is_ok( ) ) ;
281+ let bool_ty: syn:: Type = syn:: parse_str ( "bool" ) . unwrap ( ) ;
282+ assert ! ( validate_literal_default( "true" , & bool_ty) . is_ok( ) ) ;
283+ // String FromStr is infallible; Decimal is intentionally left to runtime.
284+ let string_ty: syn:: Type = syn:: parse_str ( "String" ) . unwrap ( ) ;
285+ assert ! ( validate_literal_default( "anything at all" , & string_ty) . is_ok( ) ) ;
286+ let decimal_ty: syn:: Type = syn:: parse_str ( "Decimal" ) . unwrap ( ) ;
287+ assert ! ( validate_literal_default( "not-validated-here" , & decimal_ty) . is_ok( ) ) ;
288+ }
289+
290+ #[ test]
291+ fn validate_literal_default_rejects_unparseable_and_out_of_range ( ) {
292+ let i32_ty: syn:: Type = syn:: parse_str ( "i32" ) . unwrap ( ) ;
293+ assert ! ( validate_literal_default( "abc" , & i32_ty) . is_err( ) ) ;
294+ // Range violation caught against the EXACT type, not a generic integer.
295+ let u8_ty: syn:: Type = syn:: parse_str ( "u8" ) . unwrap ( ) ;
296+ assert ! ( validate_literal_default( "300" , & u8_ty) . is_err( ) ) ;
297+ let bool_ty: syn:: Type = syn:: parse_str ( "bool" ) . unwrap ( ) ;
298+ assert ! ( validate_literal_default( "maybe" , & bool_ty) . is_err( ) ) ;
299+ let f64_ty: syn:: Type = syn:: parse_str ( "f64" ) . unwrap ( ) ;
300+ assert ! ( validate_literal_default( "3.14.15" , & f64_ty) . is_err( ) ) ;
301+ }
302+
199303 // ======================================
200304 // generate_sea_orm_default_attrs tests
201305 // ======================================
202306
307+ #[ test]
308+ fn test_sea_orm_default_attrs_valid_literal_keeps_parse_unwrap ( ) {
309+ let attrs: Vec < syn:: Attribute > = vec ! [ syn:: parse_quote!( #[ sea_orm( default_value = "42" ) ] ) ] ;
310+ let struct_name = syn:: Ident :: new ( "Test" , proc_macro2:: Span :: call_site ( ) ) ;
311+ let ty: syn:: Type = syn:: parse_str ( "i32" ) . unwrap ( ) ;
312+ let mut fns = Vec :: new ( ) ;
313+ let ( serde, _schema) = generate_sea_orm_default_attrs (
314+ & attrs,
315+ & struct_name,
316+ "count" ,
317+ & ty,
318+ & ty,
319+ false ,
320+ & mut fns,
321+ ) ;
322+ assert ! ( serde. to_string( ) . contains( "serde" ) ) ;
323+ assert_eq ! ( fns. len( ) , 1 ) ;
324+ let body = fns[ 0 ] . to_string ( ) ;
325+ assert ! ( body. contains( "parse" ) , "valid literal keeps parse: {body}" ) ;
326+ assert ! ( body. contains( "unwrap" ) , "valid literal keeps unwrap: {body}" ) ;
327+ assert ! (
328+ !body. contains( "compile_error" ) ,
329+ "valid literal must not emit compile_error: {body}"
330+ ) ;
331+ }
332+
333+ #[ test]
334+ fn test_sea_orm_default_attrs_invalid_literal_emits_compile_error ( ) {
335+ // `"abc"` cannot parse to i32: the generated default function body must
336+ // be a compile_error (pointing at the field) instead of a runtime
337+ // `.parse().unwrap()` that would panic when serde fills a missing field.
338+ let attrs: Vec < syn:: Attribute > =
339+ vec ! [ syn:: parse_quote!( #[ sea_orm( default_value = "abc" ) ] ) ] ;
340+ let struct_name = syn:: Ident :: new ( "Test" , proc_macro2:: Span :: call_site ( ) ) ;
341+ let ty: syn:: Type = syn:: parse_str ( "i32" ) . unwrap ( ) ;
342+ let mut fns = Vec :: new ( ) ;
343+ let ( serde, _schema) = generate_sea_orm_default_attrs (
344+ & attrs,
345+ & struct_name,
346+ "count" ,
347+ & ty,
348+ & ty,
349+ false ,
350+ & mut fns,
351+ ) ;
352+ assert ! ( serde. to_string( ) . contains( "serde" ) ) ;
353+ assert_eq ! ( fns. len( ) , 1 ) ;
354+ let body = fns[ 0 ] . to_string ( ) ;
355+ assert ! (
356+ body. contains( "compile_error" ) ,
357+ "invalid literal must emit compile_error: {body}"
358+ ) ;
359+ assert ! (
360+ !body. contains( "unwrap" ) ,
361+ "invalid literal must not emit a runtime parse().unwrap(): {body}"
362+ ) ;
363+ }
364+
203365 #[ test]
204366 fn test_sea_orm_default_attrs_optional_field_skips ( ) {
205367 let attrs: Vec < syn:: Attribute > = vec ! [ syn:: parse_quote!( #[ sea_orm( default_value = "42" ) ] ) ] ;
0 commit comments