1extern crate proc_macro;
39
40use proc_macro::TokenStream;
41use proc_macro2::Span;
42use quote::quote;
43use syn::{
44 parse_macro_input, Attribute, Data, DeriveInput, Expr, Fields, Lit, Meta, Type,
45 punctuated::Punctuated, spanned::Spanned, token::Comma,
46};
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50enum FieldKind {
51 PrimaryKey,
53 Timestamps,
55 TimestampField,
57 Persist,
59 Skip,
61}
62
63#[derive(Debug, Clone)]
68struct FieldIndex {
69 name: String,
71 unique: bool,
73 partners: Vec<String>,
75}
76
77struct FieldInfo {
79 ident: syn::Ident,
80 column: Option<String>,
81 kind: FieldKind,
82 ty: Type,
83 optional: bool,
85 indexes: Vec<FieldIndex>,
87}
88
89impl FieldInfo {
90 fn column_name(&self) -> String {
91 self.column
92 .clone()
93 .unwrap_or_else(|| self.ident.to_string())
94 }
95}
96
97struct ModelConfig {
99 table_name: String,
100 primary_key: String,
101}
102
103impl ModelConfig {
104 fn parse(attrs: &[Attribute]) -> syn::Result<Self> {
105 let mut table_name: Option<String> = None;
106 let mut primary_key: Option<String> = None;
107
108 for attr in attrs {
109 if !attr.path().is_ident("model") {
110 continue;
111 }
112 let Meta::List(list) = &attr.meta else { continue };
113 let nested = list.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)?;
114 for item in nested {
115 match item {
116 Meta::NameValue(nv) if nv.path.is_ident("table_name") => {
117 table_name = Some(expr_to_string(&nv.value)?);
118 }
119 Meta::NameValue(nv) if nv.path.is_ident("primary_key") => {
120 primary_key = Some(expr_to_string(&nv.value)?);
121 }
122 _ => {}
123 }
124 }
125 }
126
127 let table_name = table_name.ok_or_else(|| {
128 syn::Error::new(
129 Span::call_site(),
130 "#[model(table_name = \"...\")] is required on #[derive(Model)] structs",
131 )
132 })?;
133
134 Ok(Self {
135 table_name,
136 primary_key: primary_key.unwrap_or_else(|| "id".to_string()),
137 })
138 }
139}
140
141fn expr_to_string(expr: &Expr) -> syn::Result<String> {
142 match expr {
143 Expr::Lit(lit) => match &lit.lit {
144 Lit::Str(s) => Ok(s.value()),
145 _ => Err(syn::Error::new_spanned(lit, "expected a string literal")),
146 },
147 _ => Err(syn::Error::new_spanned(expr, "expected a string literal")),
148 }
149}
150
151fn collect_fields(input: &DeriveInput, pk_name: &str) -> syn::Result<Vec<FieldInfo>> {
153 let data = match &input.data {
154 Data::Struct(s) => s,
155 Data::Enum(_) | Data::Union(_) => {
156 return Err(syn::Error::new(
157 input.ident.span(),
158 "#[derive(Model)] is only supported on structs",
159 ))
160 }
161 };
162
163 let named = match &data.fields {
164 Fields::Named(named) => &named.named,
165 Fields::Unnamed(_) | Fields::Unit => {
166 return Err(syn::Error::new(
167 input.ident.span(),
168 "#[derive(Model)] requires named struct fields",
169 ))
170 }
171 };
172
173 let mut out = Vec::new();
174 for field in named {
175 let ident = field
176 .ident
177 .clone()
178 .ok_or_else(|| syn::Error::new(field.span(), "named field expected"))?;
179 let ty = field.ty.clone();
180 let optional = field_type_is_option(&ty);
181
182 let mut skip = false;
183 let mut column: Option<String> = None;
184 let mut indexes: Vec<FieldIndex> = Vec::new();
185 for attr in &field.attrs {
186 if !attr.path().is_ident("model") {
187 continue;
188 }
189 let Meta::List(list) = &attr.meta else { continue };
190 let nested = list.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)?;
191 for item in nested {
192 match item {
193 Meta::Path(path) if path.is_ident("skip") => skip = true,
194 Meta::Path(path) if path.is_ident("index") => {
195 indexes.push(FieldIndex {
196 name: String::new(),
197 unique: false,
198 partners: Vec::new(),
199 });
200 }
201 Meta::Path(path) if path.is_ident("uniqueIndex") => {
202 indexes.push(FieldIndex {
203 name: String::new(),
204 unique: true,
205 partners: Vec::new(),
206 });
207 }
208 Meta::NameValue(nv) if nv.path.is_ident("column") => {
209 column = Some(expr_to_string(&nv.value)?);
210 }
211 Meta::NameValue(nv) if nv.path.is_ident("index") => {
212 indexes.push(FieldIndex {
213 name: expr_to_string(&nv.value)?,
214 unique: false,
215 partners: Vec::new(),
216 });
217 }
218 Meta::NameValue(nv) if nv.path.is_ident("uniqueIndex") => {
219 indexes.push(FieldIndex {
220 name: expr_to_string(&nv.value)?,
221 unique: true,
222 partners: Vec::new(),
223 });
224 }
225 _ => {}
226 }
227 }
228 }
229
230 let kind = if skip {
231 FieldKind::Skip
232 } else if field_type_is_timestamps(&ty) {
233 FieldKind::Timestamps
234 } else if ident.to_string() == pk_name {
235 FieldKind::PrimaryKey
236 } else if is_standalone_timestamp_field(&ident, &ty) {
237 FieldKind::TimestampField
238 } else if type_tag(&ty).is_some() {
239 FieldKind::Persist
240 } else {
241 FieldKind::Skip
243 };
244
245 out.push(FieldInfo {
246 ident,
247 column,
248 kind,
249 ty,
250 optional,
251 indexes,
252 });
253 }
254
255 Ok(out)
256}
257
258fn field_type_is_timestamps(ty: &Type) -> bool {
259 let Type::Path(tp) = ty else { return false };
260 matches!(tp.path.segments.last(), Some(s) if s.ident == "Timestamps")
261}
262
263fn field_type_is_option(ty: &Type) -> bool {
264 let Type::Path(tp) = ty else { return false };
265 matches!(tp.path.segments.last(), Some(s) if s.ident == "Option")
266}
267
268fn is_standalone_timestamp_field(ident: &syn::Ident, ty: &Type) -> bool {
271 let name = ident.to_string();
272 let valid_name = matches!(name.as_str(), "created_at" | "updated_at" | "deleted_at");
273 valid_name && type_tag(ty) == Some("datetime")
274}
275
276fn inner_type(ty: &Type) -> &Type {
278 if let Type::Path(tp) = ty {
279 if let Some(s) = tp.path.segments.last() {
280 if s.ident == "Option" {
281 if let syn::PathArguments::AngleBracketed(args) = &s.arguments {
282 if let Some(syn::GenericArgument::Type(inner)) = args.args.first() {
283 return inner;
284 }
285 }
286 }
287 }
288 }
289 ty
290}
291
292fn type_tag(ty: &Type) -> Option<&'static str> {
294 let ty = inner_type(ty);
295 let Type::Path(tp) = ty else { return None };
296 let last = tp.path.segments.last()?;
297 let name = last.ident.to_string();
298 let tag = match name.as_str() {
299 "String" => "string",
300 "bool" => "bool",
301 "i8" => "i8",
302 "i16" => "i16",
303 "i32" => "i32",
304 "i64" => "i64",
305 "f32" => "f32",
306 "f64" => "f64",
307 "DateTime" => "datetime",
308 "Uuid" => "uuid",
309 "Vec" => {
310 let is_u8 = matches!(
313 &last.arguments,
314 syn::PathArguments::AngleBracketed(args)
315 if args.args.len() == 1
316 && matches!(
317 args.args.first(),
318 Some(syn::GenericArgument::Type(Type::Path(tp)))
319 if tp.path.is_ident("u8")
320 )
321 );
322 if is_u8 {
323 "bytes"
324 } else {
325 return None;
326 }
327 }
328 _ => return None,
329 };
330 Some(tag)
331}
332
333fn gen_columns_entries(fields: &[&FieldInfo]) -> Vec<proc_macro2::TokenStream> {
335 fields
336 .iter()
337 .filter_map(|f| {
338 let col = f.column_name();
339 let fname = &f.ident;
340 let tag = type_tag(&f.ty)?;
341 let expr = match tag {
342 "string" => {
343 if f.optional {
344 quote! { match &self.#fname { Some(v) => SqlValue::String(v.clone()), None => SqlValue::Null } }
345 } else {
346 quote! { SqlValue::String(self.#fname.clone()) }
347 }
348 }
349 "bool" => {
350 if f.optional {
351 quote! { match self.#fname { Some(v) => SqlValue::Bool(v), None => SqlValue::Null } }
352 } else {
353 quote! { SqlValue::Bool(self.#fname) }
354 }
355 }
356 "i8" => {
357 if f.optional {
358 quote! { match self.#fname { Some(v) => SqlValue::I8(v), None => SqlValue::Null } }
359 } else {
360 quote! { SqlValue::I8(self.#fname) }
361 }
362 }
363 "i16" => {
364 if f.optional {
365 quote! { match self.#fname { Some(v) => SqlValue::I16(v), None => SqlValue::Null } }
366 } else {
367 quote! { SqlValue::I16(self.#fname) }
368 }
369 }
370 "i32" => {
371 if f.optional {
372 quote! { match self.#fname { Some(v) => SqlValue::I32(v), None => SqlValue::Null } }
373 } else {
374 quote! { SqlValue::I32(self.#fname) }
375 }
376 }
377 "i64" => {
378 if f.optional {
379 quote! { match self.#fname { Some(v) => SqlValue::I64(v), None => SqlValue::Null } }
380 } else {
381 quote! { SqlValue::I64(self.#fname) }
382 }
383 }
384 "f32" => {
385 if f.optional {
386 quote! { match self.#fname { Some(v) => SqlValue::F32(v), None => SqlValue::Null } }
387 } else {
388 quote! { SqlValue::F32(self.#fname) }
389 }
390 }
391 "f64" => {
392 if f.optional {
393 quote! { match self.#fname { Some(v) => SqlValue::F64(v), None => SqlValue::Null } }
394 } else {
395 quote! { SqlValue::F64(self.#fname) }
396 }
397 }
398 "datetime" => {
399 if f.optional {
400 quote! { match self.#fname { Some(v) => SqlValue::DateTime(v), None => SqlValue::Null } }
401 } else {
402 quote! { SqlValue::DateTime(self.#fname) }
403 }
404 }
405 "uuid" => {
406 if f.optional {
407 quote! { match &self.#fname { Some(v) => SqlValue::String(v.to_string()), None => SqlValue::Null } }
408 } else {
409 quote! { SqlValue::String(self.#fname.to_string()) }
410 }
411 }
412 "bytes" => {
413 if f.optional {
414 quote! { match &self.#fname { Some(v) => SqlValue::Bytes(v.clone()), None => SqlValue::Null } }
415 } else {
416 quote! { SqlValue::Bytes(self.#fname.clone()) }
417 }
418 }
419 _ => return None,
420 };
421 Some(quote! { (#col, #expr) })
422 })
423 .collect()
424}
425
426fn gen_from_row_expr(f: &FieldInfo) -> Option<proc_macro2::TokenStream> {
428 let col = f.column_name();
429 let tag = type_tag(&f.ty)?;
430 let inner = match tag {
431 "string" => Some(quote! {
432 match row.get(#col)? {
433 SqlValue::String(s) => s.clone(),
434 SqlValue::Json(s) => s.clone(),
435 SqlValue::I64(i) => i.to_string(),
436 SqlValue::I32(i) => i.to_string(),
437 SqlValue::I16(i) => i.to_string(),
438 SqlValue::I8(i) => i.to_string(),
439 SqlValue::Bool(b) => b.to_string(),
440 _ => return None,
441 }
442 }),
443 "bool" => Some(quote! {
444 match row.get(#col)? {
445 SqlValue::Bool(v) => *v,
446 SqlValue::I32(1) => true,
447 SqlValue::I64(1) => true,
448 SqlValue::I32(0) => false,
449 SqlValue::I64(0) => false,
450 _ => return None,
451 }
452 }),
453 "i8" => Some(quote! {
454 match row.get(#col)? {
455 SqlValue::I8(v) => *v,
456 SqlValue::I16(v) => *v as i8,
457 SqlValue::I32(v) => *v as i8,
458 SqlValue::I64(v) => *v as i8,
459 _ => return None,
460 }
461 }),
462 "i16" => Some(quote! {
463 match row.get(#col)? {
464 SqlValue::I16(v) => *v,
465 SqlValue::I8(v) => *v as i16,
466 SqlValue::I32(v) => *v as i16,
467 SqlValue::I64(v) => *v as i16,
468 _ => return None,
469 }
470 }),
471 "i32" => Some(quote! {
472 match row.get(#col)? {
473 SqlValue::I32(v) => *v,
474 SqlValue::I8(v) => *v as i32,
475 SqlValue::I16(v) => *v as i32,
476 SqlValue::I64(v) => *v as i32,
477 _ => return None,
478 }
479 }),
480 "i64" => Some(quote! {
481 match row.get(#col)? {
482 SqlValue::I64(v) => *v,
483 SqlValue::I32(v) => *v as i64,
484 SqlValue::I16(v) => *v as i64,
485 SqlValue::I8(v) => *v as i64,
486 _ => return None,
487 }
488 }),
489 "f32" => Some(quote! {
490 match row.get(#col)? {
491 SqlValue::F32(v) => *v,
492 SqlValue::F64(v) => *v as f32,
493 _ => return None,
494 }
495 }),
496 "f64" => Some(quote! {
497 match row.get(#col)? {
498 SqlValue::F64(v) => *v,
499 SqlValue::F32(v) => *v as f64,
500 _ => return None,
501 }
502 }),
503 "datetime" => Some(quote! {
504 match row.get(#col)? {
505 SqlValue::DateTime(v) => *v,
506 SqlValue::String(s) => {
508 chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S")
509 .map(|dt| dt.and_utc())
510 .ok()?
511 }
512 _ => return None,
513 }
514 }),
515 "uuid" => Some(quote! {
516 Uuid::parse_str(row.get(#col)?.as_str()?).ok()?
517 }),
518 "bytes" => Some(quote! {
519 match row.get(#col)? {
520 SqlValue::Bytes(v) => v.clone(),
521 _ => return None,
522 }
523 }),
524 _ => None,
525 }?;
526
527 if f.optional {
528 Some(quote! {
529 match row.get(#col) {
530 Some(SqlValue::Null) | None => None,
531 _ => Some(#inner),
532 }
533 })
534 } else {
535 Some(inner)
536 }
537}
538
539fn tag_to_column_type(tag: &str) -> Option<&'static str> {
541 match tag {
542 "i8" | "i16" | "i32" => Some("Integer"),
543 "i64" => Some("BigInteger"),
544 "string" => Some("String"),
545 "bool" => Some("Boolean"),
546 "f32" => Some("Float"),
547 "f64" => Some("Double"),
548 "datetime" => Some("DateTime"),
549 "uuid" => Some("Uuid"),
550 "bytes" => Some("Binary"),
551 _ => None,
552 }
553}
554
555fn gen_schema_impl(
558 table_name: &str,
559 fields: &[&FieldInfo],
560) -> proc_macro2::TokenStream {
561 let mut column_toks: Vec<proc_macro2::TokenStream> = Vec::new();
563 for f in fields {
564 if f.kind == FieldKind::Skip {
565 continue;
566 }
567 let col = f.column_name();
568 let optional = f.optional;
569
570 let (col_type, is_pk) = match f.kind {
572 FieldKind::PrimaryKey => (
573 tag_to_column_type(type_tag(&f.ty).unwrap_or("string")).or(Some("Integer")),
574 true,
575 ),
576 FieldKind::Persist => (type_tag(&f.ty).and_then(tag_to_column_type), false),
577 FieldKind::TimestampField => (Some("DateTime"), false),
578 FieldKind::Timestamps => {
579 for ts_col in ["created_at", "updated_at", "deleted_at"] {
581 column_toks.push(quote! {
582 ColumnDefinition::new(#ts_col, ColumnType::DateTime).nullable(true)
583 });
584 }
585 continue;
586 }
587 FieldKind::Skip => continue,
588 };
589 let Some(col_type) = col_type else {
590 continue;
591 };
592 let col_type_tok = syn::Ident::new(col_type, Span::call_site());
593
594 let is_unique = f.indexes.iter().any(|ix| ix.unique && ix.partners.is_empty());
596
597 let mut col_def = quote! {
598 ColumnDefinition::new(#col, ColumnType::#col_type_tok).nullable(#optional)
599 };
600 if is_pk {
601 col_def = quote! { #col_def .primary_key() };
602 if col_type == "Integer" || col_type == "BigInteger" {
606 col_def = quote! { #col_def .auto_increment() };
607 }
608 }
609 if is_unique {
610 col_def = quote! { #col_def .unique() };
611 }
612 column_toks.push(col_def);
613 }
614
615 let mut index_defs: Vec<proc_macro2::TokenStream> = Vec::new();
618 let mut groups: std::collections::HashMap<String, (bool, Vec<String>)> =
619 std::collections::HashMap::new();
620 for f in fields {
621 if f.kind == FieldKind::Skip {
622 continue;
623 }
624 let col = f.column_name();
625 for ix in &f.indexes {
626 let key = if ix.name.is_empty() {
627 col.clone()
628 } else {
629 ix.name.clone()
630 };
631 groups
632 .entry(key.clone())
633 .or_insert((ix.unique, Vec::new()))
634 .1
635 .push(col.clone());
636 if ix.unique {
637 groups.get_mut(&key).unwrap().0 = true;
638 }
639 }
640 }
641 let mut sorted_keys: Vec<&String> = groups.keys().collect();
642 sorted_keys.sort();
643 for key in sorted_keys {
644 let (unique, cols) = &groups[key];
645 let name = if key.starts_with("idx_") {
646 key.clone()
647 } else {
648 format!("idx_{}_{}", table_name, key)
649 };
650 let cols_lit: Vec<syn::LitStr> = cols
651 .iter()
652 .map(|c| syn::LitStr::new(c, Span::call_site()))
653 .collect();
654 if *unique {
655 index_defs.push(quote! {
656 IndexDefinition::new(#name, &[#(#cols_lit),*]).unique()
657 });
658 } else {
659 index_defs.push(quote! {
660 IndexDefinition::new(#name, &[#(#cols_lit),*])
661 });
662 }
663 }
664
665 quote! {
666 fn schema() -> Option<TableDefinition> {
667 Some(TableDefinition::new(#table_name)
668 #( .add_column(#column_toks) )*
669 #( .add_index(#index_defs) )*
670 )
671 }
672 }
673}
674
675fn build_model_impl(
677 input: &DeriveInput,
678 config: &ModelConfig,
679 fields: &[FieldInfo],
680) -> proc_macro2::TokenStream {
681 let ident = &input.ident;
682 let table_name = &config.table_name;
683
684 let pk = fields.iter().find(|f| f.kind == FieldKind::PrimaryKey);
685 let ts = fields.iter().find(|f| f.kind == FieldKind::Timestamps);
686 let ts_field_created = fields
687 .iter()
688 .find(|f| f.kind == FieldKind::TimestampField && f.ident == "created_at");
689 let ts_field_updated = fields
690 .iter()
691 .find(|f| f.kind == FieldKind::TimestampField && f.ident == "updated_at");
692 let ts_field_deleted = fields
693 .iter()
694 .find(|f| f.kind == FieldKind::TimestampField && f.ident == "deleted_at");
695 let persist: Vec<&FieldInfo> = fields
696 .iter()
697 .filter(|f| f.kind == FieldKind::Persist)
698 .collect();
699
700 let (id_fn, set_id_fn) = match pk {
702 Some(pk) => {
703 let pk_ident = &pk.ident;
704 let pk_ty = inner_type(&pk.ty);
705 let pk_tag = type_tag(pk_ty).unwrap_or("");
706 let (id_expr, set_expr) = match pk_tag {
707 "string" => (
708 quote! {
709 if self.#pk_ident.is_empty() { None } else { Some(self.#pk_ident.clone()) }
710 },
711 quote! { self.#pk_ident = id; },
712 ),
713 "uuid" => (
714 quote! {
715 if self.#pk_ident.is_nil() { None } else { Some(self.#pk_ident.to_string()) }
716 },
717 quote! { self.#pk_ident = Uuid::parse_str(&id).unwrap_or_else(|_| Uuid::nil()); },
718 ),
719 "i64" => (
720 quote! {
721 if self.#pk_ident > 0 { Some(self.#pk_ident.to_string()) } else { None }
722 },
723 quote! { self.#pk_ident = id.parse().unwrap_or(0); },
724 ),
725 "i32" => (
726 quote! {
727 if self.#pk_ident > 0 { Some(self.#pk_ident.to_string()) } else { None }
728 },
729 quote! { self.#pk_ident = id.parse().unwrap_or(0); },
730 ),
731 "i16" => (
732 quote! {
733 if self.#pk_ident > 0 { Some(self.#pk_ident.to_string()) } else { None }
734 },
735 quote! { self.#pk_ident = id.parse().unwrap_or(0); },
736 ),
737 "i8" => (
738 quote! {
739 if self.#pk_ident > 0 { Some(self.#pk_ident.to_string()) } else { None }
740 },
741 quote! { self.#pk_ident = id.parse().unwrap_or(0); },
742 ),
743 _ => (
745 quote! {
746 let s = self.#pk_ident.to_string();
747 if s.is_empty() { None } else { Some(s) }
748 },
749 quote! { self.#pk_ident = id.into(); },
750 ),
751 };
752 (
753 quote! { fn id(&self) -> Option<String> { #id_expr } },
754 quote! { fn set_id(&mut self, id: String) { #set_expr } },
755 )
756 }
757 None => (
758 quote! {
759 fn id(&self) -> Option<String> { None }
760 },
761 quote! {
762 fn set_id(&mut self, _id: String) {}
763 },
764 ),
765 };
766
767 let ts_impl = if let Some(ts) = ts {
771 let t = &ts.ident;
772 quote! {
773 fn created_at(&self) -> Option<DateTime<Utc>> { self.#t.created_at }
774 fn updated_at(&self) -> Option<DateTime<Utc>> { self.#t.updated_at }
775 fn deleted_at(&self) -> Option<DateTime<Utc>> { self.#t.deleted_at }
776 fn set_created_at(&mut self, timestamp: DateTime<Utc>) { self.#t.created_at = Some(timestamp); }
777 fn set_updated_at(&mut self, timestamp: DateTime<Utc>) { self.#t.updated_at = Some(timestamp); }
778 fn set_deleted_at(&mut self, timestamp: Option<DateTime<Utc>>) { self.#t.deleted_at = timestamp; }
779 }
780 } else {
781 let created = ts_field_created.map(|f| &f.ident);
782 let updated = ts_field_updated.map(|f| &f.ident);
783 let deleted = ts_field_deleted.map(|f| &f.ident);
784 let (created_get, created_set) = match created {
785 Some(ci) => (
786 quote! { self.#ci },
787 quote! { self.#ci = Some(timestamp); },
788 ),
789 None => (quote! { None }, quote! {}),
790 };
791 let (updated_get, updated_set) = match updated {
792 Some(ui) => (
793 quote! { self.#ui },
794 quote! { self.#ui = Some(timestamp); },
795 ),
796 None => (quote! { None }, quote! {}),
797 };
798 let (deleted_get, deleted_set) = match deleted {
799 Some(di) => (
800 quote! { self.#di },
801 quote! { self.#di = timestamp; },
802 ),
803 None => (quote! { None }, quote! {}),
804 };
805 quote! {
806 fn created_at(&self) -> Option<DateTime<Utc>> { #created_get }
807 fn updated_at(&self) -> Option<DateTime<Utc>> { #updated_get }
808 fn deleted_at(&self) -> Option<DateTime<Utc>> { #deleted_get }
809 fn set_created_at(&mut self, timestamp: DateTime<Utc>) { #created_set }
810 fn set_updated_at(&mut self, timestamp: DateTime<Utc>) { #updated_set }
811 fn set_deleted_at(&mut self, timestamp: Option<DateTime<Utc>>) { #deleted_set }
812 }
813 };
814
815 let column_entries = gen_columns_entries(&persist);
817
818 let mut literals: Vec<proc_macro2::TokenStream> = Vec::new();
820 for f in fields {
821 let fident = &f.ident;
822 match f.kind {
823 FieldKind::Skip => {
824 literals.push(quote! { #fident: Default::default() });
825 }
826 FieldKind::Timestamps => {
827 literals.push(quote! {
828 #fident: {
829 let mut ts = Timestamps::new();
830 ts.created_at = row.get("created_at").and_then(|v| match v {
831 SqlValue::DateTime(dt) => Some(*dt),
832 _ => None,
833 });
834 ts.updated_at = row.get("updated_at").and_then(|v| match v {
835 SqlValue::DateTime(dt) => Some(*dt),
836 _ => None,
837 });
838 ts.deleted_at = row.get("deleted_at").and_then(|v| match v {
839 SqlValue::DateTime(dt) => Some(*dt),
840 _ => None,
841 });
842 ts
843 }
844 });
845 }
846 FieldKind::PrimaryKey | FieldKind::Persist | FieldKind::TimestampField => {
847 if let Some(expr) = gen_from_row_expr(f) {
848 literals.push(quote! { #fident: #expr });
849 } else {
850 literals.push(quote! { #fident: Default::default() });
851 }
852 }
853 }
854 }
855
856 let field_refs: Vec<&FieldInfo> = fields.iter().collect();
858 let schema_impl = gen_schema_impl(&config.table_name, &field_refs);
859
860 quote! {
861 #[automatically_derived]
862 impl Model for #ident {
863 fn table_name() -> &'static str {
864 #table_name
865 }
866
867 #id_fn
868 #set_id_fn
869 #ts_impl
870
871 #schema_impl
872
873 fn columns(&self) -> Vec<(&'static str, SqlValue)> {
874 vec![
875 #(#column_entries,)*
876 ]
877 }
878
879 fn from_row(row: &Row) -> Option<Self> {
880 Some(Self {
881 #(#literals,)*
882 })
883 }
884 }
885 }
886}
887
888#[proc_macro_derive(Model, attributes(model))]
890pub fn derive_model(input: TokenStream) -> TokenStream {
891 let input = parse_macro_input!(input as DeriveInput);
892 match expand(&input) {
893 Ok(ts) => ts.into(),
894 Err(e) => e.to_compile_error().into(),
895 }
896}
897
898fn expand(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
899 let config = ModelConfig::parse(&input.attrs)?;
900 let fields = collect_fields(input, &config.primary_key)?;
901
902 let model_impl = build_model_impl(input, &config, &fields);
903
904 Ok(quote! {
905 const _: () = {
906 use torm::orm::model::Timestamps;
907 use torm::db::db_types::{Row, SqlValue};
908 use torm::chrono::{DateTime, Utc};
909 use torm::Uuid;
910 use torm::orm::migration::{
911 TableDefinition, ColumnDefinition, ColumnType, IndexDefinition,
912 };
913
914 #model_impl
915 };
916 })
917}