rusticx_derive/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{
4    parse_macro_input, Attribute, Data, DeriveInput, Expr, Ident, Meta, MetaNameValue, Type,
5    TypePath,
6};
7
8/// Derives the `SQLModel` trait for a struct, allowing it to be used as a database model.
9///
10/// This macro automatically generates the necessary implementations for the `SQLModel`
11/// trait based on the struct's fields and the attributes applied to them.
12///
13/// # Usage
14///
15/// Apply `#[derive(Model)]` to your struct definition. You can also use `#[model(...)]`
16/// attributes on the struct itself and on individual fields to configure the model
17/// mapping and behavior.
18///
19/// ```rust
20/// use rusticx_derive::Model; // Assuming the macro is in a crate named rusticx_derive
21/// use uuid::Uuid; // Assuming you use the 'uuid' crate
22/// use chrono::NaiveDateTime; // Assuming you use the 'chrono' crate
23///
24/// #[derive(Model, Debug, serde::Serialize, serde::Deserialize)]
25/// #[model(table = "my_users")] // Optional: specify a custom table name
26/// struct User {
27///     #[model(primary_key, auto_increment)] // Marks 'id' as primary key with auto-increment
28///     // #[model(primary_key, uuid)] // Alternatively, for UUID primary keys
29///     id: Option<i32>, // Use Option<i32> for auto-increment, Uuid for uuid
30///
31///     name: String, // Maps to a text/varchar column
32///
33///     #[model(column = "user_age")] // Optional: specify a custom column name
34///     age: i32, // Maps to an integer column
35///
36///     #[model(nullable)] // Marks the 'email' column as nullable
37///     email: Option<String>,
38///
39///     #[model(default = "'active'")] // Sets a default value for the 'status' column
40///     status: String,
41///
42///     #[model(sql_type = "JSONB")] // Specify a custom SQL type
43///     metadata: serde_json::Value,
44///
45///     #[model(skip)] // This field will be ignored by the ORM
46///     temp_data: String,
47///
48///     #[model(auto_increment)] // Only valid on primary_key fields, will be ignored otherwise
49///     another_id: i32,
50///
51///     #[model(uuid)] // Can be used on non-primary key UUID fields if needed
52///     unique_id: Uuid,
53///
54///     created_at: NaiveDateTime, // Maps to a datetime column
55/// }
56/// ```
57///
58/// # Struct Attributes (`#[model(...)]` on the struct)
59///
60/// * `#[model(table = "custom_name")]`: Specifies the database table name for this model.
61///     Defaults to the struct name (e.g., `User` -> `User`).
62///
63/// # Field Attributes (`#[model(...)]` on fields)
64///
65/// * `#[model(primary_key)]`: Designates this field as the primary key for the table.
66///     Exactly one field should be marked as the primary key.
67/// * `#[model(column = "custom_name")]`: Specifies the database column name for this field.
68///     Defaults to the field name converted to lowercase.
69/// * `#[model(default = "SQL_DEFAULT_VALUE")]`: Sets a SQL default value for the column.
70///     The value is inserted directly into the SQL `CREATE TABLE` statement. Use
71///     appropriate quoting for string literals (e.g., `"'active'"`).
72/// * `#[model(nullable)]`: Explicitly marks the column as nullable (`NULL` in SQL).
73///     Fields with `Option<T>` type are automatically treated as nullable. This attribute
74///     is useful for non-Option types that should still allow `NULL`.
75/// * `#[model(sql_type = "SQL_TYPE_STRING")]`: Specifies a custom SQL data type for the column.
76///     This overrides the default type mapping based on the Rust type.
77/// * `#[model(skip)]`: Excludes this field from the generated SQL model definition (CREATE TABLE,
78///     INSERT, UPDATE) and from deserialization (`from_row`).
79/// * `#[model(auto_increment)]`: Applicable only to `primary_key` fields. Adds the
80///     database-specific syntax for auto-incrementing integer primary keys (`SERIAL` or
81///     `GENERATED ALWAYS AS IDENTITY` for PostgreSQL, `AUTO_INCREMENT` for MySQL,
82///     `AUTOINCREMENT` for SQLite). The field type *must* be an integer type, usually `Option<i32>`.
83/// * `#[model(uuid)]`: Applicable only to `primary_key` fields. Adds database-specific
84///     default value generation for UUID primary keys (`gen_random_uuid()` for PostgreSQL,
85///     `UUID()` for MySQL, and a standard UUID generation expression for SQLite). The field
86///     type *must* be `uuid::Uuid` or `Option<uuid::Uuid>`.
87///
88/// # Generated SQL Types Mapping
89///
90/// The macro attempts to infer SQL types based on common Rust types:
91/// * `i8`, `i16`, `i32`, `u8`, `u16`, `u32`: `INTEGER`
92/// * `i64`, `u64`: `BIGINT`
93/// * `f32`, `f64`: `FLOAT`
94/// * `bool`: `BOOLEAN`
95/// * `String`, `str`: `TEXT`
96/// * `Uuid` (from `uuid` crate): `TEXT` (UUIDs are typically stored as text or byte arrays)
97/// * `NaiveDate` (from `chrono` crate): `DATE`
98/// * `NaiveTime` (from `chrono` crate): `TIME`
99/// * `NaiveDateTime`, `DateTime` (from `chrono` crate): `DATETIME` or `TIMESTAMP` depending on DB
100/// * `Vec<u8>`: `BLOB`
101/// * `Option<T>`: The underlying type `T`'s mapping is used, and the column is marked nullable.
102///
103/// You can override this mapping using `#[model(sql_type = "...")]`.
104///
105/// # Requirements
106///
107/// * The derived struct must have named fields.
108/// * The struct must derive `serde::Deserialize` for `from_row` to work.
109/// * If using `Uuid` or `chrono` types, ensure the respective crates are in your `Cargo.toml`.
110/// * If using the `uuid` attribute on a primary key, the field type must be `Uuid` or `Option<Uuid>`.
111/// * If using the `auto_increment` attribute on a primary key, the field type must be an integer type, typically `Option<i32>`.
112#[proc_macro_derive(Model, attributes(model))]
113pub fn derive_model(input: TokenStream) -> TokenStream {
114    // Parse the input token stream into a DeriveInput syntax tree
115    let input = parse_macro_input!(input as DeriveInput);
116    // Get the name of the struct
117    let name = &input.ident;
118
119    // Extract the table name from the struct attributes. If not found,
120    // default to the struct name as is (without pluralizing or lowercasing).
121    let table_name = extract_table_name(&input.attrs)
122        .unwrap_or_else(|| name.to_string());
123
124    // Ensure the derived item is a struct with named fields.
125    // Panic otherwise with a descriptive error message.
126    let fields = match &input.data {
127        Data::Struct(data) => match &data.fields {
128            syn::Fields::Named(fields) => &fields.named,
129            _ => panic!("#[derive(Model)] can only be applied to structs with named fields"),
130        },
131        _ => panic!("#[derive(Model)] can only be applied to structs"),
132    };
133
134    // Variables to collect information about fields for code generation
135    let mut primary_key_field: Option<Ident> = None;
136    let mut primary_key_type: Option<Type> = None;
137    let mut _pk_is_auto_increment = false; // Track if PK is auto-increment (for generated code logic if needed)
138    let mut pk_is_uuid = false; // Track if PK is UUID (for generated code logic if needed)
139    let mut field_sql_defs = Vec::new(); // Collect SQL column definitions (name, type, constraints)
140    let mut field_names = Vec::new(); // Collect database column names
141    let mut field_to_sql_values = Vec::new(); // Collect code snippets for extracting field values for SQL binding
142    let mut field_from_row = Vec::new(); // Collect code snippets for deserializing fields from a row (JSON value)
143    let mut field_idents = Vec::new(); // Collect original field idents
144    let mut field_str_names = Vec::new(); // Collect original field names as strings
145
146    // Iterate over each field in the struct
147    for field in fields {
148        let field_ident = field.ident.clone().unwrap(); // Get the field identifier
149        let field_name = field_ident.to_string(); // Get the field name as a string
150        let mut column_name = field_name.clone(); // Initialize column name, defaults to field name
151        let mut is_primary_key = false;
152        let mut has_default = false;
153        let mut default_value = String::new();
154        let mut is_nullable = false; // Explicit #[model(nullable)]
155        let mut custom_type = None; // #[model(sql_type = "...")]
156        let mut skip = false; // #[model(skip)]
157        let mut auto_increment = false; // #[model(auto_increment)]
158        let mut uuid_pk = false; // #[model(uuid)] for primary key
159
160        // Process attributes on the current field
161        for attr in &field.attrs {
162            // Check if the attribute is our custom #[model(...)] attribute
163            if !attr.path().is_ident("model") {
164                continue;
165            }
166
167            // Parse the attribute's arguments (e.g., primary_key, column="...")
168            let parsed = attr.parse_args_with(
169                syn::punctuated::Punctuated::<Meta, syn::token::Comma>::parse_terminated,
170            );
171
172            // Process the parsed meta items within the attribute
173            if let Ok(items) = parsed {
174                for meta in items {
175                    match meta {
176                        // Handle flag attributes like `primary_key` or `nullable`
177                        Meta::Path(path) => {
178                            if path.is_ident("primary_key") {
179                                is_primary_key = true;
180                                primary_key_field = Some(field_ident.clone());
181                                primary_key_type = Some(field.ty.clone());
182                            } else if path.is_ident("nullable") {
183                                is_nullable = true;
184                            } else if path.is_ident("skip") {
185                                skip = true;
186                            } else if path.is_ident("auto_increment") {
187                                auto_increment = true;
188                                _pk_is_auto_increment = true; // Mark PK as auto-incrementing globally
189                            } else if path.is_ident("uuid") {
190                                uuid_pk = true;
191                                pk_is_uuid = true; // Mark PK as UUID globally
192                            }
193                        }
194                        // Handle name-value attributes like `column = "..."` or `default = "..."`
195                        Meta::NameValue(MetaNameValue { path, value, .. }) => {
196                            if path.is_ident("column") {
197                                if let Expr::Lit(expr_lit) = value {
198                                    if let syn::Lit::Str(lit_str) = expr_lit.lit {
199                                        column_name = lit_str.value(); // Set custom column name
200                                    }
201                                }
202                            } else if path.is_ident("default") {
203                                if let Expr::Lit(expr_lit) = value {
204                                    if let syn::Lit::Str(lit_str) = expr_lit.lit {
205                                        has_default = true;
206                                        default_value = lit_str.value(); // Set default value string
207                                    }
208                                }
209                            } else if path.is_ident("sql_type") {
210                                if let Expr::Lit(expr_lit) = value {
211                                    if let syn::Lit::Str(lit_str) = expr_lit.lit {
212                                        custom_type = Some(lit_str.value()); // Set custom SQL type string
213                                    }
214                                }
215                            }
216                        }
217                        _ => {
218                            // Ignore other meta types for forward compatibility
219                        }
220                    }
221                }
222            } else {
223                // Handle parsing errors for the attribute arguments
224                let err = parsed.unwrap_err();
225                return TokenStream::from(err.to_compile_error());
226            }
227        }
228
229        // If the field is marked to be skipped, continue to the next field
230        if skip {
231            continue;
232        }
233
234        // Store field information for later use in generated code
235        field_idents.push(field_ident.clone());
236        field_str_names.push(field_name.clone());
237        field_names.push(column_name.clone());
238
239        // Generate code snippet to extract the field's value.
240        // Assumes the field type implements `Clone` and can be converted to `Box<dyn rusticx::ToSqlConvert>`.
241        // The `rusticx::ToSqlConvert` trait would need to handle the actual type-specific conversion.
242        let field_to_sql_value = quote! {
243             // Clone the field value and box it as a trait object.
244             // The `rusticx::ToSqlConvert` trait should provide a method
245             // to convert the underlying type to database-specific parameters.
246            Box::new(self.#field_ident.clone()) as Box<dyn rusticx::ToSqlConvert>
247        };
248        field_to_sql_values.push(field_to_sql_value);
249
250        // Determine if the field is semantically optional (either Option<T> or explicitly nullable)
251        let is_option = is_nullable || is_option_type(&field.ty);
252        // Generate code snippet to deserialize the field from a JSON value (representing a database row)
253        let field_from_json = generate_from_json(&field_ident, &column_name, &field.ty, is_option);
254        field_from_row.push(field_from_json);
255
256        // Determine the SQL type definition based on custom type or Rust type mapping
257        let sql_type = if let Some(custom) = custom_type {
258            // If a custom SQL type is specified, use it
259            quote! { rusticx::SqlType::Custom(#custom.to_string()) }
260        } else {
261            // Otherwise, map the Rust type to a generic SqlType enum variant
262            let rust_type = &field.ty;
263            generate_sql_type(rust_type) // Calls helper function for mapping
264        };
265
266        // Generate the SQL column definition string part (e.g., "name TEXT NOT NULL")
267        let sql_def = quote! {
268            {
269                // Start with column name and its determined SQL type based on DB type
270                let mut part = format!("\"{}\" {}", #column_name, match db_type {
271                    rusticx::DatabaseType::PostgreSQL => #sql_type.pg_type().to_string(),
272                    rusticx::DatabaseType::MySQL => #sql_type.mysql_type().to_string(),
273                    rusticx::DatabaseType::SQLite => #sql_type.sqlite_type().to_string(),
274                });
275
276                // Add PRIMARY KEY constraint if applicable
277                if #is_primary_key {
278                    part.push_str(" PRIMARY KEY");
279
280                    // Add auto-increment or UUID default based on database type
281                    if #auto_increment {
282                        // Auto-increment specific syntax per database
283                        match db_type {
284                            rusticx::DatabaseType::PostgreSQL => part.push_str(" GENERATED ALWAYS AS IDENTITY"),
285                            rusticx::DatabaseType::MySQL => part.push_str(" AUTO_INCREMENT"),
286                            rusticx::DatabaseType::SQLite => part.push_str(" AUTOINCREMENT"),
287                        }
288                    } else if #uuid_pk {
289                         // UUID default function specific syntax per database
290                        match db_type {
291                            rusticx::DatabaseType::PostgreSQL => part.push_str(" DEFAULT gen_random_uuid()"),
292                            // MySQL's UUID() includes hyphens, stored as TEXT
293                            rusticx::DatabaseType::MySQL => part.push_str(" DEFAULT (UUID())"),
294                            // SQLite requires a custom expression for UUID generation
295                            // This is a common pattern, might need a dedicated function in the crate
296                            rusticx::DatabaseType::SQLite => part.push_str(" DEFAULT (lower(hex(randomblob(4))) || '-' || lower(hex(randomblob(2))) || '-' || lower(hex(randomblob(2))) || '-' || lower(hex(randomblob(2))) || '-' || lower(hex(randomblob(6))))"),
297                        };
298                    }
299                }
300
301                // Add NOT NULL constraint if not nullable and not primary key
302                // Primary keys are implicitly NOT NULL unless explicitly nullable
303                if !#is_option && !#is_primary_key { // Check against is_option which considers Option<T> and #[model(nullable)]
304                    part.push_str(" NOT NULL");
305                }
306
307                // Add DEFAULT value constraint if specified
308                if #has_default {
309                    part.push_str(&format!(" DEFAULT {}", #default_value));
310                }
311
312                part // Return the generated SQL part for this field
313            }
314        };
315
316        field_sql_defs.push(sql_def); // Add the generated SQL definition to the list
317    }
318
319    // Determine the identifier for the primary key field for use in `primary_key_value` and `set_primary_key`.
320    // Defaults to an identifier "id" if no field was marked as primary key (though this should ideally be a user error).
321    let pk_ident = primary_key_field.unwrap_or_else(|| Ident::new("id", name.span()));
322
323    // Collect column names as string literals for the `field_names` method
324    let field_name_literals: Vec<_> = field_names.iter().map(|name| quote! { #name }).collect();
325
326    // Generate the implementation for `primary_key_value`.
327    // This needs to handle `Option<T>` and different primary key types (int vs UUID).
328    let get_primary_key_code = match primary_key_type {
329        Some(ref pk_type) => {
330            if is_option_type(pk_type) {
331                // Handle Option<T> primary keys
332                if pk_is_uuid {
333                    // If Option<Uuid>, clone the Uuid reference
334                    quote! {
335                        // Access the Option<Uuid> field and map to clone the Uuid if Some
336                        self.#pk_ident.as_ref().map(|val| val.clone())
337                    }
338                } else {
339                    // If Option<i32> or other Option<Integer>
340                    // Assumes primary keys are returned as i32 by the ORM's fetch logic.
341                    // This might need adjustment based on the actual ORM implementation's return type.
342                    quote! {
343                         // Access the Option<i32> field and map to the i32 if Some
344                        self.#pk_ident.as_ref().map(|val| *val) // Changed to return the actual i32
345                    }
346                }
347            } else {
348                // Handle non-Option primary keys
349                if pk_is_uuid {
350                     // If Uuid (non-Option), just clone it and wrap in Some
351                    quote! { Some(self.#pk_ident.clone()) }
352                } else {
353                    // If i32 or other Integer (non-Option)
354                     // Assumes primary keys are returned as i32 by the ORM's fetch logic.
355                    quote! { Some(self.#pk_ident) } // Changed to return the actual i32
356                }
357            }
358        },
359        // Default case if no primary key was explicitly marked. Assumes an 'id' field exists.
360        // This case should ideally be an error or handled more robustly if PK is mandatory.
361        None => {
362            // Fallback logic assuming an `id` field of type `Option<i32>`
363            // This is less ideal; enforcing a #[model(primary_key)] is better.
364            // If `id` field doesn't exist or isn't Option<i32>, this will cause compilation errors.
365            quote! {
366                 // Access the Option<i32> field (assuming `id`)
367                self.#pk_ident.as_ref().map(|val| *val) // Assuming id is Option<i32>
368            }
369        }
370    };
371
372    // Generate the implementation for `set_primary_key`.
373    // This assumes the primary key field is an `Option<i32>`.
374    // This needs refinement if UUID or non-Option primary keys are supported by `set_primary_key`.
375    // Currently, `set_primary_key` takes `i32`, which fits `Option<i32>` PKs set after insert.
376    // If PK is Uuid, this method signature might need to change in the trait.
377    // Assuming for now that `set_primary_key` is only used for auto-generated *integer* IDs.
378     let set_primary_key_code = quote! {
379         // Set the primary key field value, assuming it's Option<i32>
380        self.#pk_ident = Some(id);
381    };
382
383
384    // Construct the final generated code for the SQLModel implementation
385    let expanded = quote! {
386        // Implement the SQLModel trait for the target struct
387        impl rusticx::SQLModel for #name {
388            /// Returns the database table name for this model.
389            ///
390            /// This is derived from the struct name or specified using 
391            /// the `#[model(table = "...")]` attribute.
392            fn table_name() -> String {
393                #table_name.to_string()
394            }
395
396            /// Returns the database column name of the primary key field.
397            ///
398            /// This is the field marked with `#[model(primary_key)]`.
399            /// Defaults to "id" if no primary key is explicitly marked (less ideal).
400            fn primary_key_field() -> String {
401                 // Stringify the ident of the primary key field
402                stringify!(#pk_ident).to_string()
403            }
404
405            /// Returns the value of the primary key field, if present.
406            ///
407            /// Returns `Some(value)` if the primary key is set, `None` otherwise.
408            ///
409            /// # Note
410            /// The return type `Option<i32>` is assumed for auto-increment integer keys.
411            /// If using UUIDs or other primary key types, the trait method signature
412            /// might need adjustment in the `rusticx` crate.
413            fn primary_key_value(&self) -> Option<i32> {
414                // Execute the generated code snippet to get the PK value
415                // The generated code handles Option<T> and type conversion (assumed i32 for now)
416                // TODO: Refine signature to Option<Self::PkType> if PkType is added to trait
417                #get_primary_key_code
418            }
419
420            /// Sets the value of the primary key field.
421            ///
422            /// This is typically used after an INSERT operation with an auto-generated ID.
423            ///
424            /// # Arguments
425            ///
426            /// * `id`: The integer value of the primary key.
427            ///
428            /// # Note
429            /// This method assumes the primary key field is of type `Option<i32>`.
430            /// It will cause a compilation error if the primary key field has a different type.
431            /// A more generic trait method or separate methods for different PK types might be needed.
432            fn set_primary_key(&mut self, id: i32) {
433                 // Execute the generated code snippet to set the PK value
434                #set_primary_key_code
435            }
436
437            /// Generates the SQL `CREATE TABLE` statement for this model.
438            ///
439            /// The statement is tailored to the specified database type (`db_type`).
440            /// Includes column definitions, primary key constraints, nullability,
441            /// defaults, and auto-increment/UUID syntax.
442            ///
443            /// # Arguments
444            ///
445            /// * `db_type`: The type of the database (PostgreSQL, MySQL, SQLite).
446            ///
447            /// # Returns
448            ///
449            /// A string containing the `CREATE TABLE` SQL statement.
450            fn create_table_sql(db_type: &rusticx::DatabaseType) -> String {
451                // Start the CREATE TABLE statement
452                let mut sql = format!("CREATE TABLE IF NOT EXISTS \"{}\" (", Self::table_name());
453                // Collect the generated SQL definitions for each field
454                let fields = vec![#(#field_sql_defs),*];
455                // Join field definitions with commas and close the statement
456                sql.push_str(&fields.join(", "));
457                sql.push(')');
458                sql
459            }
460
461            /// Returns a vector of static strings representing the database column names
462            /// for all non-skipped fields in the model.
463            ///
464            /// Used for constructing SELECT or INSERT statements.
465            fn field_names() -> Vec<&'static str> {
466                 // Return a vector of string literals for column names
467                vec![#(#field_name_literals),*]
468            }
469
470            /// Returns a vector of boxed trait objects (`ToSqlConvert`) representing
471            /// the values of all non-skipped fields in the model.
472            ///
473            /// Used for binding values in INSERT or UPDATE statements.
474            /// Assumes field types implement `Clone` and can be converted to `ToSqlConvert`.
475            fn to_sql_field_values(&self) -> Vec<Box<dyn rusticx::ToSqlConvert>> {
476                 // Return a vector of boxed trait objects for field values
477                vec![#(#field_to_sql_values),*]
478            }
479
480            /// Deserializes a database row (represented as a `serde_json::Value::Object`)
481            /// into an instance of the model struct.
482            ///
483            /// # Arguments
484            ///
485            /// * `row`: A reference to a `serde_json::Value`, expected to be a JSON object
486            ///          where keys are column names and values are column data.
487            ///
488            /// # Returns
489            ///
490            /// Returns `Ok(Self)` on successful deserialization, or a `RusticxError`
491            /// if the input is not a JSON object or if field deserialization fails.
492            fn from_row(row: &serde_json::Value) -> Result<Self, rusticx::RusticxError> {
493                // Ensure the input value is a JSON object
494                if !row.is_object() {
495                    return Err(rusticx::RusticxError::DeserializationError(
496                        "Input for from_row is not a JSON object".to_string()
497                    ));
498                }
499
500                // Get a reference to the JSON object
501                let obj = row.as_object().unwrap(); // Safe to unwrap because we checked is_object()
502
503                // Construct the struct instance by deserializing each field
504                Ok(Self {
505                    #(#field_from_row),* // Execute the generated code snippets for each field
506                })
507            }
508        }
509    };
510
511    // Return the generated code as a TokenStream
512    TokenStream::from(expanded)
513}
514
515/// Helper function to check if a given Rust type is an `Option<T>`.
516fn is_option_type(ty: &Type) -> bool {
517    // Check if the type is a path (like `std::option::Option`)
518    if let Type::Path(TypePath { path, .. }) = ty {
519        // Get the last segment of the path (e.g., `Option`)
520        if let Some(segment) = path.segments.last() {
521            // Check if the identifier of the last segment is "Option"
522            return segment.ident == "Option";
523        }
524    }
525    false // Not an Option type
526}
527
528/// Helper function to generate the code snippet for deserializing a single field
529/// from a `serde_json::Value` object (representing a database row).
530///
531/// Handles both optional (`Option<T>`) and required fields.
532///
533/// # Arguments
534///
535/// * `field_ident`: The identifier of the struct field.
536/// * `column_name`: The database column name corresponding to the field.
537/// * `_field_type`: The Rust type of the field (used implicitly by `serde_json::from_value`).
538/// * `is_optional`: Boolean indicating if the field is `Option<T>` or marked nullable.
539///
540/// # Returns
541///
542/// A `proc_macro2::TokenStream` containing the code to deserialize the field.
543fn generate_from_json(field_ident: &Ident, column_name: &str, _field_type: &Type, is_optional: bool) -> proc_macro2::TokenStream {
544    // Use the column name as the key to look up the value in the JSON object
545    let column_literal = column_name;
546
547    if is_optional {
548        // Code for optional fields (Option<T> or #[model(nullable)])
549        quote! {
550            #field_ident: if let Some(val) = obj.get(#column_literal) {
551                // If the key exists, check if the value is null
552                if val.is_null() {
553                    None // If null, set field to None
554                } else {
555                    // If not null, attempt to deserialize the value
556                    match serde_json::from_value(val.clone()) {
557                        Ok(v) => Some(v), // If successful, wrap in Some
558                        Err(e) => return Err(rusticx::RusticxError::DeserializationError(
559                            format!("Failed to deserialize field `{}`: {}", #column_literal, e)
560                        )), // If deserialization fails, return an error
561                    }
562                }
563            } else {
564                // If the key does not exist in the JSON object, treat as None
565                // This handles cases where a nullable column is not included in the query result
566                None
567            }
568        }
569    } else {
570        // Code for required fields (non-Option and not #[model(nullable)])
571        quote! {
572            #field_ident: if let Some(val) = obj.get(#column_literal) {
573                // If the key exists, attempt to deserialize the value
574                 match serde_json::from_value(val.clone()) {
575                    Ok(v) => v, // If successful, use the value
576                    Err(e) => return Err(rusticx::RusticxError::DeserializationError(
577                        format!("Failed to deserialize field `{}`: {}", #column_literal, e)
578                    )), // If deserialization fails, return an error
579                }
580            } else {
581                // If the key does not exist for a required field, return an error
582                return Err(rusticx::RusticxError::DeserializationError(
583                    format!("Missing required field: `{}`", #column_literal)
584                ));
585            }
586        }
587    }
588}
589
590/// Helper function to map a Rust type to a generic `SqlType` enum variant.
591///
592/// This mapping is used to determine the database column type in the `CREATE TABLE` statement,
593/// which is then translated to the database-specific syntax by the `SqlType` methods.
594///
595/// # Arguments
596///
597/// * `rust_type`: The `syn::Type` of the Rust field.
598///
599/// # Returns
600///
601/// A `proc_macro2::TokenStream` representing the corresponding `rusticx::SqlType` variant.
602///
603/// # Panics
604///
605/// Panics if the Rust type is not recognized or supported by the mapping.
606fn generate_sql_type(rust_type: &Type) -> proc_macro2::TokenStream {
607    // Only support path types (like `i32`, `String`, `Option<T>`, etc.)
608    match rust_type {
609        Type::Path(TypePath { path, .. }) => {
610            // Get the last segment of the path
611            let segment = path.segments.last().unwrap();
612            let ident = &segment.ident;
613            let type_name = ident.to_string();
614
615            // Handle Option<T> recursively: get the inner type's mapping
616            if type_name == "Option" {
617                if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
618                    if let Some(arg) = args.args.first() {
619                        if let syn::GenericArgument::Type(inner_type) = arg {
620                            // Recursively call for the inner type
621                            return generate_sql_type(inner_type);
622                        }
623                    }
624                }
625                // Panic if Option type has invalid arguments
626                panic!("Invalid Option<T> type specification for field: {}", quote!{#rust_type});
627            }
628
629            // Map common Rust types to SqlType variants
630            match type_name.as_str() {
631                "i8" | "i16" | "i32" | "u8" | "u16" | "u32" => quote! { rusticx::SqlType::Integer },
632                "i64" | "u64" => quote! { rusticx::SqlType::BigInt },
633                "f32" | "f64" => quote! { rusticx::SqlType::Float },
634                "bool" => quote! { rusticx::SqlType::Boolean },
635                // Map String/str to Text
636                "String" | "str" => quote! { rusticx::SqlType::Text },
637                // Map Uuid (from `uuid` crate) to Text (common storage, can be overridden)
638                "Uuid" => quote! { rusticx::SqlType::Text },
639                // Map chrono date/time types
640                "NaiveDate" => quote! { rusticx::SqlType::Date },
641                "NaiveTime" => quote! { rusticx::SqlType::Time },
642                "NaiveDateTime" | "DateTime" => quote! { rusticx::SqlType::DateTime },
643                // Map Vec<u8> to Blob
644                "Vec" => {
645                    if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
646                        if let Some(arg) = args.args.first() {
647                            if let syn::GenericArgument::Type(Type::Path(TypePath { path, .. })) = arg {
648                                if let Some(seg) = path.segments.last() {
649                                    if seg.ident == "u8" {
650                                        return quote! { rusticx::SqlType::Blob };
651                                    }
652                                }
653                            }
654                        }
655                    }
656                    // Fallback for other Vec types, treat as Blob (might need refinement)
657                    quote! { rusticx::SqlType::Blob }
658                }
659                // Panic for unknown types
660                _ => panic!("Unknown or unsupported Rust type for SQL mapping: `{}`. Consider using #[model(sql_type = \"...\")]", quote!{#rust_type}),
661            }
662        }
663        // Panic for other complex types (arrays, tuples, pointers, etc.)
664        _ => panic!("Unsupported complex type for SQL mapping: `{}`. Only simple path types and Option<T> are automatically mapped. Consider using #[model(sql_type = \"...\")]", quote!{#rust_type}),
665    }
666}
667
668/// Helper function to extract the custom table name from the struct-level `#[model(table = "...")]` attribute.
669///
670/// # Arguments
671///
672/// * `attrs`: A slice of `syn::Attribute` applied to the struct.
673///
674/// # Returns
675///
676/// An `Option<String>` containing the custom table name if found, otherwise `None`.
677fn extract_table_name(attrs: &[Attribute]) -> Option<String> {
678    // Iterate through all attributes on the struct
679    for attr in attrs {
680        // Check if the attribute is our custom #[model(...)] attribute
681        if !attr.path().is_ident("model") {
682            continue;
683        }
684
685        // Parse the attribute's arguments
686        let parsed = attr.parse_args_with(
687            syn::punctuated::Punctuated::<Meta, syn::token::Comma>::parse_terminated,
688        );
689
690        // Process the parsed meta items
691        if let Ok(items) = parsed {
692            for meta in items {
693                // Check for the `table = "..."` name-value pair
694                if let Meta::NameValue(MetaNameValue { path, value, .. }) = meta {
695                    if path.is_ident("table") {
696                        // If found, extract the string literal value
697                        if let Expr::Lit(expr_lit) = value {
698                            if let syn::Lit::Str(lit_str) = expr_lit.lit {
699                                return Some(lit_str.value()); // Return the extracted table name
700                            }
701                        }
702                    }
703                }
704            }
705        } else {
706             // Log or handle parsing errors for struct attributes if necessary
707             // For simplicity, we ignore errors here and let subsequent logic handle missing name
708             let _ = parsed.unwrap_err(); // Consume the error
709        }
710    }
711    None // No custom table name found
712}