ormlite_attr/metadata/
column.rs1use crate::{Ident, Type};
2use proc_macro2::TokenStream;
3use structmeta::{Flag, StructMeta};
4use syn::{Attribute, Field, LitStr, Path};
5
6#[derive(Debug, Clone)]
7pub enum Join {
8 ManyToOne {
9 column: String,
11 },
12 ManyToMany {
13 table: String,
14 },
15 OneToMany {
16 model: String,
17 field: String,
18 },
19}
20
21#[derive(Clone, Debug)]
23pub struct ColumnMeta {
24 pub name: String,
26 pub ty: Type,
27 pub marked_primary_key: bool,
29 pub has_database_default: bool,
30 pub ident: Ident,
32
33 pub skip: bool,
34 pub rust_default: Option<String>,
35 pub join: Option<Join>,
36 pub json: bool,
37}
38
39impl ColumnMeta {
40 pub fn is_default(&self) -> bool {
41 self.rust_default.is_some() || self.has_database_default
42 }
43
44 pub fn from_fields<'a>(fields: impl Iterator<Item = &'a Field>) -> Vec<Self> {
45 fields.map(|f| ColumnMeta::from_field(f)).collect()
46 }
47
48 pub fn from_syn(ident: &syn::Ident, ty: &syn::Type) -> Self {
49 let syn::Type::Path(ty) = &ty else {
50 panic!("No type on field {}", ident);
51 };
52 Self {
53 name: ident.to_string(),
54 ty: Type::from(&ty.path),
55 marked_primary_key: false,
56 has_database_default: false,
57 ident: Ident::from(ident),
58 skip: false,
59 rust_default: None,
60 join: None,
61 json: false,
62 }
63 }
64
65 pub fn is_join(&self) -> bool {
66 matches!(self.ty, Type::Join(_))
67 }
68
69 pub fn is_join_one(&self) -> bool {
70 let Some(join) = &self.join else {
71 return false;
72 };
73 matches!(join, Join::ManyToOne { .. })
74 }
75
76 pub fn is_join_many(&self) -> bool {
77 let Some(join) = &self.join else {
78 return false;
79 };
80 matches!(join, Join::ManyToOne { .. } | Join::ManyToMany { .. })
81 }
82
83 pub fn is_option(&self) -> bool {
84 matches!(self.ty, Type::Option(_))
85 }
86
87 pub fn is_json(&self) -> bool {
88 self.ty.is_json() || self.json
89 }
90
91 pub fn joined_struct_name(&self) -> Option<String> {
93 let Type::Join(join) = &self.ty else {
94 return None;
95 };
96 Some(join.inner_type_name())
97 }
98
99 pub fn joined_model(&self) -> TokenStream {
100 self.ty.qualified_inner_name()
101 }
102
103 pub fn from_field(f: &Field) -> Self {
104 let ident = f.ident.as_ref().expect("No ident on field");
105 let attrs = ColumnAttr::from_attrs(&f.attrs);
106 let mut column = ColumnMeta::from_syn(ident, &f.ty);
107 for attr in attrs {
108 if attr.primary_key.value() {
109 column.marked_primary_key = true;
110 column.has_database_default = true;
111 }
112 if let Some(c) = attr.column {
113 column.name = c.value();
114 if column.ty.is_join() {
115 column.join = Some(Join::ManyToOne { column: c.value() });
116 }
117 }
118 if let Some(table_name) = attr.join_table {
119 column.join = Some(Join::ManyToMany {
120 table: table_name.value(),
121 });
122 }
123 if let Some(path) = attr.foreign_field {
124 let mut segments = path.segments.iter();
125 let model = segments
126 .next()
127 .expect("no model on foreign field attribute")
128 .ident
129 .to_string();
130 let field = segments
131 .next()
132 .expect("no field on foreign field attribute")
133 .ident
134 .to_string();
135 column.join = Some(Join::OneToMany { model, field });
136 }
137 if let Some(default_value) = attr.default_value {
138 column.rust_default = Some(default_value.value());
139 }
140 column.has_database_default |= attr.default.value();
141 column.marked_primary_key |= attr.insertable_primary_key.value();
142 column.skip |= attr.skip.value();
143 column.json |= attr.json.value();
144 }
145 if column.ty.is_join() ^ column.join.is_some() {
146 panic!("Column {ident} is a Join. You must specify one of these attributes: column (many to one), join_table (many to many), or foreign_field (one to many)");
147 }
148 column
149 }
150
151 #[doc(hidden)]
152 pub fn mock(name: &str, ty: &str) -> Self {
153 Self {
154 name: name.to_string(),
155 ty: Type::Inner(crate::InnerType::mock(ty)),
156 marked_primary_key: false,
157 has_database_default: false,
158 ident: Ident::from(name),
159 skip: false,
160 rust_default: None,
161 join: None,
162 json: false,
163 }
164 }
165
166 #[doc(hidden)]
167 pub fn mock_join(name: &str, join_model: &str) -> Self {
168 Self {
169 name: name.to_string(),
170 ty: Type::Join(Box::new(Type::Inner(crate::InnerType::mock(join_model)))),
171 marked_primary_key: false,
172 has_database_default: false,
173 ident: Ident::from(name),
174 skip: false,
175 rust_default: None,
176 join: None,
177 json: false,
178 }
179 }
180}
181
182#[derive(Clone, Debug)]
183pub struct ForeignKey {
184 pub model: String,
185 pub column: String,
186}
187
188#[derive(StructMeta)]
190pub struct ColumnAttr {
191 pub primary_key: Flag,
192 pub insertable_primary_key: Flag,
194 pub default: Flag,
196 pub default_value: Option<LitStr>,
198
199 pub join_table: Option<LitStr>,
206
207 pub foreign_field: Option<Path>,
219
220 pub column: Option<LitStr>,
229
230 pub skip: Flag,
232
233 pub json: Flag,
234}
235
236impl ColumnAttr {
237 pub fn from_attrs(ast: &[Attribute]) -> Vec<Self> {
238 ast.iter()
239 .filter(|a| a.path().is_ident("ormlite"))
240 .map(|a| a.parse_args().unwrap())
241 .collect()
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248 use syn::{parse_quote, Attribute, Fields, ItemStruct};
249
250 #[test]
251 fn test_from_field() {
252 let item: ItemStruct = syn::parse_str(
253 r#"
254struct Foo {
255#[ormlite(default_value = "\"foo\".to_string()")]
256pub name: String
257}
258"#,
259 )
260 .unwrap();
261 let Fields::Named(fields) = item.fields else {
262 panic!();
263 };
264 let field = fields.named.first().unwrap();
265 let column = ColumnMeta::from_field(field);
266 assert_eq!(column.name, "name");
267 assert_eq!(column.ty, "String");
268 assert_eq!(column.marked_primary_key, false);
269 assert_eq!(column.has_database_default, false);
270 assert_eq!(column.rust_default, Some("\"foo\".to_string()".to_string()));
271 assert_eq!(column.ident, "name");
272 }
273
274 #[test]
275 fn test_default() {
276 let attr: Attribute = parse_quote!(#[ormlite(default_value = "serde_json::Value::Null")]);
277 let args: ColumnAttr = attr.parse_args().unwrap();
278 assert!(args.default_value.is_some());
279
280 let attr: Attribute = parse_quote!(#[ormlite(default)]);
281 let args: ColumnAttr = attr.parse_args().unwrap();
282 assert!(args.default.value());
283 }
284
285 #[test]
286 fn test_column() {
287 let attr: Attribute = parse_quote!(#[ormlite(column = "org_id")]);
288 let args: ColumnAttr = attr.parse_args().unwrap();
289 assert!(args.column.is_some());
290 }
291}