diff --git a/cot-codegen/src/model.rs b/cot-codegen/src/model.rs index 84ae0585..d548072e 100644 --- a/cot-codegen/src/model.rs +++ b/cot-codegen/src/model.rs @@ -146,6 +146,7 @@ pub struct FieldOpts { pub ty: syn::Type, pub primary_key: darling::util::Flag, pub unique: darling::util::Flag, + pub field_name: Option, } impl FieldOpts { @@ -205,11 +206,13 @@ impl FieldOpts { symbol_resolver: &SymbolResolver, self_reference: Option<&String>, ) -> Result { - let name = self - .ident - .clone() - .expect("Only named struct fields are supported"); - let column_name = name.unraw().to_string(); + let name = self.ident.clone().expect("Only structs are supported"); + + let column_name = if let Some(specified_field_name) = &self.field_name { + specified_field_name.clone() + } else { + name.unraw().to_string() + }; let (auto_value, foreign_key) = ( self.find_type("cot::db::Auto", symbol_resolver).is_some(), @@ -492,6 +495,20 @@ mod tests { assert_eq!(field.column_name, "abstract"); } + #[test] + fn field_opts_specified_field_name() { + let input: syn::Field = parse_quote! { + #[model(field_name="test_field")] + test: String + }; + let field_opts = FieldOpts::from_field(&input).unwrap(); + let field = field_opts + .as_field(&SymbolResolver::new(vec![]), Some(&"TestModel".to_string())) + .unwrap(); + assert_eq!(field.name.to_string(), "test"); + assert_eq!(field.column_name, "test_field"); + } + #[test] fn find_type_resolved() { let input: syn::Type = @@ -516,6 +533,7 @@ mod tests { ty: parse_quote! { MyContainer }, primary_key: darling::util::Flag::default(), unique: darling::util::Flag::default(), + field_name: None, }; assert!(opts.find_type("my_crate::MyContainer", &resolver).is_some()); diff --git a/cot-macros/src/lib.rs b/cot-macros/src/lib.rs index 34fe4134..1ca1b479 100644 --- a/cot-macros/src/lib.rs +++ b/cot-macros/src/lib.rs @@ -173,6 +173,24 @@ pub fn derive_admin_model(input: TokenStream) -> TokenStream { /// } /// ``` /// +/// ## `field_name` +/// The `field_name` attribute is used to provide a specific name for the field +/// in the created database table to which the Rust field is mapped to. This +/// allows, in the following example, to map the `name` parameter to the +/// `username` column in the database. +/// +/// ``` +/// use cot::db::{Auto, model}; +/// +/// #[model] +/// struct User { +/// #[model(primary_key)] +/// id: Auto, +/// #[model(field_name = "username")] +/// name: String, +/// } +/// ``` +/// /// [`Model`]: trait.Model.html /// [`DatabaseField`]: trait.DatabaseField.html #[proc_macro_attribute]