diff --git a/cot-cli/tests/migration_generator.rs b/cot-cli/tests/migration_generator.rs index d1b7f21d..4fe81ff4 100644 --- a/cot-cli/tests/migration_generator.rs +++ b/cot-cli/tests/migration_generator.rs @@ -160,6 +160,64 @@ fn create_models_foreign_key_two_migrations() { assert_eq!(table_name, "cot__child"); } +#[test] +fn create_model_keywords() { + let generator = test_generator(); + let src = include_str!("migration_generator/keywords.rs"); + let source_files = vec![SourceFile::parse(PathBuf::from("main.rs"), src).unwrap()]; + + let migration = generator + .generate_migrations_as_generated_from_files(source_files) + .unwrap() + .unwrap(); + + assert_eq!(migration.migration_name, "m_0001_initial"); + assert!(migration.dependencies.is_empty()); + + let (table_name, fields) = unwrap_create_model(&migration.operations[0]); + assert_eq!(table_name, "cot__const"); + assert_eq!(fields.len(), 3); + + let field = &fields[0]; + assert_eq!(field.column_name, "id"); + + let field = &fields[1]; + assert_eq!(field.column_name, "abstract"); + assert_eq!(field.name.to_string(), "r#abstract"); + + let field = &fields[2]; + assert_eq!(field.column_name, "type"); + assert_eq!(field.name.to_string(), "r#type"); +} + +#[test] +fn create_model_keywords_source() { + let generator = test_generator(); + let src = include_str!("migration_generator/keywords.rs"); + let source_files = vec![SourceFile::parse(PathBuf::from("main.rs"), src).unwrap()]; + + let migration = generator + .generate_migrations_as_source_from_files(source_files) + .unwrap() + .unwrap(); + + assert!( + migration + .content + .contains(r#"::cot::db::Identifier::new("cot__const")"#) + ); + assert!( + migration + .content + .contains(r#"::cot::db::Identifier::new("abstract")"#) + ); + assert!( + migration + .content + .contains(r#"::cot::db::Identifier::new("type")"#) + ); +} + /// Test that the migration generator can generate a "create model" migration /// for a given model which compiles successfully. #[test] diff --git a/cot-cli/tests/migration_generator/keywords.rs b/cot-cli/tests/migration_generator/keywords.rs new file mode 100644 index 00000000..2a2720b5 --- /dev/null +++ b/cot-cli/tests/migration_generator/keywords.rs @@ -0,0 +1,9 @@ +use cot::db::{Auto, ForeignKey, model}; + +#[model] +struct r#const { + #[model(primary_key)] + id: Auto, + r#abstract: String, + r#type: i32, +} diff --git a/cot-codegen/src/model.rs b/cot-codegen/src/model.rs index 6610b070..8b2f6bf9 100644 --- a/cot-codegen/src/model.rs +++ b/cot-codegen/src/model.rs @@ -1,5 +1,6 @@ use darling::{FromDeriveInput, FromField, FromMeta}; use heck::ToSnakeCase; +use syn::ext::IdentExt; use syn::spanned::Spanned; use crate::symbol_resolver::SymbolResolver; @@ -77,7 +78,7 @@ impl ModelOpts { .map(as_field) .collect::, _>>()?; - let mut original_name = self.ident.to_string(); + let mut original_name = self.ident.unraw().to_string(); if args.model_type == ModelType::Migration { original_name = original_name .strip_prefix("_") @@ -92,7 +93,7 @@ impl ModelOpts { let table_name = if let Some(table_name) = &args.table_name { table_name.clone() } else { - original_name.clone().to_snake_case() + original_name.to_snake_case() }; let primary_key_field = self.get_primary_key_field(&fields)?; @@ -145,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 { @@ -204,8 +206,13 @@ impl FieldOpts { symbol_resolver: &SymbolResolver, self_reference: Option<&String>, ) -> Result { - let name = self.ident.as_ref().unwrap(); - let column_name = name.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(), @@ -363,6 +370,23 @@ mod tests { assert_eq!(model.field_count(), 2); } + #[test] + fn model_opts_raw_name() { + let input: syn::DeriveInput = parse_quote! { + struct r#abstract { + #[model(primary_key)] + id: i32, + name: String, + } + }; + let opts = ModelOpts::new_from_derive_input(&input).unwrap(); + let model = opts + .as_model(&ModelArgs::default(), &SymbolResolver::new(vec![])) + .unwrap(); + assert_eq!(model.name.to_string(), "r#abstract"); + assert_eq!(model.table_name, "abstract"); + } + #[test] fn model_opts_as_model_migration() { let input: syn::DeriveInput = parse_quote! { @@ -458,6 +482,33 @@ mod tests { assert!(field.unique); } + #[test] + fn field_opts_raw_name() { + let input: syn::Field = parse_quote! { + r#abstract: 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(), "r#abstract"); + 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 = diff --git a/cot-codegen/src/symbol_resolver.rs b/cot-codegen/src/symbol_resolver.rs index 24d57efa..a69f1450 100644 --- a/cot-codegen/src/symbol_resolver.rs +++ b/cot-codegen/src/symbol_resolver.rs @@ -5,6 +5,7 @@ use std::iter::FromIterator; #[cfg(feature = "symbol-resolver")] use std::path::Path; +use quote::format_ident; #[cfg(feature = "symbol-resolver")] use syn::UseTree; #[cfg(feature = "symbol-resolver")] @@ -88,7 +89,7 @@ impl SymbolResolver { let mut new_segments: Vec<_> = symbol .full_path_parts() .map(|s| syn::PathSegment { - ident: syn::Ident::new(s, first_segment.ident.span()), + ident: format_ident!("{}", s, span = first_segment.ident.span()), arguments: syn::PathArguments::None, }) .collect();