1use proc_macro2::Span;
2use std::slice::from_ref;
3use syn::Result;
4use syn::punctuated::Punctuated;
5use syn::token::Comma;
6use syn::{
7 Data, DataStruct, DeriveInput, Field as SynField, Fields, FieldsNamed, FieldsUnnamed, Ident,
8 LitBool, Path, Type,
9};
10
11use crate::field::Field;
12use crate::util::camel_to_snake;
13use diesel_attribute_parser::CheckForBackend;
14use diesel_attribute_parser::parsers::{
15 BelongsTo, MariadbType, MysqlType, PostgresType, SqliteType,
16};
17use diesel_attribute_parser::{StructAttr, parse_attributes};
18
19pub struct Model {
20 name: Path,
21 table_names: Vec<Path>,
22 pub primary_key_names: Vec<Ident>,
23 treat_none_as_default_value: Option<LitBool>,
24 treat_none_as_null: Option<LitBool>,
25 pub belongs_to: Vec<BelongsTo>,
26 pub sql_types: Vec<Type>,
27 pub aggregate: bool,
28 pub not_sized: bool,
29 pub foreign_derive: bool,
30 pub enum_type: bool,
31 pub mysql_type: Option<MysqlType>,
32 pub mariadb_type: Option<MariadbType>,
33 pub sqlite_type: Option<SqliteType>,
34 pub postgres_type: Option<PostgresType>,
35 pub check_for_backend: Option<CheckForBackend>,
36 pub base_query: Option<syn::Expr>,
37 pub base_query_type: Option<syn::Type>,
38 fields: Vec<Field>,
39}
40
41impl Model {
42 pub fn from_item(
43 item: &DeriveInput,
44 allow_unit_structs: bool,
45 allow_multiple_table: bool,
46 ) -> Result<Self> {
47 let DeriveInput {
48 data, ident, attrs, ..
49 } = item;
50
51 let fields = match *data {
52 Data::Struct(DataStruct {
53 fields: Fields::Named(FieldsNamed { ref named, .. }),
54 ..
55 }) => Some(named),
56 Data::Struct(DataStruct {
57 fields: Fields::Unnamed(FieldsUnnamed { ref unnamed, .. }),
58 ..
59 }) => Some(unnamed),
60 _ if !allow_unit_structs => {
61 return Err(syn::Error::new(
62 proc_macro2::Span::mixed_site(),
63 "this derive can only be used on non-unit structs",
64 ));
65 }
66 _ => None,
67 };
68
69 let mut table_names = ::alloc::vec::Vec::new()vec![];
70 let mut primary_key_names = ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[Ident::new("id", Span::mixed_site())]))vec![Ident::new("id", Span::mixed_site())];
71 let mut treat_none_as_default_value = None;
72 let mut treat_none_as_null = None;
73 let mut belongs_to = ::alloc::vec::Vec::new()vec![];
74 let mut sql_types = ::alloc::vec::Vec::new()vec![];
75 let mut aggregate = false;
76 let mut not_sized = false;
77 let mut enum_type = false;
78 let mut foreign_derive = false;
79 let mut mysql_type = None;
80 let mut mariadb_type = None;
81 let mut sqlite_type = None;
82 let mut postgres_type = None;
83 let mut check_for_backend = None;
84 let mut base_query = None;
85 let mut base_query_type = None;
86
87 for attr in parse_attributes(attrs)? {
88 match attr.item {
89 StructAttr::SqlType(_, value) => sql_types.push(Type::Path(value)),
90 StructAttr::TableName(ident, value) => {
91 if !allow_multiple_table && !table_names.is_empty() {
92 return Err(syn::Error::new(
93 ident.span(),
94 "expected a single table name attribute\n\
95 note: remove this attribute",
96 ));
97 }
98 table_names.push(value)
99 }
100 StructAttr::PrimaryKey(_, keys) => {
101 primary_key_names = keys.into_iter().collect();
102 }
103 StructAttr::TreatNoneAsDefaultValue(_, val) => {
104 treat_none_as_default_value = Some(val)
105 }
106 StructAttr::TreatNoneAsNull(_, val) => treat_none_as_null = Some(val),
107 StructAttr::BelongsTo(_, val) => belongs_to.push(val),
108 StructAttr::Aggregate(_) => aggregate = true,
109 StructAttr::NotSized(_) => not_sized = true,
110 StructAttr::ForeignDerive(_) => foreign_derive = true,
111 StructAttr::EnumType(_) => enum_type = true,
112 StructAttr::MysqlType(_, val) => mysql_type = Some(val),
113 StructAttr::MariadbType(_, val) => mariadb_type = Some(val),
114 StructAttr::SqliteType(_, val) => sqlite_type = Some(val),
115 StructAttr::PostgresType(_, val) => postgres_type = Some(val),
116 StructAttr::CheckForBackend(_, b) => {
117 check_for_backend = Some(b);
118 }
119 StructAttr::BaseQuery(_, e) => base_query = Some(e),
120 StructAttr::BaseQueryType(_, t) => base_query_type = Some(t),
121 StructAttr::RenameAll(_, _) => { }
122 }
123 }
124
125 let name = Ident::new(&infer_table_name(&ident.to_string()), ident.span()).into();
126
127 Ok(Self {
128 name,
129 table_names,
130 primary_key_names,
131 treat_none_as_default_value,
132 treat_none_as_null,
133 belongs_to,
134 sql_types,
135 aggregate,
136 not_sized,
137 foreign_derive,
138 mysql_type,
139 mariadb_type,
140 sqlite_type,
141 postgres_type,
142 fields: fields_from_item_data(fields)?,
143 check_for_backend,
144 base_query,
145 base_query_type,
146 enum_type,
147 })
148 }
149
150 pub fn table_names(&self) -> &[Path] {
151 match self.table_names.len() {
152 0 => from_ref(&self.name),
153 _ => &self.table_names,
154 }
155 }
156
157 pub fn fields(&self) -> &[Field] {
158 &self.fields
159 }
160
161 pub fn find_column(&self, column_name: &Ident) -> Result<&Field> {
162 self.fields()
163 .iter()
164 .find(|f| {
165 f.column_name()
166 .map(|c| c == *column_name)
167 .unwrap_or_default()
168 })
169 .ok_or_else(|| {
170 syn::Error::new(
171 column_name.span(),
172 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("no field with column name `{0}`",
column_name))
})format!("no field with column name `{column_name}`"),
173 )
174 })
175 }
176
177 pub fn treat_none_as_default_value(&self) -> bool {
178 self.treat_none_as_default_value
179 .as_ref()
180 .map(|v| v.value())
181 .unwrap_or(true)
182 }
183
184 pub fn treat_none_as_null(&self) -> bool {
185 self.treat_none_as_null
186 .as_ref()
187 .map(|v| v.value())
188 .unwrap_or(false)
189 }
190}
191
192fn fields_from_item_data(fields: Option<&Punctuated<SynField, Comma>>) -> Result<Vec<Field>> {
193 fields
194 .map(|fields| {
195 fields
196 .iter()
197 .enumerate()
198 .map(|(i, f)| Field::from_struct_field(f, i))
199 .collect::<Result<Vec<_>>>()
200 })
201 .unwrap_or_else(|| Ok(Vec::new()))
202}
203
204pub fn infer_table_name(name: &str) -> String {
205 let mut result = camel_to_snake(name);
206 result.push('s');
207 result
208}
209
210#[test]
211fn infer_table_name_pluralizes_and_downcases() {
212 assert_eq!("foos", &infer_table_name("Foo"));
213 assert_eq!("bars", &infer_table_name("Bar"));
214}
215
216#[test]
217fn infer_table_name_properly_handles_underscores() {
218 assert_eq!("foo_bars", &infer_table_name("FooBar"));
219 assert_eq!("foo_bar_bazs", &infer_table_name("FooBarBaz"));
220}