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 }
603 if is_unique {
604 col_def = quote! { #col_def .unique() };
605 }
606 column_toks.push(col_def);
607 }
608
609 let mut index_defs: Vec<proc_macro2::TokenStream> = Vec::new();
612 let mut groups: std::collections::HashMap<String, (bool, Vec<String>)> =
613 std::collections::HashMap::new();
614 for f in fields {
615 if f.kind == FieldKind::Skip {
616 continue;
617 }
618 let col = f.column_name();
619 for ix in &f.indexes {
620 let key = if ix.name.is_empty() {
621 col.clone()
622 } else {
623 ix.name.clone()
624 };
625 groups
626 .entry(key.clone())
627 .or_insert((ix.unique, Vec::new()))
628 .1
629 .push(col.clone());
630 if ix.unique {
631 groups.get_mut(&key).unwrap().0 = true;
632 }
633 }
634 }
635 let mut sorted_keys: Vec<&String> = groups.keys().collect();
636 sorted_keys.sort();
637 for key in sorted_keys {
638 let (unique, cols) = &groups[key];
639 let name = if key.starts_with("idx_") {
640 key.clone()
641 } else {
642 format!("idx_{}_{}", table_name, key)
643 };
644 let cols_lit: Vec<syn::LitStr> = cols
645 .iter()
646 .map(|c| syn::LitStr::new(c, Span::call_site()))
647 .collect();
648 if *unique {
649 index_defs.push(quote! {
650 IndexDefinition::new(#name, &[#(#cols_lit),*]).unique()
651 });
652 } else {
653 index_defs.push(quote! {
654 IndexDefinition::new(#name, &[#(#cols_lit),*])
655 });
656 }
657 }
658
659 quote! {
660 fn schema() -> Option<TableDefinition> {
661 Some(TableDefinition::new(#table_name)
662 #( .add_column(#column_toks) )*
663 #( .add_index(#index_defs) )*
664 )
665 }
666 }
667}
668
669fn build_model_impl(
671 input: &DeriveInput,
672 config: &ModelConfig,
673 fields: &[FieldInfo],
674) -> proc_macro2::TokenStream {
675 let ident = &input.ident;
676 let table_name = &config.table_name;
677
678 let pk = fields.iter().find(|f| f.kind == FieldKind::PrimaryKey);
679 let ts = fields.iter().find(|f| f.kind == FieldKind::Timestamps);
680 let ts_field_created = fields
681 .iter()
682 .find(|f| f.kind == FieldKind::TimestampField && f.ident == "created_at");
683 let ts_field_updated = fields
684 .iter()
685 .find(|f| f.kind == FieldKind::TimestampField && f.ident == "updated_at");
686 let ts_field_deleted = fields
687 .iter()
688 .find(|f| f.kind == FieldKind::TimestampField && f.ident == "deleted_at");
689 let persist: Vec<&FieldInfo> = fields
690 .iter()
691 .filter(|f| f.kind == FieldKind::Persist)
692 .collect();
693
694 let (id_fn, set_id_fn) = match pk {
696 Some(pk) => {
697 let pk_ident = &pk.ident;
698 let pk_ty = inner_type(&pk.ty);
699 let pk_tag = type_tag(pk_ty).unwrap_or("");
700 let (id_expr, set_expr) = match pk_tag {
701 "string" => (
702 quote! {
703 if self.#pk_ident.is_empty() { None } else { Some(self.#pk_ident.clone()) }
704 },
705 quote! { self.#pk_ident = id; },
706 ),
707 "uuid" => (
708 quote! {
709 if self.#pk_ident.is_nil() { None } else { Some(self.#pk_ident.to_string()) }
710 },
711 quote! { self.#pk_ident = Uuid::parse_str(&id).unwrap_or_else(|_| Uuid::nil()); },
712 ),
713 "i64" => (
714 quote! {
715 if self.#pk_ident > 0 { Some(self.#pk_ident.to_string()) } else { None }
716 },
717 quote! { self.#pk_ident = id.parse().unwrap_or(0); },
718 ),
719 "i32" => (
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 "i16" => (
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 "i8" => (
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 _ => (
739 quote! {
740 let s = self.#pk_ident.to_string();
741 if s.is_empty() { None } else { Some(s) }
742 },
743 quote! { self.#pk_ident = id.into(); },
744 ),
745 };
746 (
747 quote! { fn id(&self) -> Option<String> { #id_expr } },
748 quote! { fn set_id(&mut self, id: String) { #set_expr } },
749 )
750 }
751 None => (
752 quote! {
753 fn id(&self) -> Option<String> { None }
754 },
755 quote! {
756 fn set_id(&mut self, _id: String) {}
757 },
758 ),
759 };
760
761 let ts_impl = if let Some(ts) = ts {
765 let t = &ts.ident;
766 quote! {
767 fn created_at(&self) -> Option<DateTime<Utc>> { self.#t.created_at }
768 fn updated_at(&self) -> Option<DateTime<Utc>> { self.#t.updated_at }
769 fn deleted_at(&self) -> Option<DateTime<Utc>> { self.#t.deleted_at }
770 fn set_created_at(&mut self, timestamp: DateTime<Utc>) { self.#t.created_at = Some(timestamp); }
771 fn set_updated_at(&mut self, timestamp: DateTime<Utc>) { self.#t.updated_at = Some(timestamp); }
772 fn set_deleted_at(&mut self, timestamp: Option<DateTime<Utc>>) { self.#t.deleted_at = timestamp; }
773 }
774 } else {
775 let created = ts_field_created.map(|f| &f.ident);
776 let updated = ts_field_updated.map(|f| &f.ident);
777 let deleted = ts_field_deleted.map(|f| &f.ident);
778 let (created_get, created_set) = match created {
779 Some(ci) => (
780 quote! { self.#ci },
781 quote! { self.#ci = Some(timestamp); },
782 ),
783 None => (quote! { None }, quote! {}),
784 };
785 let (updated_get, updated_set) = match updated {
786 Some(ui) => (
787 quote! { self.#ui },
788 quote! { self.#ui = Some(timestamp); },
789 ),
790 None => (quote! { None }, quote! {}),
791 };
792 let (deleted_get, deleted_set) = match deleted {
793 Some(di) => (
794 quote! { self.#di },
795 quote! { self.#di = timestamp; },
796 ),
797 None => (quote! { None }, quote! {}),
798 };
799 quote! {
800 fn created_at(&self) -> Option<DateTime<Utc>> { #created_get }
801 fn updated_at(&self) -> Option<DateTime<Utc>> { #updated_get }
802 fn deleted_at(&self) -> Option<DateTime<Utc>> { #deleted_get }
803 fn set_created_at(&mut self, timestamp: DateTime<Utc>) { #created_set }
804 fn set_updated_at(&mut self, timestamp: DateTime<Utc>) { #updated_set }
805 fn set_deleted_at(&mut self, timestamp: Option<DateTime<Utc>>) { #deleted_set }
806 }
807 };
808
809 let column_entries = gen_columns_entries(&persist);
811
812 let mut literals: Vec<proc_macro2::TokenStream> = Vec::new();
814 for f in fields {
815 let fident = &f.ident;
816 match f.kind {
817 FieldKind::Skip => {
818 literals.push(quote! { #fident: Default::default() });
819 }
820 FieldKind::Timestamps => {
821 literals.push(quote! {
822 #fident: {
823 let mut ts = Timestamps::new();
824 ts.created_at = row.get("created_at").and_then(|v| match v {
825 SqlValue::DateTime(dt) => Some(*dt),
826 _ => None,
827 });
828 ts.updated_at = row.get("updated_at").and_then(|v| match v {
829 SqlValue::DateTime(dt) => Some(*dt),
830 _ => None,
831 });
832 ts.deleted_at = row.get("deleted_at").and_then(|v| match v {
833 SqlValue::DateTime(dt) => Some(*dt),
834 _ => None,
835 });
836 ts
837 }
838 });
839 }
840 FieldKind::PrimaryKey | FieldKind::Persist | FieldKind::TimestampField => {
841 if let Some(expr) = gen_from_row_expr(f) {
842 literals.push(quote! { #fident: #expr });
843 } else {
844 literals.push(quote! { #fident: Default::default() });
845 }
846 }
847 }
848 }
849
850 let field_refs: Vec<&FieldInfo> = fields.iter().collect();
852 let schema_impl = gen_schema_impl(&config.table_name, &field_refs);
853
854 quote! {
855 #[automatically_derived]
856 impl Model for #ident {
857 fn table_name() -> &'static str {
858 #table_name
859 }
860
861 #id_fn
862 #set_id_fn
863 #ts_impl
864
865 #schema_impl
866
867 fn columns(&self) -> Vec<(&'static str, SqlValue)> {
868 vec![
869 #(#column_entries,)*
870 ]
871 }
872
873 fn from_row(row: &Row) -> Option<Self> {
874 Some(Self {
875 #(#literals,)*
876 })
877 }
878 }
879 }
880}
881
882#[proc_macro_derive(Model, attributes(model))]
884pub fn derive_model(input: TokenStream) -> TokenStream {
885 let input = parse_macro_input!(input as DeriveInput);
886 match expand(&input) {
887 Ok(ts) => ts.into(),
888 Err(e) => e.to_compile_error().into(),
889 }
890}
891
892fn expand(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
893 let config = ModelConfig::parse(&input.attrs)?;
894 let fields = collect_fields(input, &config.primary_key)?;
895
896 let model_impl = build_model_impl(input, &config, &fields);
897
898 Ok(quote! {
899 const _: () = {
900 use torm::orm::model::Timestamps;
901 use torm::db::db_types::{Row, SqlValue};
902 use torm::chrono::{DateTime, Utc};
903 use torm::Uuid;
904 use torm::orm::migration::{
905 TableDefinition, ColumnDefinition, ColumnType, IndexDefinition,
906 };
907
908 #model_impl
909 };
910 })
911}