1use proc_macro::TokenStream;
10use proc_macro2::TokenStream as TokenStream2;
11use quote::quote;
12use syn::{
13 parse_macro_input, spanned::Spanned, Data, DeriveInput, Fields, GenericArgument, LitStr,
14 PathArguments, Type, TypePath,
15};
16
17fn rustango_root() -> TokenStream2 {
38 use proc_macro_crate::{crate_name, FoundCrate};
39 match crate_name("rustango") {
40 Ok(FoundCrate::Itself) => quote!(::rustango),
41 Ok(FoundCrate::Name(name)) => {
42 let ident = proc_macro2::Ident::new(&name, proc_macro2::Span::call_site());
43 quote!(::#ident)
44 }
45 Err(_) => quote!(::rustango),
46 }
47}
48
49#[proc_macro_derive(Model, attributes(rustango, rgo))]
51pub fn derive_model(input: TokenStream) -> TokenStream {
52 let input = parse_macro_input!(input as DeriveInput);
53 expand(&input)
54 .unwrap_or_else(syn::Error::into_compile_error)
55 .into()
56}
57
58#[proc_macro_derive(ViewSet, attributes(viewset))]
99pub fn derive_viewset(input: TokenStream) -> TokenStream {
100 let input = parse_macro_input!(input as DeriveInput);
101 expand_viewset(&input)
102 .unwrap_or_else(syn::Error::into_compile_error)
103 .into()
104}
105
106#[proc_macro_derive(Form, attributes(form))]
134pub fn derive_form(input: TokenStream) -> TokenStream {
135 let input = parse_macro_input!(input as DeriveInput);
136 expand_form(&input)
137 .unwrap_or_else(syn::Error::into_compile_error)
138 .into()
139}
140
141#[proc_macro_derive(Serializer, attributes(serializer, serde))]
166pub fn derive_serializer(input: TokenStream) -> TokenStream {
167 let input = parse_macro_input!(input as DeriveInput);
168 expand_serializer(&input)
169 .unwrap_or_else(syn::Error::into_compile_error)
170 .into()
171}
172
173#[proc_macro]
212pub fn embed_migrations(input: TokenStream) -> TokenStream {
213 expand_embed_migrations(input.into())
214 .unwrap_or_else(syn::Error::into_compile_error)
215 .into()
216}
217
218#[allow(non_snake_case)]
268#[proc_macro]
269pub fn Q(input: TokenStream) -> TokenStream {
270 expand_q(input.into())
271 .unwrap_or_else(syn::Error::into_compile_error)
272 .into()
273}
274
275#[proc_macro_attribute]
317pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
318 expand_main(args.into(), item.into())
319 .unwrap_or_else(syn::Error::into_compile_error)
320 .into()
321}
322
323fn expand_main(args: TokenStream2, item: TokenStream2) -> syn::Result<TokenStream2> {
324 let mut input: syn::ItemFn = syn::parse2(item)?;
325 if input.sig.asyncness.is_none() {
326 return Err(syn::Error::new(
327 input.sig.ident.span(),
328 "`#[rustango::main]` must wrap an `async fn`",
329 ));
330 }
331
332 let root = rustango_root();
342 let MainArgs { flavor, logging } = parse_main_args(&args)?;
343 let builder_call = match flavor {
344 Flavor::CurrentThread => quote! {
345 #root::__private_runtime::tokio::runtime::Builder::new_current_thread()
346 },
347 Flavor::MultiThread => quote! {
348 #root::__private_runtime::tokio::runtime::Builder::new_multi_thread()
349 },
350 };
351
352 let user_body = input.block.clone();
355 input.sig.asyncness = None;
356
357 let logging_prologue = if logging {
361 quote! {
362 let _ = #root::logging::Setup::new()
367 .with_default_env_filter(#root::__private_runtime::main_default_filter())
368 .with_format(#root::__private_runtime::main_format())
369 .install();
370 }
371 } else {
372 quote! {}
373 };
374
375 input.block = syn::parse2(quote! {{
376 { #logging_prologue }
377 let __rt = #builder_call
378 .enable_all()
379 .build()
380 .expect("failed to build tokio runtime");
381 __rt.block_on(async move #user_body)
382 }})?;
383
384 Ok(quote! {
385 #input
386 })
387}
388
389enum Flavor {
390 MultiThread,
391 CurrentThread,
392}
393
394struct MainArgs {
396 flavor: Flavor,
397 logging: bool,
398}
399
400const MAIN_ARGS_HELP: &str =
401 "supported: `flavor = \"current_thread\" | \"multi_thread\"`, `logging = true | false`";
402
403fn parse_main_args(args: &TokenStream2) -> syn::Result<MainArgs> {
405 use syn::parse::Parser as _;
406 let metas = syn::punctuated::Punctuated::<syn::MetaNameValue, syn::Token![,]>::parse_terminated
407 .parse2(args.clone())
408 .map_err(|e| {
409 syn::Error::new(
410 e.span(),
411 format!("#[rustango::main]: {e}; {MAIN_ARGS_HELP}"),
412 )
413 })?;
414 let mut out = MainArgs {
415 flavor: Flavor::MultiThread,
416 logging: true,
417 };
418 for meta in &metas {
419 let lit = match &meta.value {
420 syn::Expr::Lit(l) => Some(&l.lit),
421 _ => None,
422 };
423 let name = meta.path.get_ident().map(ToString::to_string);
424 match (name.as_deref(), lit) {
425 (Some("flavor"), Some(syn::Lit::Str(s))) if s.value() == "current_thread" => {
426 out.flavor = Flavor::CurrentThread;
427 }
428 (Some("flavor"), Some(syn::Lit::Str(s))) if s.value() == "multi_thread" => {
429 out.flavor = Flavor::MultiThread;
430 }
431 (Some("logging"), Some(syn::Lit::Bool(b))) => out.logging = b.value,
432 _ => {
433 return Err(syn::Error::new(
434 meta.span(),
435 format!("#[rustango::main]: unsupported argument; {MAIN_ARGS_HELP}"),
436 ))
437 }
438 }
439 }
440 Ok(out)
441}
442
443struct QInput {
445 base_path: syn::Path,
446 field: syn::Ident,
447 value: syn::Expr,
448}
449
450impl syn::parse::Parse for QInput {
451 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
452 let base_path: syn::Path = input.parse()?;
453 input.parse::<syn::Token![.]>()?;
454 let field: syn::Ident = input.parse()?;
455 input.parse::<syn::Token![=]>()?;
456 let value: syn::Expr = input.parse()?;
457 Ok(QInput {
458 base_path,
459 field,
460 value,
461 })
462 }
463}
464
465fn expand_q(input: TokenStream2) -> syn::Result<TokenStream2> {
466 let q: QInput = syn::parse2(input)?;
467 let root = rustango_root();
468 let field_str = ident_name(&q.field);
469 let field_span = q.field.span();
470 let (base, suffix) = match field_str.find("__") {
471 Some(idx) => (&field_str[..idx], &field_str[idx + 2..]),
472 None => (field_str.as_str(), ""),
473 };
474 if base.is_empty() {
475 return Err(syn::Error::new(
476 field_span,
477 "Q!(): field name is empty before `__` suffix",
478 ));
479 }
480 let base_ident = ident_from_name(base, field_span);
481 let value = &q.value;
482 let path = &q.base_path;
483
484 let expanded = match suffix {
489 "" | "exact" => quote! {
490 #root::core::Column::eq(#path::#base_ident, #value)
491 },
492 "ne" => quote! {
493 #root::core::Column::ne(#path::#base_ident, #value)
494 },
495 "gt" => quote! {
496 #root::core::Column::gt(#path::#base_ident, #value)
497 },
498 "gte" => quote! {
499 #root::core::Column::gte(#path::#base_ident, #value)
500 },
501 "lt" => quote! {
502 #root::core::Column::lt(#path::#base_ident, #value)
503 },
504 "lte" => quote! {
505 #root::core::Column::lte(#path::#base_ident, #value)
506 },
507 "iexact" => quote! {
512 #root::core::Column::iexact(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
513 },
514 "contains" => quote! {
515 #root::core::Column::contains(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
516 },
517 "icontains" => quote! {
518 #root::core::Column::icontains(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
519 },
520 "startswith" => quote! {
521 #root::core::Column::startswith(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
522 },
523 "istartswith" => quote! {
524 #root::core::Column::istartswith(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
525 },
526 "endswith" => quote! {
527 #root::core::Column::endswith(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
528 },
529 "iendswith" => quote! {
530 #root::core::Column::iendswith(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
531 },
532 "in" => quote! {
533 #root::core::Column::is_in(#path::#base_ident, #value)
534 },
535 "not_in" => quote! {
536 #root::core::Column::not_in(#path::#base_ident, #value)
537 },
538 "isnull" => {
539 let b = match value {
542 syn::Expr::Lit(syn::ExprLit {
543 lit: syn::Lit::Bool(b),
544 ..
545 }) => b.value(),
546 _ => {
547 return Err(syn::Error::new_spanned(
548 value,
549 "Q!(): `__isnull` requires a `true` or `false` literal",
550 ));
551 }
552 };
553 if b {
554 quote! { #root::core::Column::is_null(#path::#base_ident) }
555 } else {
556 quote! { #root::core::Column::is_not_null(#path::#base_ident) }
557 }
558 }
559 "between" => {
560 let tuple = match value {
562 syn::Expr::Tuple(t) if t.elems.len() == 2 => t,
563 _ => {
564 return Err(syn::Error::new_spanned(
565 value,
566 "Q!(): `__between` requires a tuple literal `(lo, hi)`",
567 ));
568 }
569 };
570 let lo = &tuple.elems[0];
571 let hi = &tuple.elems[1];
572 quote! { #root::core::Column::between(#path::#base_ident, #lo, #hi) }
573 }
574 "regex" => quote! {
575 #root::core::Column::regex(#path::#base_ident, #value)
576 },
577 "iregex" => quote! {
578 #root::core::Column::iregex(#path::#base_ident, #value)
579 },
580 _ => {
581 return Err(syn::Error::new(
582 field_span,
583 format!(
584 "Q!(): unknown lookup suffix `__{}`. Supported: __exact / __iexact / __ne / __gt / __gte / __lt / __lte / __contains / __icontains / __startswith / __istartswith / __endswith / __iendswith / __in / __not_in / __isnull / __between / __regex / __iregex",
585 suffix
586 ),
587 ));
588 }
589 };
590 Ok(expanded)
591}
592
593fn expand_embed_migrations(input: TokenStream2) -> syn::Result<TokenStream2> {
594 let path_str = if input.is_empty() {
596 "./migrations".to_string()
597 } else {
598 let lit: LitStr = syn::parse2(input)?;
599 lit.value()
600 };
601
602 let manifest = std::env::var("CARGO_MANIFEST_DIR").map_err(|_| {
603 syn::Error::new(
604 proc_macro2::Span::call_site(),
605 "embed_migrations! must be invoked during a Cargo build (CARGO_MANIFEST_DIR not set)",
606 )
607 })?;
608 let abs = std::path::Path::new(&manifest).join(&path_str);
609
610 let mut entries: Vec<(String, std::path::PathBuf)> = Vec::new();
611 if abs.is_dir() {
612 let read = std::fs::read_dir(&abs).map_err(|e| {
613 syn::Error::new(
614 proc_macro2::Span::call_site(),
615 format!("embed_migrations!: cannot read {}: {e}", abs.display()),
616 )
617 })?;
618 for entry in read.flatten() {
619 let path = entry.path();
620 if !path.is_file() {
621 continue;
622 }
623 if path.extension().and_then(|s| s.to_str()) != Some("json") {
624 continue;
625 }
626 let Some(stem) = path.file_stem().and_then(|s| s.to_str()) else {
627 continue;
628 };
629 entries.push((stem.to_owned(), path));
630 }
631 }
632 entries.sort_by(|a, b| a.0.cmp(&b.0));
633
634 let mut chain_names: Vec<String> = Vec::with_capacity(entries.len());
647 let mut prev_refs: Vec<(String, Option<String>)> = Vec::with_capacity(entries.len());
648 for (stem, path) in &entries {
649 let raw = std::fs::read_to_string(path).map_err(|e| {
650 syn::Error::new(
651 proc_macro2::Span::call_site(),
652 format!(
653 "embed_migrations!: cannot read {} for chain validation: {e}",
654 path.display()
655 ),
656 )
657 })?;
658 let json: serde_json::Value = serde_json::from_str(&raw).map_err(|e| {
659 syn::Error::new(
660 proc_macro2::Span::call_site(),
661 format!(
662 "embed_migrations!: {} is not valid JSON: {e}",
663 path.display()
664 ),
665 )
666 })?;
667 let name = json
668 .get("name")
669 .and_then(|v| v.as_str())
670 .ok_or_else(|| {
671 syn::Error::new(
672 proc_macro2::Span::call_site(),
673 format!(
674 "embed_migrations!: {} is missing the `name` field",
675 path.display()
676 ),
677 )
678 })?
679 .to_owned();
680 if name != *stem {
681 return Err(syn::Error::new(
682 proc_macro2::Span::call_site(),
683 format!(
684 "embed_migrations!: file stem `{stem}` does not match the migration's \
685 `name` field `{name}` — rename the file or fix the JSON",
686 ),
687 ));
688 }
689 let prev = json.get("prev").and_then(|v| v.as_str()).map(str::to_owned);
690 chain_names.push(name.clone());
691 prev_refs.push((name, prev));
692 }
693
694 let name_set: std::collections::HashSet<&str> =
695 chain_names.iter().map(String::as_str).collect();
696 for (name, prev) in &prev_refs {
697 if let Some(p) = prev {
698 if !name_set.contains(p.as_str()) {
699 return Err(syn::Error::new(
700 proc_macro2::Span::call_site(),
701 format!(
702 "embed_migrations!: broken migration chain — `{name}` declares \
703 prev=`{p}` but no migration with that name exists in {}",
704 abs.display()
705 ),
706 ));
707 }
708 }
709 }
710
711 let pairs: Vec<TokenStream2> = entries
712 .iter()
713 .map(|(name, path)| {
714 let path_lit = path.display().to_string();
715 quote! { (#name, ::core::include_str!(#path_lit)) }
716 })
717 .collect();
718
719 let missing_warning = if abs.is_dir() {
722 quote! {}
723 } else {
724 let note = format!(
725 "embed_migrations!: directory {} does not exist; no migrations are embedded",
726 abs.display()
727 );
728 quote! {
729 #[deprecated(note = #note)]
730 const __RUSTANGO_MIGRATIONS_DIR_MISSING: () = ();
731 #[allow(clippy::let_unit_value)]
732 let _ = __RUSTANGO_MIGRATIONS_DIR_MISSING;
733 }
734 };
735
736 Ok(quote! {
737 {
738 #missing_warning
739 const __RUSTANGO_EMBEDDED: &[(&'static str, &'static str)] = &[#(#pairs),*];
740 __RUSTANGO_EMBEDDED
741 }
742 })
743}
744
745fn expand(input: &DeriveInput) -> syn::Result<TokenStream2> {
746 let root = rustango_root();
747 let struct_name = &input.ident;
748
749 let Data::Struct(data) = &input.data else {
750 return Err(syn::Error::new_spanned(
751 struct_name,
752 "Model can only be derived on structs",
753 ));
754 };
755 let Fields::Named(named) = &data.fields else {
756 return Err(syn::Error::new_spanned(
757 struct_name,
758 "Model requires a struct with named fields",
759 ));
760 };
761
762 let mut container = parse_container_attrs(input)?;
763 let table = container
764 .table
765 .unwrap_or_else(|| to_snake_case(&struct_name.to_string()));
766 let model_name = struct_name.to_string();
767
768 let collected = collect_fields(named, &table)?;
769 let explicit_pks = container
771 .reverse_has_relations
772 .iter()
773 .map(|r| (&r.self_pk_column, r.self_pk_span))
774 .chain(
775 container
776 .generic_has_relations
777 .iter()
778 .map(|r| (&r.self_pk_column, r.self_pk_span)),
779 );
780 for (column, span) in explicit_pks {
781 if let (Some(column), Some(span)) = (column, span) {
782 if !collected.column_entries.iter().any(|c| &c.column == column) {
783 return Err(syn::Error::new(
784 span,
785 format!("`self_pk_column = \"{column}\"` names no column of `{model_name}`"),
786 ));
787 }
788 }
789 }
790 if let Some((_, pk_column)) = &collected.primary_key {
792 for rel in &mut container.reverse_has_relations {
793 rel.self_pk_column.get_or_insert_with(|| pk_column.clone());
794 }
795 for rel in &mut container.generic_has_relations {
796 rel.self_pk_column.get_or_insert_with(|| pk_column.clone());
797 }
798 }
799
800 if let Some((ref display, span)) = container.display {
802 if !collected.field_names.iter().any(|n| n == display) {
803 return Err(syn::Error::new(
804 span,
805 format!("`display = \"{display}\"` does not match any field on this struct"),
806 ));
807 }
808 }
809 let display = container.display.map(|(name, _)| name);
810 let app_label = container.app.clone();
811
812 if let Some(admin) = &container.admin {
822 for (label, list) in [
823 ("search_fields", &admin.search_fields),
824 ("readonly_fields", &admin.readonly_fields),
825 ("list_filter", &admin.list_filter),
826 ] {
827 if let Some((names, span)) = list {
828 for name in names {
829 if !collected.field_names.iter().any(|n| n == name) {
830 return Err(syn::Error::new(
831 *span,
832 format!(
833 "`{label} = \"{name}\"`: \"{name}\" is not a declared field on this struct"
834 ),
835 ));
836 }
837 }
838 }
839 }
840 if let Some((pairs, span)) = &admin.ordering {
841 for (name, _) in pairs {
842 if !collected.field_names.iter().any(|n| n == name) {
843 return Err(syn::Error::new(
844 *span,
845 format!(
846 "`ordering = \"{name}\"`: \"{name}\" is not a declared field on this struct"
847 ),
848 ));
849 }
850 }
851 }
852 if let Some((groups, span)) = &admin.fieldsets {
853 for (_, fields) in groups {
854 for name in fields {
855 if !collected.field_names.iter().any(|n| n == name) {
856 return Err(syn::Error::new(
857 *span,
858 format!(
859 "`fieldsets`: \"{name}\" is not a declared field on this struct"
860 ),
861 ));
862 }
863 }
864 }
865 }
866 }
867 if let Some(audit) = &container.audit {
868 if let Some((names, span)) = &audit.track {
869 for name in names {
870 if !collected.field_names.iter().any(|n| n == name) {
871 return Err(syn::Error::new(
872 *span,
873 format!(
874 "`audit(track = \"{name}\")`: \"{name}\" is not a declared field on this struct"
875 ),
876 ));
877 }
878 }
879 }
880 }
881
882 for (col, _desc, span) in &container.default_order {
886 if !collected.field_names.iter().any(|n| n == col) {
887 return Err(syn::Error::new(
888 *span,
889 format!(
890 "`default_order = \"...\"`: \"{col}\" is not a declared field on this struct"
891 ),
892 ));
893 }
894 }
895
896 check_known_columns(&container.indexes, &container.get_latest_by, &collected)?;
897
898 let audit_track_names: Option<Vec<String>> = container.audit.as_ref().map(|audit| {
901 audit
902 .track
903 .as_ref()
904 .map(|(names, _)| names.clone())
905 .unwrap_or_default()
906 });
907
908 let upsert_unique: Option<Vec<String>> = container
911 .indexes
912 .iter()
913 .find(|i| i.unique && i.where_clause.is_none() && !i.columns.is_empty())
914 .map(|i| i.columns.clone());
915
916 let mut all_indexes: Vec<IndexAttr> = container.indexes;
918 for field in &named.named {
919 let ident = field.ident.as_ref().expect("named");
920 let col = ident_name(ident);
922 if let Ok(fa) = parse_field_attrs(field) {
924 if fa.index {
925 let col_name = fa.column.clone().unwrap_or_else(|| col.clone());
926 let auto_name = capped_index_name(if fa.index_unique {
927 format!("{table}_{col_name}_uq_idx")
928 } else {
929 format!("{table}_{col_name}_idx")
930 });
931 all_indexes.push(IndexAttr {
932 span: field.span(),
933 name: fa.index_name.or(Some(auto_name)),
934 columns: vec![col_name],
935 unique: fa.index_unique,
936 method: fa.index_method,
937 where_clause: None,
938 include: Vec::new(),
939 });
940 }
941 }
942 }
943 for field in &named.named {
945 let (Ok(fa), Ok(det)) = (parse_field_attrs(field), detect_type(&field.ty)) else {
946 continue;
947 };
948 if det.kind != DetectedKind::String || fa.max_length.is_some() {
949 continue;
950 }
951 let col = fa
952 .column
953 .clone()
954 .unwrap_or_else(|| ident_name(field.ident.as_ref().expect("named")));
955 let relation = det.fk_inner.is_some() || fa.fk.is_some() || fa.o2o.is_some();
957 if fa.primary_key
958 || fa.unique
959 || relation
960 || all_indexes.iter().any(|i| i.columns.contains(&col))
961 {
962 return Err(syn::Error::new_spanned(
963 field,
964 "a primary-key, unique, indexed or foreign-key `String` needs \
965 `max_length`: MySQL cannot index an unbounded (LONGTEXT) column",
966 ));
967 }
968 }
969
970 let model_impl = model_impl_tokens(
971 struct_name,
972 &model_name,
973 &table,
974 display.as_deref(),
975 app_label.as_deref(),
976 container.admin.as_ref(),
977 &container.default_order,
978 &collected.field_schemas,
979 collected.soft_delete_column.as_deref(),
980 container.permissions,
981 audit_track_names.as_deref(),
982 &container.m2m,
983 &all_indexes,
984 &container.checks,
985 &container.excludes,
986 &container.composite_fks,
987 &container.generic_fks,
988 container.scope.as_deref(),
989 container.is_view,
990 container.verbose_name.as_deref(),
991 container.verbose_name_plural.as_deref(),
992 container.managed,
993 container.base_manager_name.as_deref(),
994 container.order_with_respect_to.as_deref(),
995 container.proxy,
996 &container.required_db_features,
997 container.required_db_vendor.as_deref(),
998 container.default_related_name.as_deref(),
999 container.db_table_comment.as_deref(),
1000 container
1001 .get_latest_by
1002 .as_ref()
1003 .map(|(c, d, _)| (c.as_str(), *d)),
1004 &container.extra_permissions,
1005 &container.default_permissions,
1006 &container.global_scopes,
1007 &container.reverse_has_relations,
1008 &container.generic_has_relations,
1009 );
1010 let module_ident = column_module_ident(struct_name);
1011 let column_consts = column_const_tokens(&module_ident, &collected.column_entries);
1012 let audited_fields: Option<Vec<&ColumnEntry>> = container.audit.as_ref().map(|audit| {
1013 let track_set: Option<std::collections::HashSet<&str>> = audit
1014 .track
1015 .as_ref()
1016 .map(|(names, _)| names.iter().map(String::as_str).collect());
1017 collected
1018 .column_entries
1019 .iter()
1020 .filter(|c| {
1021 track_set
1022 .as_ref()
1023 .map_or(true, |s| s.contains(c.name.as_str()))
1024 })
1025 .collect()
1026 });
1027 let inherent_impl = inherent_impl_tokens(
1028 struct_name,
1029 &collected,
1030 collected.primary_key.as_ref(),
1031 &column_consts,
1032 audited_fields.as_deref(),
1033 upsert_unique.as_deref(),
1034 &container.manager_fns,
1035 );
1036 let column_module = column_module_tokens(&module_ident, struct_name, &collected.column_entries);
1037 let from_row_impl = from_row_impl_tokens(
1038 struct_name,
1039 &collected.from_row_inits,
1040 &collected.from_row_inits_my,
1041 );
1042 let reverse_helpers = reverse_helper_tokens(
1043 struct_name,
1044 &collected.fk_relations,
1045 container.default_related_name.as_deref(),
1046 )?;
1047 let m2m_accessors = m2m_accessor_tokens(struct_name, &container.m2m);
1048 let generic_m2m_accessors = generic_m2m_accessor_tokens(struct_name, &container.generic_m2m);
1049 let through_accessors = through_accessor_tokens(struct_name, &container.through_relations);
1051 let reverse_has_accessors = reverse_has_accessor_tokens(
1053 struct_name,
1054 &container.reverse_has_relations,
1055 &collected.column_entries,
1056 );
1057 let generic_fk_accessors = generic_fk_accessor_tokens(
1058 struct_name,
1059 &container.generic_fks,
1060 &collected.column_entries,
1061 );
1062
1063 Ok(quote! {
1064 #model_impl
1065 #inherent_impl
1066 #from_row_impl
1067 #column_module
1068 #reverse_helpers
1069 #m2m_accessors
1070 #generic_m2m_accessors
1071 #through_accessors
1072 #reverse_has_accessors
1073 #generic_fk_accessors
1074
1075 #root::core::inventory::submit! {
1076 #root::core::ModelEntry::new(
1081 <#struct_name as #root::core::Model>::SCHEMA,
1082 ::core::module_path!(),
1083 )
1084 .with_audited(
1085 <#struct_name as #root::core::Model>::__rustango_audited_update,
1086 <#struct_name as #root::core::Model>::__rustango_audited_delete,
1087 )
1088 .with_audited_create(<#struct_name as #root::core::Model>::__rustango_audited_create)
1089 .with_audited_update_record(
1090 <#struct_name as #root::core::Model>::__rustango_audited_update_record,
1091 )
1092 .with_audited_cascade(<#struct_name as #root::core::Model>::__rustango_audited_cascade)
1093 }
1094 })
1095}
1096
1097fn check_known_columns(
1100 indexes: &[IndexAttr],
1101 get_latest_by: &Option<(String, bool, proc_macro2::Span)>,
1102 collected: &CollectedFields,
1103) -> syn::Result<()> {
1104 let is_column = |c: &str| collected.column_entries.iter().any(|e| e.column == c);
1105 let plain = |c: &str| c.chars().all(|ch| ch.is_ascii_alphanumeric() || ch == '_');
1107 let mut found: Vec<(proc_macro2::Span, String)> = Vec::new();
1108 for idx in indexes {
1109 for col in idx.columns.iter().filter(|c| plain(c) && !is_column(c)) {
1110 found.push((
1111 idx.span,
1112 format!("index column `{col}` is not a column of this model; its DDL will fail"),
1113 ));
1114 }
1115 }
1116 if let Some((col, _, span)) = get_latest_by {
1117 if !is_column(col) && !collected.field_names.iter().any(|n| n == col) {
1118 found.push((
1119 *span,
1120 format!("`get_latest_by`: `{col}` is not a field of this model"),
1121 ));
1122 }
1123 }
1124 let mut errors = found
1125 .into_iter()
1126 .map(|(span, note)| syn::Error::new(span, note));
1127 let Some(mut first) = errors.next() else {
1128 return Ok(());
1129 };
1130 first.extend(errors);
1131 Err(first)
1132}
1133
1134fn join_guard_tokens(rel: &FkRelation, receiver: &TokenStream2) -> TokenStream2 {
1137 let root = rustango_root();
1138 let parent_ty = &rel.parent_type;
1139 let field_ident = &rel.field_ident;
1140 let null_fk = if rel.nullable {
1141 quote! {
1142 if #receiver.#field_ident.is_none() {
1143 return ::core::result::Result::Ok(false);
1144 }
1145 }
1146 } else {
1147 quote! {}
1148 };
1149 quote! {
1150 #null_fk
1151 #root::sql::__rustango_require_join(
1152 row, <#parent_ty as #root::core::Model>::SCHEMA, alias,
1153 )?;
1154 }
1155}
1156
1157fn loaded_reuse_tokens(
1161 rel: &FkRelation,
1162 receiver: &TokenStream2,
1163 load: &TokenStream2,
1164) -> TokenStream2 {
1165 let root = rustango_root();
1166 let field_ident = &rel.field_ident;
1167 let pattern = if rel.nullable {
1168 quote!(::core::option::Option::Some(#root::sql::ForeignKey::Loaded { value: __loaded, .. }))
1169 } else {
1170 quote!(#root::sql::ForeignKey::Loaded { value: __loaded, .. })
1171 };
1172 quote! {
1173 if let #pattern = &mut #receiver.#field_ident {
1174 if let ::core::option::Option::Some(__r) = __rest {
1175 let _ = #load(&mut **__loaded, row, __r, &__next_alias)?;
1176 }
1177 return ::core::result::Result::Ok(true);
1178 }
1179 }
1180}
1181
1182fn loaded_assign_tokens(rel: &FkRelation, receiver: &TokenStream2) -> TokenStream2 {
1185 let root = rustango_root();
1186 let field_ident = &rel.field_ident;
1187 if rel.nullable {
1188 quote! {
1189 if let ::core::option::Option::Some(__fk) = &mut #receiver.#field_ident {
1190 let _pk = ::core::clone::Clone::clone(__fk.pk_ref());
1191 *__fk = #root::sql::ForeignKey::loaded(_pk, _parent);
1192 }
1193 }
1194 } else {
1195 quote! {
1196 let _pk = ::core::clone::Clone::clone(#receiver.#field_ident.pk_ref());
1197 #receiver.#field_ident = #root::sql::ForeignKey::loaded(_pk, _parent);
1198 }
1199 }
1200}
1201
1202fn load_related_impl_tokens(struct_name: &syn::Ident, fk_relations: &[FkRelation]) -> TokenStream2 {
1213 let root = rustango_root();
1214 let arms = fk_relations.iter().map(|rel| {
1215 let parent_ty = &rel.parent_type;
1216 let field_ident = &rel.field_ident;
1217 let fk_name = ident_name(field_ident);
1218 let fk_col = fk_name.as_str();
1219 let guard = join_guard_tokens(rel, "e!(self));
1220 let reuse = loaded_reuse_tokens(
1221 rel,
1222 "e!(self),
1223 "e!(#root::sql::LoadRelated::__rustango_load_related),
1224 );
1225 let assign = loaded_assign_tokens(rel, "e!(self));
1226 quote! {
1227 #fk_col => {
1228 #guard
1229 #reuse
1230 let mut _parent: #parent_ty = <#parent_ty>::__rustango_from_aliased_row(row, alias)?;
1231 if let ::core::option::Option::Some(__r) = __rest {
1237 let _ = #root::sql::LoadRelated::__rustango_load_related(
1238 &mut _parent, row, __r, &__next_alias,
1239 )?;
1240 }
1241 #assign
1242 ::core::result::Result::Ok(true)
1243 }
1244 }
1245 });
1246 quote! {
1247 #[cfg(feature = "postgres")]
1248 impl #root::sql::LoadRelated for #struct_name {
1249 #[allow(unused_variables)]
1250 fn __rustango_load_related(
1251 &mut self,
1252 row: &#root::sql::sqlx::postgres::PgRow,
1253 field_name: &str,
1254 alias: &str,
1255 ) -> ::core::result::Result<bool, #root::sql::sqlx::Error> {
1256 let (__base, __rest): (&str, ::core::option::Option<&str>) =
1262 match field_name.split_once("__") {
1263 ::core::option::Option::Some((b, r)) => (b, ::core::option::Option::Some(r)),
1264 ::core::option::Option::None => (field_name, ::core::option::Option::None),
1265 };
1266 let __next_alias: ::std::string::String = match __rest {
1267 ::core::option::Option::Some(__r) => {
1268 let __rb = __r.split_once("__").map(|(b, _)| b).unwrap_or(__r);
1269 ::std::format!("{}__{}", alias, __rb)
1270 }
1271 ::core::option::Option::None => ::std::string::String::new(),
1272 };
1273 match __base {
1274 #( #arms )*
1275 _ => ::core::result::Result::Ok(false),
1276 }
1277 }
1278 }
1279 }
1280}
1281
1282fn load_related_impl_my_tokens(
1290 struct_name: &syn::Ident,
1291 fk_relations: &[FkRelation],
1292) -> TokenStream2 {
1293 let root = rustango_root();
1294 let arms = fk_relations.iter().map(|rel| {
1295 let parent_ty = &rel.parent_type;
1296 let field_ident = &rel.field_ident;
1297 let fk_name = ident_name(field_ident);
1298 let fk_col = fk_name.as_str();
1299 let guard = join_guard_tokens(rel, "e!(__self));
1300 let reuse = loaded_reuse_tokens(
1301 rel,
1302 "e!(__self),
1303 "e!(#root::sql::LoadRelatedMy::__rustango_load_related_my),
1304 );
1305 let assign = loaded_assign_tokens(rel, "e!(__self));
1306 quote! {
1311 #fk_col => {
1312 #guard
1313 #reuse
1314 let mut _parent: #parent_ty =
1315 <#parent_ty>::__rustango_from_aliased_my_row(row, alias)?;
1316 if let ::core::option::Option::Some(__r) = __rest {
1319 let _ = #root::sql::LoadRelatedMy::__rustango_load_related_my(
1320 &mut _parent, row, __r, &__next_alias,
1321 )?;
1322 }
1323 #assign
1324 ::core::result::Result::Ok(true)
1325 }
1326 }
1327 });
1328 quote! {
1329 #root::__impl_my_load_related!(#struct_name, |__self, row, field_name, alias, __rest, __next_alias| {
1330 #( #arms )*
1331 });
1332 }
1333}
1334
1335fn load_related_impl_sqlite_tokens(
1339 struct_name: &syn::Ident,
1340 fk_relations: &[FkRelation],
1341) -> TokenStream2 {
1342 let root = rustango_root();
1343 let arms = fk_relations.iter().map(|rel| {
1344 let parent_ty = &rel.parent_type;
1345 let field_ident = &rel.field_ident;
1346 let fk_name = ident_name(field_ident);
1347 let fk_col = fk_name.as_str();
1348 let guard = join_guard_tokens(rel, "e!(__self));
1349 let reuse = loaded_reuse_tokens(
1350 rel,
1351 "e!(__self),
1352 "e!(#root::sql::LoadRelatedSqlite::__rustango_load_related_sqlite),
1353 );
1354 let assign = loaded_assign_tokens(rel, "e!(__self));
1355 quote! {
1356 #fk_col => {
1357 #guard
1358 #reuse
1359 let mut _parent: #parent_ty =
1360 <#parent_ty>::__rustango_from_aliased_sqlite_row(row, alias)?;
1361 if let ::core::option::Option::Some(__r) = __rest {
1364 let _ = #root::sql::LoadRelatedSqlite::__rustango_load_related_sqlite(
1365 &mut _parent, row, __r, &__next_alias,
1366 )?;
1367 }
1368 #assign
1369 ::core::result::Result::Ok(true)
1370 }
1371 }
1372 });
1373 quote! {
1374 #root::__impl_sqlite_load_related!(#struct_name, |__self, row, field_name, alias, __rest, __next_alias| {
1375 #( #arms )*
1376 });
1377 }
1378}
1379
1380fn fk_pk_access_impl_tokens(struct_name: &syn::Ident, fk_relations: &[FkRelation]) -> TokenStream2 {
1388 let root = rustango_root();
1389 let arms = fk_relations.iter().map(|rel| {
1390 let field_ident = &rel.field_ident;
1391 let fk_name = ident_name(field_ident);
1392 let fk_col = fk_name.as_str();
1393 if rel.pk_kind == DetectedKind::I64 {
1394 if rel.nullable {
1400 quote! {
1401 #fk_col => self.#field_ident
1402 .as_ref()
1403 .map(|fk| #root::sql::ForeignKey::pk(fk)),
1404 }
1405 } else {
1406 quote! {
1407 #fk_col => ::core::option::Option::Some(self.#field_ident.pk()),
1408 }
1409 }
1410 } else {
1411 quote! {
1419 #fk_col => ::core::option::Option::None,
1420 }
1421 }
1422 });
1423 let value_arms = fk_relations.iter().map(|rel| {
1429 let field_ident = &rel.field_ident;
1430 let fk_name = ident_name(field_ident);
1431 let fk_col = fk_name.as_str();
1432 if rel.nullable {
1433 quote! {
1434 #fk_col => self.#field_ident
1435 .as_ref()
1436 .map(|fk| ::core::convert::Into::<#root::core::SqlValue>::into(
1437 #root::sql::ForeignKey::pk(fk)
1438 )),
1439 }
1440 } else {
1441 quote! {
1442 #fk_col => ::core::option::Option::Some(
1443 ::core::convert::Into::<#root::core::SqlValue>::into(
1444 self.#field_ident.pk()
1445 )
1446 ),
1447 }
1448 }
1449 });
1450 quote! {
1451 impl #root::sql::FkPkAccess for #struct_name {
1452 #[allow(unused_variables)]
1453 fn __rustango_fk_pk(&self, field_name: &str) -> ::core::option::Option<i64> {
1454 match field_name {
1455 #( #arms )*
1456 _ => ::core::option::Option::None,
1457 }
1458 }
1459 #[allow(unused_variables)]
1460 fn __rustango_fk_pk_value(
1461 &self,
1462 field_name: &str,
1463 ) -> ::core::option::Option<#root::core::SqlValue> {
1464 match field_name {
1465 #( #value_arms )*
1466 _ => ::core::option::Option::None,
1467 }
1468 }
1469 }
1470 }
1471}
1472
1473const NO_REVERSE_ACCESSOR: &str = "+";
1475
1476fn reverse_helper_tokens(
1484 child_ident: &syn::Ident,
1485 fk_relations: &[FkRelation],
1486 default_related_name: Option<&str>,
1487) -> syn::Result<TokenStream2> {
1488 let root = rustango_root();
1489 let fk_relations: Vec<&FkRelation> = fk_relations
1491 .iter()
1492 .filter(|rel| rel.related_name.as_deref() != Some(NO_REVERSE_ACCESSOR))
1493 .collect();
1494 if fk_relations.is_empty() {
1495 return Ok(TokenStream2::new());
1496 }
1497 let default_pg_suffix = default_related_name
1511 .map(str::to_owned)
1512 .unwrap_or_else(|| format!("{}_set", to_snake_case(&ident_name(child_ident))));
1513 let name_of = |rel: &FkRelation| {
1514 rel.related_name
1515 .clone()
1516 .unwrap_or_else(|| default_pg_suffix.clone())
1517 };
1518 for (i, rel) in fk_relations.iter().enumerate() {
1520 let parent_ty = &rel.parent_type;
1521 let parent = quote!(#parent_ty).to_string();
1522 let name = name_of(rel);
1523 if let Some(first) = fk_relations[..i].iter().find(|r| {
1524 let other = &r.parent_type;
1525 quote!(#other).to_string() == parent && name_of(r) == name
1526 }) {
1527 return Err(syn::Error::new_spanned(
1528 &rel.field_ident,
1529 format!(
1530 "`{}` and `{}` both point at `{parent}` and would both add `{parent}::{name}`; \
1531 give one `#[rustango(related_name = \"...\")]`, or `related_name = \"+\"` for none",
1532 first.field_ident, rel.field_ident
1533 ),
1534 ));
1535 }
1536 }
1537 let impls = fk_relations.iter().map(|rel| {
1538 let pg_suffix = name_of(rel);
1539 let pool_suffix = format!("{}_pool", pg_suffix);
1540 let pg_method_ident = syn::Ident::new(&pg_suffix, child_ident.span());
1541 let pool_method_ident = syn::Ident::new(&pool_suffix, child_ident.span());
1542 let parent_ty = &rel.parent_type;
1543 let fk_name = ident_name(&rel.field_ident);
1544 let fk_col = fk_name.as_str();
1545 let doc = format!(
1546 "Fetch every `{child_ident}` whose `{fk_col}` foreign key points at this row. \
1547 Single SQL query — `SELECT … FROM <{child_ident} table> WHERE {fk_col} = $1` — \
1548 generated from the FK declaration on `{child_ident}::{fk_col}`. Composes with \
1549 further `{child_ident}::objects()` filters via direct queryset use."
1550 );
1551 quote! {
1552 impl #parent_ty {
1553 #[doc = #doc]
1554 pub async fn #pg_method_ident<'_c, _E: #root::sql::SqlExecutor<'_c>>(
1562 &self,
1563 _executor: _E,
1564 ) -> ::core::result::Result<
1565 ::std::vec::Vec<#child_ident>,
1566 #root::sql::ExecError,
1567 > {
1568 let _key = #root::sql::__reverse_fk_key::<Self, #child_ident>(self, #fk_col)?;
1569 #root::query::QuerySet::<#child_ident>::new()
1570 .filter_op(#fk_col, #root::core::Op::Eq, _key)
1571 .fetch_on(_executor)
1572 .await
1573 }
1574
1575 #[deprecated(since = "0.62.0", note = "drop the `_pool` suffix")]
1580 pub async fn #pool_method_ident(
1581 &self,
1582 pool: &#root::sql::Pool,
1583 ) -> ::core::result::Result<
1584 ::std::vec::Vec<#child_ident>,
1585 #root::sql::ExecError,
1586 > {
1587 self.#pg_method_ident(pool).await
1588 }
1589 }
1590 }
1591 });
1592 Ok(quote! { #( #impls )* })
1593}
1594
1595fn generic_fk_accessor_tokens(
1609 struct_name: &syn::Ident,
1610 generic_fks: &[GenericFkAttr],
1611 column_entries: &[ColumnEntry],
1612) -> TokenStream2 {
1613 let root = rustango_root();
1614 if generic_fks.is_empty() {
1615 return TokenStream2::new();
1616 }
1617 let methods = generic_fks.iter().filter_map(|gfk| {
1618 let ct_ident = column_entries
1624 .iter()
1625 .find(|c| c.column == gfk.ct_column)
1626 .map(|c| c.ident.clone())?;
1627 let pk_ident = column_entries
1628 .iter()
1629 .find(|c| c.column == gfk.pk_column)
1630 .map(|c| c.ident.clone())?;
1631
1632 let accessor_ident =
1633 syn::Ident::new(&format!("{}_pool", gfk.name), struct_name.span());
1634 let setter_ident =
1635 syn::Ident::new(&format!("set_{}_for", gfk.name), struct_name.span());
1636 let name_literal = gfk.name.as_str();
1637
1638 Some(quote! {
1639 #[doc = concat!(
1640 "Resolve the polymorphic `",
1641 #name_literal,
1642 "` relation. Reads `self.",
1643 stringify!(#ct_ident),
1644 "` + `self.",
1645 stringify!(#pk_ident),
1646 "`, looks up the matching `ContentType`, and fetches the target row as a JSON map.\n\n",
1647 "Returns `Ok(None)` when the ContentType is stale / unseeded or the target row was deleted. Emitted by `#[rustango(generic_fk(name = \"",
1648 #name_literal,
1649 "\", ...))]`."
1650 )]
1651 pub async fn #accessor_ident(
1652 &self,
1653 pool: &#root::sql::Pool,
1654 ) -> ::core::result::Result<
1655 ::core::option::Option<#root::__serde_json::Value>,
1656 #root::sql::ExecError,
1657 > {
1658 let gfk = #root::contenttypes::GenericForeignKey::new(
1659 self.#ct_ident as i64,
1660 self.#pk_ident as i64,
1661 );
1662 gfk.get_object(pool).await
1663 }
1664
1665 #[doc = concat!(
1666 "Set the polymorphic `",
1667 #name_literal,
1668 "` target. Looks up the `ContentType` for `T` via the cached registry, then assigns both `self.",
1669 stringify!(#ct_ident),
1670 "` and `self.",
1671 stringify!(#pk_ident),
1672 "`.\n\nFollow with `self.insert(pool)` or `self.update(pool)` to persist. Emitted by `#[rustango(generic_fk(name = \"",
1673 #name_literal,
1674 "\", ...))]`."
1675 )]
1676 pub async fn #setter_ident<T: #root::core::Model>(
1677 &mut self,
1678 pool: &#root::sql::Pool,
1679 target_pk: i64,
1680 ) -> ::core::result::Result<(), #root::sql::ExecError> {
1681 let gfk = #root::contenttypes::GenericForeignKey::for_target::<T>(
1682 pool,
1683 target_pk,
1684 ).await?;
1685 self.#ct_ident = gfk.content_type_id as _;
1686 self.#pk_ident = gfk.object_pk as _;
1687 ::core::result::Result::Ok(())
1688 }
1689 })
1690 });
1691 quote! {
1692 impl #struct_name {
1693 #( #methods )*
1694 }
1695 }
1696}
1697
1698fn m2m_accessor_tokens(struct_name: &syn::Ident, m2m_relations: &[M2MAttr]) -> TokenStream2 {
1699 let root = rustango_root();
1700 if m2m_relations.is_empty() {
1701 return TokenStream2::new();
1702 }
1703 let methods = m2m_relations.iter().map(|rel| {
1704 let method_name = format!("{}_m2m", rel.name);
1705 let method_ident = syn::Ident::new(&method_name, struct_name.span());
1706 let through = rel.through.as_str();
1707 let src_col = rel.src.as_str();
1708 let dst_col = rel.dst.as_str();
1709 quote! {
1710 pub fn #method_ident(&self) -> #root::sql::M2MManager {
1711 #root::sql::M2MManager {
1712 src_pk: self.__rustango_pk_value(),
1713 through: #through,
1714 src_col: #src_col,
1715 dst_col: #dst_col,
1716 }
1717 }
1718 }
1719 });
1720 quote! {
1721 impl #struct_name {
1722 #( #methods )*
1723 }
1724 }
1725}
1726
1727fn generic_m2m_accessor_tokens(
1730 struct_name: &syn::Ident,
1731 relations: &[GenericM2MAttr],
1732) -> TokenStream2 {
1733 let root = rustango_root();
1734 if relations.is_empty() {
1735 return TokenStream2::new();
1736 }
1737 let methods = relations.iter().map(|rel| {
1738 let method_ident = syn::Ident::new(&format!("{}_m2m", rel.name), struct_name.span());
1739 let through = rel.through.as_str();
1740 let pk_col = rel.pk_column.as_str();
1741 let ct_col = rel.ct_column.as_str();
1742 let related_col = rel.related_column.as_str();
1743 quote! {
1744 pub fn #method_ident(&self) -> #root::sql::GenericM2MManager {
1745 #root::sql::GenericM2MManager {
1746 src_pk: self.__rustango_pk_value(),
1747 src_schema: <Self as #root::core::Model>::SCHEMA,
1748 through: #through,
1749 pk_col: #pk_col,
1750 ct_col: #ct_col,
1751 dst_col: #related_col,
1752 }
1753 }
1754 }
1755 });
1756 let entries = relations.iter().map(|rel| {
1758 let through = rel.through.as_str();
1759 let pk_col = rel.pk_column.as_str();
1760 let ct_col = rel.ct_column.as_str();
1761 quote! {
1762 #root::core::inventory::submit! {
1763 #root::core::GenericM2MEntry::new(
1764 <#struct_name as #root::core::Model>::SCHEMA,
1765 #through,
1766 #pk_col,
1767 #ct_col,
1768 )
1769 }
1770 }
1771 });
1772 quote! {
1773 impl #struct_name {
1774 #( #methods )*
1775 }
1776 #( #entries )*
1777 }
1778}
1779
1780fn reverse_has_accessor_tokens(
1802 struct_name: &syn::Ident,
1803 reverse_has_relations: &[ReverseHasAttr],
1804 columns: &[ColumnEntry],
1805) -> TokenStream2 {
1806 let root = rustango_root();
1807 if reverse_has_relations.is_empty() {
1808 return TokenStream2::new();
1809 }
1810 let methods = reverse_has_relations.iter().map(|rel| {
1811 let exists_name = format!("{}_exists_expr", rel.name);
1812 let not_exists_name = format!("{}_not_exists_expr", rel.name);
1813 let count_name = format!("{}_count", rel.name);
1814 let fetch_name = format!("{}_fetch", rel.name);
1815 let first_name = format!("{}_first", rel.name);
1816 let pluck_name = format!("{}_pluck", rel.name);
1817 let accessor_name = rel.name.as_str();
1818 let exists_ident = syn::Ident::new(&exists_name, struct_name.span());
1819 let not_exists_ident = syn::Ident::new(¬_exists_name, struct_name.span());
1820 let count_ident = syn::Ident::new(&count_name, struct_name.span());
1821 let fetch_ident = syn::Ident::new(&fetch_name, struct_name.span());
1822 let first_ident = syn::Ident::new(&first_name, struct_name.span());
1823 let pluck_ident = syn::Ident::new(&pluck_name, struct_name.span());
1824 let accessor_ident = syn::Ident::new(accessor_name, struct_name.span());
1825 let child = &rel.child;
1826 let child_fk_column = rel.child_fk_column.as_str();
1827 let self_pk_column = rel.self_pk_column();
1828 let self_value = columns
1830 .iter()
1831 .find(|c| c.column == self_pk_column)
1832 .map_or_else(
1833 || quote! { self.__rustango_pk_value() },
1834 |c| {
1835 let ident = &c.ident;
1836 quote! {
1837 ::core::convert::Into::<#root::core::SqlValue>::into(
1838 ::core::clone::Clone::clone(&self.#ident)
1839 )
1840 }
1841 },
1842 );
1843 let exists_doc = format!(
1844 "Eloquent `whereHas` analog — yields `EXISTS (SELECT 1 \
1845 FROM <{child}> WHERE {child_fk_column} = <outer>.{self_pk_column})`. \
1846 Drop into `QuerySet::<{struct_name}>::where_raw(...)` to \
1847 filter to {struct_name}s with at least one matching child.",
1848 );
1849 let not_exists_doc = format!(
1850 "Eloquent `whereDoesntHave` analog — yields `NOT EXISTS \
1851 (SELECT 1 FROM <{child}> WHERE {child_fk_column} = \
1852 <outer>.{self_pk_column})`. Drop into \
1853 `QuerySet::<{struct_name}>::where_raw(...)` to filter to \
1854 {struct_name}s with **no** matching child.",
1855 );
1856 let count_doc = format!(
1857 "Eloquent `$model->{name}->count()` analog — returns \
1858 the number of `{child}` rows whose `{child_fk_column}` \
1859 matches this `{struct_name}` instance's `{self_pk_column}`. \
1860 Issued as `SELECT COUNT(*) FROM <{child}> WHERE \
1861 {child_fk_column} = <self.{self_pk_column}>`.",
1862 name = rel.name,
1863 );
1864 let accessor_doc = format!(
1865 "Eloquent `$model->{name}` accessor — returns a \
1866 `QuerySet<{child}>` filtered to rows whose \
1867 `{child_fk_column}` matches this `{struct_name}` \
1868 instance's `{self_pk_column}`. **Chainable**: compose `.filter()` \
1869 / `.order_by()` / `.limit()` etc. on top, then call \
1870 `.fetch(&pool)` (the QuerySet trait method) when \
1871 done. For the simple \"fetch all\" hot path with no \
1872 further composition, prefer the bare-name \
1873 `{name}_fetch(&pool)` companion.",
1874 name = rel.name,
1875 );
1876 let fetch_doc = format!(
1877 "Eloquent `$model->{name}->get()` — bare-name hot-path \
1878 over `{name}(&self).fetch(&pool)`. Use this when \
1879 you don't need further `.filter()` / `.order_by()` \
1880 composition; falls back to the chainable accessor when \
1881 you do. Avoids the `_pool` suffix on the most common \
1882 call-site shape.",
1883 name = rel.name,
1884 );
1885 quote! {
1886 #[doc = #accessor_doc]
1887 pub fn #accessor_ident(&self) -> #root::query::QuerySet<#child> {
1888 #root::query::QuerySet::<#child>::new()
1889 .filter(#child_fk_column, #self_value)
1890 }
1891
1892 #[doc = #fetch_doc]
1893 pub async fn #fetch_ident(
1894 &self,
1895 pool: &#root::sql::Pool,
1896 ) -> ::core::result::Result<
1897 ::std::vec::Vec<#child>,
1898 #root::sql::ExecError,
1899 > {
1900 use #root::sql::FetcherPool as _;
1901 self.#accessor_ident().fetch(pool).await
1902 }
1903
1904 pub async fn #first_ident(
1911 &self,
1912 pool: &#root::sql::Pool,
1913 ) -> ::core::result::Result<
1914 ::core::option::Option<#child>,
1915 #root::sql::ExecError,
1916 > {
1917 self.#accessor_ident().first(pool).await
1918 }
1919
1920 pub async fn #pluck_ident<U>(
1925 &self,
1926 col: &'static str,
1927 pool: &#root::sql::Pool,
1928 ) -> ::core::result::Result<
1929 ::std::vec::Vec<U>,
1930 #root::sql::ExecError,
1931 >
1932 where
1933 U: #root::sql::FlatScalar,
1934 {
1935 self.#accessor_ident()
1936 .values_list_flat(col)
1937 .fetch::<U>(pool)
1938 .await
1939 }
1940
1941 #[doc = #exists_doc]
1942 pub fn #exists_ident() -> #root::core::WhereExpr {
1943 use #root::core::{Expr, Model as _, Op, SelectQuery, WhereExpr};
1944 let child_schema =
1945 <#child as #root::core::Model>::SCHEMA;
1946 let inner = SelectQuery::new(child_schema).where_clause(WhereExpr::ExprCompare {
1947 lhs: Expr::Column(#child_fk_column),
1948 op: Op::Eq,
1949 rhs: Expr::OuterRef(#self_pk_column),
1950 });
1951 WhereExpr::Exists(::std::boxed::Box::new(inner))
1952 }
1953
1954 #[doc = #not_exists_doc]
1955 pub fn #not_exists_ident() -> #root::core::WhereExpr {
1956 use #root::core::{Expr, Model as _, Op, SelectQuery, WhereExpr};
1957 let child_schema =
1958 <#child as #root::core::Model>::SCHEMA;
1959 let inner = SelectQuery::new(child_schema).where_clause(WhereExpr::ExprCompare {
1960 lhs: Expr::Column(#child_fk_column),
1961 op: Op::Eq,
1962 rhs: Expr::OuterRef(#self_pk_column),
1963 });
1964 WhereExpr::NotExists(::std::boxed::Box::new(inner))
1965 }
1966
1967 #[doc = #count_doc]
1968 pub async fn #count_ident(
1969 &self,
1970 pool: &#root::sql::Pool,
1971 ) -> ::core::result::Result<
1972 i64,
1973 #root::sql::ExecError,
1974 > {
1975 use #root::sql::CounterPool as _;
1976 self.#accessor_ident().count(pool).await
1977 }
1978 }
1979 });
1980 quote! {
1981 impl #struct_name {
1982 #( #methods )*
1983 }
1984 }
1985}
1986
1987fn through_accessor_tokens(
2005 struct_name: &syn::Ident,
2006 through_relations: &[ThroughAttr],
2007) -> TokenStream2 {
2008 let root = rustango_root();
2009 if through_relations.is_empty() {
2010 return TokenStream2::new();
2011 }
2012 let methods = through_relations.iter().map(|rel| {
2013 let method_name = format!("{}_through", rel.name);
2014 let count_name = format!("{}_through_count", rel.name);
2015 let fetch_name = format!("{}_through_fetch", rel.name);
2016 let first_name = format!("{}_through_first", rel.name);
2017 let pluck_name = format!("{}_through_pluck", rel.name);
2018 let method_ident = syn::Ident::new(&method_name, struct_name.span());
2019 let count_ident = syn::Ident::new(&count_name, struct_name.span());
2020 let fetch_ident = syn::Ident::new(&fetch_name, struct_name.span());
2021 let first_ident = syn::Ident::new(&first_name, struct_name.span());
2022 let pluck_ident = syn::Ident::new(&pluck_name, struct_name.span());
2023 let far = &rel.far;
2024 let intermediate = &rel.intermediate;
2025 let far_fk_column = rel.far_fk_column.as_str();
2026 let intermediate_fk_column = rel.intermediate_fk_column.as_str();
2027 let intermediate_pk_column = rel.intermediate_pk_column.as_str();
2028 let doc = format!(
2029 "Eloquent `hasManyThrough` accessor — returns a \
2030 `QuerySet<{far}>` whose rows reach this `{struct_name}` \
2031 instance through the intermediate `{intermediate}` table. \
2032 Generated SQL shape: \
2033 `… WHERE {far_fk_column} IN (SELECT \
2034 {intermediate_pk_column} FROM <{intermediate}> WHERE \
2035 {intermediate_fk_column} = self.pk)`. Chainable like any \
2036 other QuerySet — compose `.filter()` / `.order_by()` / \
2037 `.limit()` etc. on top.",
2038 );
2039 let count_doc = format!(
2040 "Eloquent `$model->{name}->count()` analog for the \
2041 through-relation — returns the number of `{far}` rows \
2042 reachable through `{intermediate}`. Equivalent to \
2043 `self.{name}_through().count(pool)` but spelled \
2044 as a bare instance method for parity with the \
2045 `reverse_has` `<name>_count` shape.",
2046 name = rel.name,
2047 );
2048 quote! {
2049 #[doc = #doc]
2050 pub fn #method_ident(&self) -> #root::query::QuerySet<#far> {
2051 use #root::core::{Filter, Model as _, Op, SelectQuery, WhereExpr};
2052 let intermediate_schema =
2053 <#intermediate as #root::core::Model>::SCHEMA;
2054 let sub = SelectQuery::new(intermediate_schema)
2055 .where_clause(WhereExpr::Predicate(Filter::new(
2056 #intermediate_fk_column,
2057 Op::Eq,
2058 self.__rustango_pk_value(),
2059 )))
2060 .projection(::std::vec![#intermediate_pk_column]);
2061 #root::query::QuerySet::<#far>::new().where_raw(
2062 WhereExpr::InSubquery {
2063 column: #far_fk_column,
2064 negated: false,
2065 subquery: ::std::boxed::Box::new(sub),
2066 },
2067 )
2068 }
2069
2070 #[doc = #count_doc]
2071 pub async fn #count_ident(
2072 &self,
2073 pool: &#root::sql::Pool,
2074 ) -> ::core::result::Result<
2075 i64,
2076 #root::sql::ExecError,
2077 > {
2078 use #root::sql::CounterPool as _;
2079 self.#method_ident().count(pool).await
2080 }
2081
2082 pub async fn #fetch_ident(
2090 &self,
2091 pool: &#root::sql::Pool,
2092 ) -> ::core::result::Result<
2093 ::std::vec::Vec<#far>,
2094 #root::sql::ExecError,
2095 > {
2096 use #root::sql::FetcherPool as _;
2097 self.#method_ident().fetch(pool).await
2098 }
2099
2100 pub async fn #first_ident(
2107 &self,
2108 pool: &#root::sql::Pool,
2109 ) -> ::core::result::Result<
2110 ::core::option::Option<#far>,
2111 #root::sql::ExecError,
2112 > {
2113 self.#method_ident().first(pool).await
2114 }
2115
2116 pub async fn #pluck_ident<U>(
2120 &self,
2121 col: &'static str,
2122 pool: &#root::sql::Pool,
2123 ) -> ::core::result::Result<
2124 ::std::vec::Vec<U>,
2125 #root::sql::ExecError,
2126 >
2127 where
2128 U: #root::sql::FlatScalar,
2129 {
2130 self.#method_ident()
2131 .values_list_flat(col)
2132 .fetch::<U>(pool)
2133 .await
2134 }
2135 }
2136 });
2137 quote! {
2138 impl #struct_name {
2139 #( #methods )*
2140 }
2141 }
2142}
2143
2144struct ColumnEntry {
2145 ident: syn::Ident,
2148 value_ty: Type,
2150 name: String,
2152 column: String,
2154 field_type_tokens: TokenStream2,
2156 uuid_column: bool,
2158}
2159
2160struct CollectedFields {
2161 field_schemas: Vec<TokenStream2>,
2162 from_row_inits: Vec<TokenStream2>,
2163 from_aliased_row_inits: Vec<TokenStream2>,
2167 from_row_inits_my: Vec<TokenStream2>,
2169 from_aliased_row_inits_my: Vec<TokenStream2>,
2170 insert_columns: Vec<TokenStream2>,
2173 insert_values: Vec<TokenStream2>,
2176 insert_pushes: Vec<TokenStream2>,
2181 returning_cols: Vec<TokenStream2>,
2184 auto_assigns: Vec<TokenStream2>,
2187 auto_field_idents: Vec<(syn::Ident, String, bool)>,
2191 generated_field_idents: Vec<(syn::Ident, String)>,
2196 first_db_auto: Option<(syn::Ident, Type)>,
2199 bulk_pushes_no_auto: Vec<TokenStream2>,
2203 bulk_pushes_all: Vec<TokenStream2>,
2207 bulk_columns_no_auto: Vec<TokenStream2>,
2210 bulk_columns_all: Vec<TokenStream2>,
2213 bulk_fill_in_place: Vec<TokenStream2>,
2216 rust_filled_idents: Vec<syn::Ident>,
2218 auto_now_idents: Vec<syn::Ident>,
2220 uuid_v7_fields: Vec<(syn::Ident, String)>,
2223 bulk_auto_uniformity: Vec<TokenStream2>,
2227 first_auto_ident: Option<syn::Ident>,
2230 has_auto: bool,
2232 pk_is_auto: bool,
2236 update_assignments: Vec<TokenStream2>,
2239 upsert_update_columns: Vec<TokenStream2>,
2242 primary_key: Option<(syn::Ident, String)>,
2243 column_entries: Vec<ColumnEntry>,
2244 field_names: Vec<String>,
2247 fk_relations: Vec<FkRelation>,
2252 soft_delete_column: Option<String>,
2257 soft_delete_field_ident: Option<syn::Ident>,
2261}
2262
2263#[derive(Clone)]
2264struct FkRelation {
2265 parent_type: Type,
2268 field_ident: syn::Ident,
2271 pk_kind: DetectedKind,
2276 nullable: bool,
2281 related_name: Option<String>,
2286}
2287
2288fn collect_fields(named: &syn::FieldsNamed, table: &str) -> syn::Result<CollectedFields> {
2289 let root = rustango_root();
2290 let cap = named.named.len();
2291 let mut out = CollectedFields {
2292 field_schemas: Vec::with_capacity(cap),
2293 from_row_inits: Vec::with_capacity(cap),
2294 from_aliased_row_inits: Vec::with_capacity(cap),
2295 from_row_inits_my: Vec::with_capacity(cap),
2296 from_aliased_row_inits_my: Vec::with_capacity(cap),
2297 insert_columns: Vec::with_capacity(cap),
2298 insert_values: Vec::with_capacity(cap),
2299 insert_pushes: Vec::with_capacity(cap),
2300 returning_cols: Vec::new(),
2301 auto_assigns: Vec::new(),
2302 auto_field_idents: Vec::new(),
2303 generated_field_idents: Vec::new(),
2304 first_db_auto: None,
2305 bulk_pushes_no_auto: Vec::with_capacity(cap),
2306 bulk_pushes_all: Vec::with_capacity(cap),
2307 bulk_columns_no_auto: Vec::with_capacity(cap),
2308 bulk_columns_all: Vec::with_capacity(cap),
2309 bulk_fill_in_place: Vec::new(),
2310 rust_filled_idents: Vec::new(),
2311 auto_now_idents: Vec::new(),
2312 uuid_v7_fields: Vec::new(),
2313 bulk_auto_uniformity: Vec::new(),
2314 first_auto_ident: None,
2315 has_auto: false,
2316 pk_is_auto: false,
2317 update_assignments: Vec::with_capacity(cap),
2318 upsert_update_columns: Vec::with_capacity(cap),
2319 primary_key: None,
2320 column_entries: Vec::with_capacity(cap),
2321 field_names: Vec::with_capacity(cap),
2322 fk_relations: Vec::new(),
2323 soft_delete_column: None,
2324 soft_delete_field_ident: None,
2325 };
2326
2327 for field in &named.named {
2328 let info = process_field(field, table)?;
2329 out.field_names.push(ident_name(info.ident));
2330 out.field_schemas.push(info.schema);
2331 out.from_row_inits.push(info.from_row_init);
2332 out.from_aliased_row_inits.push(info.from_aliased_row_init);
2333 out.from_row_inits_my.push(info.from_row_init_my);
2334 out.from_aliased_row_inits_my
2335 .push(info.from_aliased_row_init_my);
2336 if let Some(parent_ty) = info.fk_inner.clone() {
2337 out.fk_relations.push(FkRelation {
2338 parent_type: parent_ty,
2339 field_ident: info.ident.clone(),
2340 pk_kind: info.fk_pk_kind,
2341 nullable: info.nullable,
2342 related_name: info.related_name.clone(),
2343 });
2344 }
2345 if info.soft_delete {
2346 if out.soft_delete_column.is_some() {
2347 return Err(syn::Error::new_spanned(
2348 field,
2349 "only one field may be marked `#[rustango(soft_delete)]`",
2350 ));
2351 }
2352 out.soft_delete_column = Some(info.column.clone());
2353 out.soft_delete_field_ident = Some(info.ident.clone());
2354 }
2355 let column = info.column.as_str();
2356 let ident = info.ident;
2357 if info.generated_as.is_some() {
2366 out.column_entries.push(ColumnEntry {
2367 ident: ident.clone(),
2368 value_ty: info.value_ty.clone(),
2369 name: ident_name(ident),
2370 column: info.column.clone(),
2371 field_type_tokens: info.field_type_tokens,
2372 uuid_column: info.uuid_column,
2373 });
2374 out.returning_cols.push(quote!(#column));
2381 out.generated_field_idents
2382 .push((ident.clone(), info.column.clone()));
2383 continue;
2384 }
2385 out.insert_columns.push(quote!(#column));
2386 out.insert_values.push(quote! {
2387 ::core::convert::Into::<#root::core::SqlValue>::into(
2388 ::core::clone::Clone::clone(&self.#ident)
2389 )
2390 });
2391 if info.auto {
2392 out.has_auto = true;
2393 if out.first_auto_ident.is_none() {
2394 out.first_auto_ident = Some(ident.clone());
2395 }
2396 if !info.default_uuid_v7 && !info.auto_now_add && !info.auto_now {
2409 if out.first_db_auto.is_none() {
2410 out.first_db_auto = auto_inner_type(info.value_ty)
2411 .cloned()
2412 .map(|ty| (ident.clone(), ty));
2413 }
2414 out.returning_cols.push(quote!(#column));
2415 out.auto_field_idents.push((
2416 ident.clone(),
2417 info.column.clone(),
2418 info.fk_pk_kind == DetectedKind::Uuid,
2419 ));
2420 out.auto_assigns.push(quote! {
2421 self.#ident = #root::sql::try_get_returning(_returning_row, #column)?;
2422 });
2423 }
2424 if info.default_uuid_v7 {
2425 out.insert_pushes.push(quote! {
2431 if matches!(&self.#ident, #root::sql::Auto::Unset) {
2432 self.#ident = #root::sql::Auto::Set(
2433 #root::__uuid::Uuid::now_v7(),
2434 );
2435 }
2436 if let #root::sql::Auto::Set(_v) = &self.#ident {
2437 _columns.push(#column);
2438 _values.push(::core::convert::Into::<#root::core::SqlValue>::into(
2439 ::core::clone::Clone::clone(_v)
2440 ));
2441 }
2442 });
2443 } else if info.auto_now_add || info.auto_now {
2444 out.insert_pushes.push(quote! {
2466 if matches!(&self.#ident, #root::sql::Auto::Unset) {
2467 self.#ident = #root::sql::Auto::Set(
2468 #root::__chrono::Utc::now(),
2469 );
2470 }
2471 if let #root::sql::Auto::Set(_v) = &self.#ident {
2472 _columns.push(#column);
2473 _values.push(::core::convert::Into::<#root::core::SqlValue>::into(
2474 ::core::clone::Clone::clone(_v)
2475 ));
2476 }
2477 });
2478 } else {
2479 out.insert_pushes.push(quote! {
2480 if let #root::sql::Auto::Set(_v) = &self.#ident {
2481 _columns.push(#column);
2482 _values.push(::core::convert::Into::<#root::core::SqlValue>::into(
2483 ::core::clone::Clone::clone(_v)
2484 ));
2485 }
2486 });
2487 }
2488 if info.default_uuid_v7 || info.auto_now_add || info.auto_now {
2501 out.rust_filled_idents.push(ident.clone());
2502 }
2503 if info.auto_now {
2504 out.auto_now_idents.push(ident.clone());
2505 }
2506 if info.default_uuid_v7 {
2507 out.uuid_v7_fields
2508 .push((ident.clone(), info.column.clone()));
2509 }
2510 let rust_fill = if info.default_uuid_v7 {
2511 Some(quote!(#root::__uuid::Uuid::now_v7()))
2512 } else if info.auto_now_add || info.auto_now {
2513 Some(quote!(#root::__chrono::Utc::now()))
2514 } else {
2515 None
2516 };
2517 out.bulk_columns_all.push(quote!(#column));
2520 if let Some(fill) = &rust_fill {
2521 let push = quote! {
2522 _row_vals.push(::core::convert::Into::<#root::core::SqlValue>::into(
2523 match &_row.#ident {
2524 #root::sql::Auto::Set(_v) => ::core::clone::Clone::clone(_v),
2525 #root::sql::Auto::Unset => #fill,
2526 }
2527 ));
2528 };
2529 out.bulk_columns_no_auto.push(quote!(#column));
2530 out.bulk_pushes_no_auto.push(push.clone());
2531 out.bulk_pushes_all.push(push);
2532 } else {
2533 out.bulk_pushes_all.push(quote! {
2534 _row_vals.push(::core::convert::Into::<#root::core::SqlValue>::into(
2535 ::core::clone::Clone::clone(&_row.#ident)
2536 ));
2537 });
2538 }
2539 if info.default_uuid_v7 {
2540 out.bulk_fill_in_place.push(quote! {
2541 if matches!(&_row.#ident, #root::sql::Auto::Unset) {
2542 _row.#ident = #root::sql::Auto::Set(#root::__uuid::Uuid::now_v7());
2543 }
2544 });
2545 }
2546 let ident_clone = ident.clone();
2551 if rust_fill.is_none() {
2552 out.bulk_auto_uniformity.push(quote! {
2553 for _r in rows.iter().skip(1) {
2554 if matches!(_r.#ident_clone, #root::sql::Auto::Unset) != _first_unset {
2555 return ::core::result::Result::Err(
2556 #root::sql::ExecError::Sql(
2557 #root::sql::SqlError::BulkAutoMixed
2558 )
2559 );
2560 }
2561 }
2562 });
2563 }
2564 } else {
2565 out.insert_pushes.push(quote! {
2566 _columns.push(#column);
2567 _values.push(::core::convert::Into::<#root::core::SqlValue>::into(
2568 ::core::clone::Clone::clone(&self.#ident)
2569 ));
2570 });
2571 out.bulk_columns_no_auto.push(quote!(#column));
2573 out.bulk_columns_all.push(quote!(#column));
2574 let push_expr = quote! {
2575 _row_vals.push(::core::convert::Into::<#root::core::SqlValue>::into(
2576 ::core::clone::Clone::clone(&_row.#ident)
2577 ));
2578 };
2579 out.bulk_pushes_no_auto.push(push_expr.clone());
2580 out.bulk_pushes_all.push(push_expr);
2581 }
2582 if info.primary_key {
2583 if out.primary_key.is_some() {
2584 return Err(syn::Error::new_spanned(
2585 field,
2586 "only one field may be marked `#[rustango(primary_key)]`",
2587 ));
2588 }
2589 out.primary_key = Some((ident.clone(), info.column.clone()));
2590 if info.auto {
2591 out.pk_is_auto = true;
2592 }
2593 } else if info.auto_now_add {
2594 } else if info.auto_now {
2596 out.update_assignments.push(quote! {
2599 #root::core::Assignment::new(
2600 #column,
2601 {
2602 let _now = #root::__chrono::Utc::now();
2603 self.#ident = #root::sql::Auto::Set(_now);
2604 ::core::convert::Into::<#root::core::SqlValue>::into(_now)
2605 },
2606 )
2607 });
2608 out.upsert_update_columns.push(quote!(#column));
2609 } else {
2610 out.update_assignments.push(quote! {
2611 #root::core::Assignment::new(
2612 #column,
2613 ::core::convert::Into::<#root::core::SqlValue>::into(
2614 ::core::clone::Clone::clone(&self.#ident)
2615 ),
2616 )
2617 });
2618 out.upsert_update_columns.push(quote!(#column));
2619 }
2620 out.column_entries.push(ColumnEntry {
2621 ident: ident.clone(),
2622 value_ty: info.value_ty.clone(),
2623 name: ident_name(ident),
2624 column: info.column.clone(),
2625 field_type_tokens: info.field_type_tokens,
2626 uuid_column: info.uuid_column,
2627 });
2628 }
2629 Ok(out)
2630}
2631
2632fn capped_index_name(name: String) -> String {
2635 const MAX: usize = 63;
2636 if name.len() <= MAX {
2637 return name;
2638 }
2639 let hash = name.bytes().fold(0x811c_9dc5_u32, |h, b| {
2641 (h ^ u32::from(b)).wrapping_mul(0x0100_0193)
2642 });
2643 let mut cut = MAX - 9;
2644 while !name.is_char_boundary(cut) {
2645 cut -= 1;
2646 }
2647 format!("{}_{hash:08x}", &name[..cut])
2648}
2649
2650fn model_impl_tokens(
2651 struct_name: &syn::Ident,
2652 model_name: &str,
2653 table: &str,
2654 display: Option<&str>,
2655 app_label: Option<&str>,
2656 admin: Option<&AdminAttrs>,
2657 default_order: &[(String, bool, proc_macro2::Span)],
2658 field_schemas: &[TokenStream2],
2659 soft_delete_column: Option<&str>,
2660 permissions: bool,
2661 audit_track: Option<&[String]>,
2662 m2m_relations: &[M2MAttr],
2663 indexes: &[IndexAttr],
2664 checks: &[CheckAttr],
2665 excludes: &[ExcludeAttr],
2666 composite_fks: &[CompositeFkAttr],
2667 generic_fks: &[GenericFkAttr],
2668 scope: Option<&str>,
2669 is_view: bool,
2670 verbose_name: Option<&str>,
2671 verbose_name_plural: Option<&str>,
2672 managed: bool,
2673 base_manager_name: Option<&str>,
2674 order_with_respect_to: Option<&str>,
2675 proxy: bool,
2676 required_db_features: &[String],
2677 required_db_vendor: Option<&str>,
2678 default_related_name: Option<&str>,
2679 db_table_comment: Option<&str>,
2680 get_latest_by: Option<(&str, bool)>,
2681 extra_permissions: &[(String, String)],
2682 default_permissions: &[String],
2683 global_scopes: &[GlobalScopeAttr],
2684 reverse_has_relations: &[ReverseHasAttr],
2685 generic_has_relations: &[GenericHasAttr],
2686) -> TokenStream2 {
2687 let root = rustango_root();
2688 let display_tokens = if let Some(name) = display {
2689 quote!(::core::option::Option::Some(#name))
2690 } else {
2691 quote!(::core::option::Option::None)
2692 };
2693 let app_label_tokens = if let Some(name) = app_label {
2694 quote!(::core::option::Option::Some(#name))
2695 } else {
2696 quote!(::core::option::Option::None)
2697 };
2698 let soft_delete_tokens = if let Some(col) = soft_delete_column {
2699 quote!(::core::option::Option::Some(#col))
2700 } else {
2701 quote!(::core::option::Option::None)
2702 };
2703 let audit_track_tokens = match audit_track {
2704 None => quote!(::core::option::Option::None),
2705 Some(names) => {
2706 let lits = names.iter().map(|n| n.as_str());
2707 quote!(::core::option::Option::Some(&[ #(#lits),* ]))
2708 }
2709 };
2710 let admin_tokens = admin_config_tokens(admin);
2711 let scope_tokens = match scope.map(|s| s.to_ascii_lowercase()).as_deref() {
2715 Some("registry") => quote!(#root::core::ModelScope::Registry),
2716 _ => quote!(#root::core::ModelScope::Tenant),
2717 };
2718 let verbose_name_tokens = optional_str(verbose_name);
2719 let verbose_name_plural_tokens = optional_str(verbose_name_plural);
2720 let base_manager_name_tokens = optional_str(base_manager_name);
2721 let order_with_respect_to_tokens = optional_str(order_with_respect_to);
2722 let required_db_features_lits: Vec<&str> =
2723 required_db_features.iter().map(String::as_str).collect();
2724 let required_db_vendor_tokens = optional_str(required_db_vendor);
2725 let default_related_name_tokens = optional_str(default_related_name);
2726 let db_table_comment_tokens = optional_str(db_table_comment);
2727 let get_latest_by_tokens = match get_latest_by {
2728 Some((col, desc)) => {
2729 quote!(::core::option::Option::Some((#col, #desc)))
2730 }
2731 None => quote!(::core::option::Option::None),
2732 };
2733 let extra_permission_tokens: Vec<_> = extra_permissions
2734 .iter()
2735 .map(|(c, l)| quote!((#c, #l)))
2736 .collect();
2737 let default_permission_tokens: Vec<_> = default_permissions
2738 .iter()
2739 .map(|action| quote!(#action))
2740 .collect();
2741 let indexes_tokens = indexes.iter().map(|idx| {
2742 let derived_name = idx
2747 .name
2748 .clone()
2749 .unwrap_or_else(|| capped_index_name(format!("{table}_{}_idx", idx.columns.join("_"))));
2750 let name = derived_name.as_str();
2751 let cols: Vec<&str> = idx.columns.iter().map(String::as_str).collect();
2752 let unique = idx.unique;
2753 let method_variant = match idx.method.as_str() {
2757 "gin" => quote!(#root::core::IndexMethod::Gin),
2758 "gist" => quote!(#root::core::IndexMethod::Gist),
2759 "brin" => quote!(#root::core::IndexMethod::Brin),
2760 "spgist" => quote!(#root::core::IndexMethod::SpGist),
2761 "hash" => quote!(#root::core::IndexMethod::Hash),
2762 "bloom" => quote!(#root::core::IndexMethod::Bloom),
2763 _ => quote!(#root::core::IndexMethod::BTree),
2764 };
2765 let where_clause = match &idx.where_clause {
2766 Some(s) => quote!(::core::option::Option::Some(#s)),
2767 None => quote!(::core::option::Option::None),
2768 };
2769 let include_lits: Vec<&str> = idx.include.iter().map(String::as_str).collect();
2770 quote! {{
2771 let mut i = #root::core::IndexSchema::new(#name, &[ #(#cols),* ]);
2772 i.unique = #unique;
2773 i.method = #method_variant;
2774 i.where_clause = #where_clause;
2775 i.include = &[ #(#include_lits),* ];
2776 i
2777 }}
2778 });
2779 let checks_tokens = checks.iter().map(|c| {
2780 let name = c.name.as_str();
2781 let expr = c.expr.as_str();
2782 quote!(#root::core::CheckConstraint::new(#name, #expr))
2783 });
2784 let excludes_tokens = excludes.iter().map(|e| {
2785 let name = e.name.as_str();
2786 let using = e.using.as_str();
2787 let element_tokens = e.elements.iter().map(|(col, op)| {
2788 let col_s = col.as_str();
2789 let op_s = op.as_str();
2790 quote!((#col_s, #op_s))
2791 });
2792 let where_tokens = match e.where_clause.as_deref() {
2793 Some(w) => quote!(::core::option::Option::Some(#w)),
2794 None => quote!(::core::option::Option::None),
2795 };
2796 quote! {{
2797 let mut e = #root::core::ExclusionConstraint::new(
2798 #name,
2799 #using,
2800 &[ #(#element_tokens),* ],
2801 );
2802 e.where_clause = #where_tokens;
2803 e
2804 }}
2805 });
2806 let composite_fk_tokens = composite_fks.iter().map(|rel| {
2807 let name = rel.name.as_str();
2808 let to = rel.to.as_str();
2809 let from_cols: Vec<&str> = rel.from.iter().map(String::as_str).collect();
2810 let on_cols: Vec<&str> = rel.on.iter().map(String::as_str).collect();
2811 quote! {
2812 #root::core::CompositeFkRelation::new(
2813 #name,
2814 #to,
2815 &[ #(#from_cols),* ],
2816 &[ #(#on_cols),* ],
2817 )
2818 }
2819 });
2820 let generic_fk_tokens = generic_fks.iter().map(|rel| {
2821 let name = rel.name.as_str();
2822 let ct_col = rel.ct_column.as_str();
2823 let pk_col = rel.pk_column.as_str();
2824 quote!(#root::core::GenericRelation::new(#name, #ct_col, #pk_col))
2825 });
2826 let default_order_tokens = default_order.iter().map(|(col, desc, _)| {
2829 let col_lit = col.as_str();
2830 quote! { (#col_lit, #desc) }
2831 });
2832
2833 let global_scope_tokens = global_scopes.iter().map(|s| {
2838 let name = s.name.as_str();
2839 let mut apply = s.apply.clone();
2841 if let Some(first) = apply.segments.first_mut() {
2842 if first.ident == "Self" {
2843 first.ident = struct_name.clone();
2844 }
2845 }
2846 quote!(#root::core::GlobalScope::new(#name, #apply))
2847 });
2848
2849 let m2m_tokens = m2m_relations.iter().map(|rel| {
2850 let name = rel.name.as_str();
2851 let to = rel.to.as_str();
2852 let through = rel.through.as_str();
2853 let src = rel.src.as_str();
2854 let dst = rel.dst.as_str();
2855 let auto_create = rel.auto_create;
2856 quote! {{
2857 let mut m = #root::core::M2MRelation::new(#name, #to, #through, #src, #dst);
2858 m.auto_create = #auto_create;
2859 m
2860 }}
2861 });
2862 let reverse_relations_override = if reverse_has_relations.is_empty() {
2868 quote!()
2869 } else {
2870 let entries = reverse_has_relations.iter().map(|rel| {
2871 let name = rel.name.as_str();
2872 let child = &rel.child;
2873 let child_fk_column = rel.child_fk_column.as_str();
2874 let self_pk_column = rel.self_pk_column();
2875 quote! {
2876 #root::core::ReverseRelation::new(
2877 #name,
2878 <#child as #root::core::Model>::SCHEMA,
2879 #child_fk_column,
2880 #self_pk_column,
2881 )
2882 }
2883 });
2884 quote! {
2885 fn reverse_relations() -> &'static [#root::core::ReverseRelation] {
2886 const RELS: &[#root::core::ReverseRelation] = &[ #(#entries),* ];
2887 RELS
2888 }
2889 }
2890 };
2891 let generic_reverse_relations_override = if generic_has_relations.is_empty() {
2894 quote!()
2895 } else {
2896 let entries = generic_has_relations.iter().map(|rel| {
2897 let name = rel.name.as_str();
2898 let child = &rel.child;
2899 let ct_column = rel.ct_column.as_str();
2900 let pk_column = rel.pk_column.as_str();
2901 let self_pk_column = rel.self_pk_column();
2902 quote! {
2903 #root::core::GenericReverseRelation::new(
2904 #name,
2905 <#child as #root::core::Model>::SCHEMA,
2906 #ct_column,
2907 #pk_column,
2908 #self_pk_column,
2909 )
2910 }
2911 });
2912 quote! {
2913 fn generic_reverse_relations() -> &'static [#root::core::GenericReverseRelation] {
2914 const RELS: &[#root::core::GenericReverseRelation] = &[ #(#entries),* ];
2915 RELS
2916 }
2917 }
2918 };
2919 let fields_slice = const_slice(
2920 "e!(#root::core::FieldSchema),
2921 field_schemas.iter().cloned(),
2922 );
2923 let m2m_slice = const_slice("e!(#root::core::M2MRelation), m2m_tokens);
2924 let indexes_slice = const_slice("e!(#root::core::IndexSchema), indexes_tokens);
2925 let checks_slice = const_slice("e!(#root::core::CheckConstraint), checks_tokens);
2926 let excludes_slice = const_slice("e!(#root::core::ExclusionConstraint), excludes_tokens);
2927 let composite_fk_slice = const_slice(
2928 "e!(#root::core::CompositeFkRelation),
2929 composite_fk_tokens,
2930 );
2931 let generic_fk_slice = const_slice("e!(#root::core::GenericRelation), generic_fk_tokens);
2932 let audited_update_override = if audit_track.is_some() {
2933 quote! {
2934 fn __rustango_audited_update() -> ::core::option::Option<#root::audit::AuditedUpdate> {
2935 ::core::option::Option::Some(Self::__rustango_update_audited)
2936 }
2937 fn __rustango_audited_delete() -> ::core::option::Option<#root::audit::AuditedDelete> {
2938 ::core::option::Option::Some(Self::__rustango_delete_audited)
2939 }
2940 fn __rustango_audited_create() -> ::core::option::Option<#root::audit::AuditedCreate> {
2941 ::core::option::Option::Some(Self::__rustango_create_audited)
2942 }
2943 fn __rustango_audited_update_record(
2944 ) -> ::core::option::Option<#root::audit::AuditedUpdateRecord> {
2945 ::core::option::Option::Some(Self::__rustango_update_record_audited)
2946 }
2947 fn __rustango_audited_cascade() -> ::core::option::Option<#root::audit::AuditedCascade> {
2948 ::core::option::Option::Some(Self::__rustango_cascade_audited)
2949 }
2950 }
2951 } else {
2952 quote!()
2953 };
2954 let global_scope_slice = const_slice("e!(#root::core::GlobalScope), global_scope_tokens);
2955 quote! {
2956 impl #root::core::Model for #struct_name {
2957 const SCHEMA: &'static #root::core::ModelSchema = &{
2958 #root::core::ModelSchema::from_parts(#root::core::ModelSchemaParts {
2959 name: #model_name,
2960 table: #table,
2961 fields: #fields_slice,
2962 display: #display_tokens,
2963 app_label: #app_label_tokens,
2964 admin: #admin_tokens,
2965 soft_delete_column: #soft_delete_tokens,
2966 permissions: #permissions,
2967 audit_track: #audit_track_tokens,
2968 m2m: #m2m_slice,
2969 indexes: #indexes_slice,
2970 check_constraints: #checks_slice,
2971 exclusion_constraints: #excludes_slice,
2972 composite_relations: #composite_fk_slice,
2973 generic_relations: #generic_fk_slice,
2974 scope: #scope_tokens,
2975 default_order: &[ #(#default_order_tokens),* ],
2976 is_view: #is_view,
2977 verbose_name: #verbose_name_tokens,
2978 verbose_name_plural: #verbose_name_plural_tokens,
2979 managed: #managed,
2980 base_manager_name: #base_manager_name_tokens,
2981 order_with_respect_to: #order_with_respect_to_tokens,
2982 proxy: #proxy,
2983 required_db_features: &[ #(#required_db_features_lits),* ],
2984 required_db_vendor: #required_db_vendor_tokens,
2985 default_related_name: #default_related_name_tokens,
2986 db_table_comment: #db_table_comment_tokens,
2987 get_latest_by: #get_latest_by_tokens,
2988 extra_permissions: &[ #(#extra_permission_tokens),* ],
2989 default_permissions: &[ #(#default_permission_tokens),* ],
2990 global_scopes: #global_scope_slice,
2991 })
2992 };
2993
2994 #reverse_relations_override
2995 #generic_reverse_relations_override
2996 #audited_update_override
2997 }
2998 }
2999}
3000
3001fn admin_config_tokens(admin: Option<&AdminAttrs>) -> TokenStream2 {
3005 let root = rustango_root();
3006 let Some(admin) = admin else {
3007 return quote!(::core::option::Option::None);
3008 };
3009
3010 let list_display = admin
3011 .list_display
3012 .as_ref()
3013 .map(|(v, _)| v.as_slice())
3014 .unwrap_or(&[]);
3015 let list_display_lits = list_display.iter().map(|s| s.as_str());
3016
3017 let search_fields = admin
3018 .search_fields
3019 .as_ref()
3020 .map(|(v, _)| v.as_slice())
3021 .unwrap_or(&[]);
3022 let search_fields_lits = search_fields.iter().map(|s| s.as_str());
3023
3024 let readonly_fields = admin
3025 .readonly_fields
3026 .as_ref()
3027 .map(|(v, _)| v.as_slice())
3028 .unwrap_or(&[]);
3029 let readonly_fields_lits = readonly_fields.iter().map(|s| s.as_str());
3030
3031 let list_filter = admin
3032 .list_filter
3033 .as_ref()
3034 .map(|(v, _)| v.as_slice())
3035 .unwrap_or(&[]);
3036 let list_filter_lits = list_filter.iter().map(|s| s.as_str());
3037
3038 let actions = admin
3039 .actions
3040 .as_ref()
3041 .map(|(v, _)| v.as_slice())
3042 .unwrap_or(&[]);
3043 let actions_lits = actions.iter().map(|s| s.as_str());
3044
3045 let fieldsets = admin
3046 .fieldsets
3047 .as_ref()
3048 .map(|(v, _)| v.as_slice())
3049 .unwrap_or(&[]);
3050 let fieldset_tokens = const_slice(
3051 "e!(#root::core::Fieldset),
3052 fieldsets.iter().map(|(title, fields)| {
3053 let title = title.as_str();
3054 let field_lits = fields.iter().map(String::as_str);
3055 quote!(#root::core::Fieldset::new(#title, &[ #( #field_lits ),* ]))
3056 }),
3057 );
3058
3059 let list_display_links = admin
3060 .list_display_links
3061 .as_ref()
3062 .map(|(v, _)| v.as_slice())
3063 .unwrap_or(&[]);
3064 let list_display_links_lits = list_display_links.iter().map(|s| s.as_str());
3065
3066 let search_help_text = admin.search_help_text.as_deref().unwrap_or("");
3067 let actions_on_top = admin.actions_on_top.unwrap_or(true);
3068 let actions_on_bottom = admin.actions_on_bottom.unwrap_or(false);
3069 let date_hierarchy = admin.date_hierarchy.as_deref().unwrap_or("");
3070
3071 let prepopulated = admin
3072 .prepopulated_fields
3073 .as_ref()
3074 .map(|(v, _)| v.as_slice())
3075 .unwrap_or(&[]);
3076 let prepopulated_tokens = const_slice(
3077 "e!(#root::core::PrepopulatedField),
3078 prepopulated.iter().map(|(target, sources)| {
3079 let target = target.as_str();
3080 let source_lits = sources.iter().map(String::as_str);
3081 quote!(#root::core::PrepopulatedField::new(#target, &[ #( #source_lits ),* ]))
3082 }),
3083 );
3084
3085 let raw_id_fields = admin
3086 .raw_id_fields
3087 .as_ref()
3088 .map(|(v, _)| v.as_slice())
3089 .unwrap_or(&[]);
3090 let raw_id_fields_lits = raw_id_fields.iter().map(|s| s.as_str());
3091
3092 let autocomplete_fields = admin
3093 .autocomplete_fields
3094 .as_ref()
3095 .map(|(v, _)| v.as_slice())
3096 .unwrap_or(&[]);
3097 let autocomplete_fields_lits = autocomplete_fields.iter().map(|s| s.as_str());
3098
3099 let list_select_related_tokens = match admin.list_select_related.as_deref() {
3101 None | Some("all") => quote!(#root::core::ListSelectRelated::All),
3102 Some("none") => quote!(#root::core::ListSelectRelated::None),
3103 Some(raw) => {
3104 let names: Vec<&str> = raw
3105 .split(',')
3106 .map(str::trim)
3107 .filter(|s| !s.is_empty())
3108 .collect();
3109 quote!(#root::core::ListSelectRelated::Only(&[ #( #names ),* ]))
3110 }
3111 };
3112
3113 let formfield_pairs: Vec<(&str, &str)> = admin
3116 .formfield_overrides
3117 .as_ref()
3118 .map(|(v, _)| v.iter().map(|(f, w)| (f.as_str(), w.as_str())).collect())
3119 .unwrap_or_default();
3120 let formfield_tokens = formfield_pairs.iter().map(|(field, widget)| {
3121 let field = *field;
3122 let widget = *widget;
3123 quote!((#field, #widget))
3124 });
3125
3126 let list_per_page = admin.list_per_page.unwrap_or(0);
3127
3128 let ordering_pairs = admin
3129 .ordering
3130 .as_ref()
3131 .map(|(v, _)| v.as_slice())
3132 .unwrap_or(&[]);
3133 let ordering_tokens = ordering_pairs.iter().map(|(name, desc)| {
3134 let name = name.as_str();
3135 let desc = *desc;
3136 quote!((#name, #desc))
3137 });
3138
3139 quote! {
3140 ::core::option::Option::Some({
3141 const ADMIN: &#root::core::AdminConfig = &{
3142 #root::core::AdminConfig::from_parts(#root::core::AdminConfigParts {
3143 list_display: &[ #( #list_display_lits ),* ],
3144 search_fields: &[ #( #search_fields_lits ),* ],
3145 list_per_page: #list_per_page,
3146 ordering: &[ #( #ordering_tokens ),* ],
3147 readonly_fields: &[ #( #readonly_fields_lits ),* ],
3148 list_filter: &[ #( #list_filter_lits ),* ],
3149 actions: &[ #( #actions_lits ),* ],
3150 fieldsets: #fieldset_tokens,
3151 list_display_links: &[ #( #list_display_links_lits ),* ],
3152 search_help_text: #search_help_text,
3153 actions_on_top: #actions_on_top,
3154 actions_on_bottom: #actions_on_bottom,
3155 date_hierarchy: #date_hierarchy,
3156 prepopulated_fields: #prepopulated_tokens,
3157 raw_id_fields: &[ #( #raw_id_fields_lits ),* ],
3158 autocomplete_fields: &[ #( #autocomplete_fields_lits ),* ],
3159 list_select_related: #list_select_related_tokens,
3160 formfield_overrides: &[ #( #formfield_tokens ),* ],
3161 })
3162 };
3163 ADMIN
3164 })
3165 }
3166}
3167
3168fn inherent_impl_tokens(
3169 struct_name: &syn::Ident,
3170 fields: &CollectedFields,
3171 primary_key: Option<&(syn::Ident, String)>,
3172 column_consts: &TokenStream2,
3173 audited_fields: Option<&[&ColumnEntry]>,
3174 upsert_unique: Option<&[String]>,
3175 manager_fns: &[syn::Ident],
3176) -> TokenStream2 {
3177 let root = rustango_root();
3178
3179 let pool_insert_method = if audited_fields.is_some() && !fields.has_auto {
3197 quote!()
3206 } else if audited_fields.is_some() && fields.has_auto {
3207 quote!()
3210 } else if fields.has_auto {
3211 let pushes = &fields.insert_pushes;
3212 let filled = &fields.rust_filled_idents;
3214 let was_unset: Vec<syn::Ident> = filled
3215 .iter()
3216 .map(|i| quote::format_ident!("__{}_was_unset", ident_name(i)))
3217 .collect();
3218 let insert_or_ignore_call = quote! {
3219 #( let #was_unset = matches!(&self.#filled, #root::sql::Auto::Unset); )*
3220 let mut _columns: ::std::vec::Vec<&'static str> =
3221 ::std::vec::Vec::new();
3222 let mut _values: ::std::vec::Vec<#root::core::SqlValue> =
3223 ::std::vec::Vec::new();
3224 #( #pushes )*
3225 let _query = #root::core::InsertQuery::new(
3226 <Self as #root::core::Model>::SCHEMA,
3227 _columns,
3228 _values,
3229 );
3230 let _result = #root::sql::insert_or_ignore(pool, &_query).await;
3231 if !matches!(_result, ::core::result::Result::Ok(true)) {
3232 #( if #was_unset { self.#filled = #root::sql::Auto::Unset; } )*
3233 }
3234 _result
3235 };
3236 if fields.returning_cols.is_empty() {
3243 quote! {
3244
3245
3246 pub async fn insert_or_ignore(
3254 &mut self,
3255 pool: &#root::sql::Pool,
3256 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
3257 #insert_or_ignore_call
3258 }
3259 }
3260 } else {
3261 quote! {
3262
3263
3264 pub async fn insert_or_ignore(
3281 &mut self,
3282 pool: &#root::sql::Pool,
3283 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
3284 #insert_or_ignore_call
3285 }
3286 }
3287 }
3288 } else {
3289 let insert_columns = &fields.insert_columns;
3290 let insert_values = &fields.insert_values;
3291 quote! {
3292
3293
3294 pub async fn insert_or_ignore(
3314 &self,
3315 pool: &#root::sql::Pool,
3316 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
3317 let _query = #root::core::InsertQuery::new(
3318 <Self as #root::core::Model>::SCHEMA,
3319 ::std::vec![ #( #insert_columns ),* ],
3320 ::std::vec![ #( #insert_values ),* ],
3321 );
3322 #root::sql::insert_or_ignore(pool, &_query).await
3323 }
3324 }
3325 };
3326
3327 let audit_pair_tokens: Vec<TokenStream2> = audited_fields
3330 .map(|tracked| {
3331 tracked
3332 .iter()
3333 .map(|c| {
3334 let column_lit = c.column.as_str();
3335 let ident = &c.ident;
3336 quote! {
3337 (
3338 #column_lit,
3339 #root::core::__json_value(&self.#ident),
3340 )
3341 }
3342 })
3343 .collect()
3344 })
3345 .unwrap_or_default();
3346 let audit_pk_to_string = if let Some((pk_ident, _)) = primary_key {
3347 if fields.pk_is_auto {
3348 quote!(self.#pk_ident.get().map(|v| ::std::format!("{}", v)).unwrap_or_default())
3349 } else {
3350 quote!(::std::format!("{}", &self.#pk_ident))
3351 }
3352 } else {
3353 quote!(::std::string::String::new())
3354 };
3355 let delete_audited_body = if primary_key.is_some() {
3358 quote! {
3359 ::std::boxed::Box::pin(#root::audit::delete_many_with_audit::<Self>(
3360 pool,
3361 query,
3362 |_r: &Self| _r.__rustango_audit_entry(#root::audit::AuditOp::Delete),
3363 ))
3364 }
3365 } else {
3366 quote! {
3367 let _ = (pool, query);
3368 ::std::boxed::Box::pin(async {
3369 ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
3370 table: <Self as #root::core::Model>::SCHEMA.table,
3371 })
3372 })
3373 }
3374 };
3375 let cascade_audited_body = if primary_key.is_some() {
3376 quote! {
3377 ::std::boxed::Box::pin(#root::audit::cascade_rows::<Self>(
3378 tx,
3379 <Self as #root::core::Model>::SCHEMA,
3380 rows,
3381 |_r: &Self| _r.__rustango_audit_entry(#root::audit::AuditOp::Delete),
3382 ))
3383 }
3384 } else {
3385 quote! {
3386 let _ = (tx, rows);
3387 ::std::boxed::Box::pin(async {
3388 ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
3389 table: <Self as #root::core::Model>::SCHEMA.table,
3390 })
3391 })
3392 }
3393 };
3394 let update_audited_body = if primary_key.is_some() {
3395 quote! {
3396 ::std::boxed::Box::pin(#root::audit::update_many_with_audit::<Self>(
3397 pool,
3398 query,
3399 move |_r: &Self| _r.__rustango_audit_entry(op),
3400 ))
3401 }
3402 } else {
3403 quote! {
3404 let _ = (pool, query, op);
3405 ::std::boxed::Box::pin(async {
3406 ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
3407 table: <Self as #root::core::Model>::SCHEMA.table,
3408 })
3409 })
3410 }
3411 };
3412 let audit_entry_method = if audited_fields.is_some() {
3413 let pairs = audit_pair_tokens.iter();
3414 let pk_str = audit_pk_to_string.clone();
3415 quote! {
3416 #[doc(hidden)]
3418 pub fn __rustango_audit_pairs(
3419 &self,
3420 ) -> ::std::vec::Vec<(&'static str, #root::__serde_json::Value)> {
3421 ::std::vec![ #( #pairs ),* ]
3422 }
3423
3424 #[doc(hidden)]
3426 pub fn __rustango_audit_pk(&self) -> ::std::string::String {
3427 #pk_str
3428 }
3429
3430 #[doc(hidden)]
3432 pub fn __rustango_audit_entry(
3433 &self,
3434 operation: #root::audit::AuditOp,
3435 ) -> #root::audit::PendingEntry {
3436 #root::audit::PendingEntry {
3437 entity_table: <Self as #root::core::Model>::SCHEMA.table,
3438 entity_pk: self.__rustango_audit_pk(),
3439 operation,
3440 source: #root::audit::current_source(),
3441 changes: #root::audit::snapshot_changes(&self.__rustango_audit_pairs()),
3442 }
3443 }
3444
3445 #[doc(hidden)]
3447 pub fn __rustango_update_audited<'a>(
3448 pool: &'a #root::sql::Pool,
3449 query: &'a #root::core::UpdateQuery,
3450 op: #root::audit::AuditOp,
3451 ) -> ::std::pin::Pin<::std::boxed::Box<
3452 dyn ::core::future::Future<
3453 Output = ::core::result::Result<u64, #root::sql::ExecError>,
3454 > + ::core::marker::Send + 'a,
3455 >> {
3456 #update_audited_body
3457 }
3458
3459 #[doc(hidden)]
3461 pub fn __rustango_create_audited<'a, 't>(
3462 tx: &'a mut #root::sql::PoolTx<'t>,
3463 pk: #root::core::SqlValue,
3464 ) -> ::std::pin::Pin<::std::boxed::Box<
3465 dyn ::core::future::Future<
3466 Output = ::core::result::Result<(), #root::sql::ExecError>,
3467 > + ::core::marker::Send + 'a,
3468 >> {
3469 ::std::boxed::Box::pin(#root::audit::record_create::<Self>(
3470 tx,
3471 <Self as #root::core::Model>::SCHEMA,
3472 pk,
3473 |_r: &Self| _r.__rustango_audit_entry(#root::audit::AuditOp::Create),
3474 ))
3475 }
3476
3477 #[doc(hidden)]
3479 pub fn __rustango_update_record_audited<'a, 't>(
3480 tx: &'a mut #root::sql::PoolTx<'t>,
3481 pk: #root::core::SqlValue,
3482 ) -> ::std::pin::Pin<::std::boxed::Box<
3483 dyn ::core::future::Future<
3484 Output = ::core::result::Result<(), #root::sql::ExecError>,
3485 > + ::core::marker::Send + 'a,
3486 >> {
3487 ::std::boxed::Box::pin(#root::audit::record_update::<Self>(
3488 tx,
3489 <Self as #root::core::Model>::SCHEMA,
3490 pk,
3491 |_r: &Self| _r.__rustango_audit_entry(#root::audit::AuditOp::Update),
3492 ))
3493 }
3494
3495 #[doc(hidden)]
3497 pub fn __rustango_cascade_audited<'a, 't>(
3498 tx: &'a mut #root::sql::PoolTx<'t>,
3499 rows: #root::core::WhereExpr,
3500 ) -> ::std::pin::Pin<::std::boxed::Box<
3501 dyn ::core::future::Future<
3502 Output = ::core::result::Result<
3503 ::std::vec::Vec<(#root::core::SqlValue, #root::audit::PendingEntry)>,
3504 #root::sql::ExecError,
3505 >,
3506 > + ::core::marker::Send + 'a,
3507 >> {
3508 #cascade_audited_body
3509 }
3510
3511 #[doc(hidden)]
3513 pub fn __rustango_delete_audited<'a>(
3514 pool: &'a #root::sql::Pool,
3515 query: &'a #root::core::DeleteQuery,
3516 ) -> ::std::pin::Pin<::std::boxed::Box<
3517 dyn ::core::future::Future<
3518 Output = ::core::result::Result<u64, #root::sql::ExecError>,
3519 > + ::core::marker::Send + 'a,
3520 >> {
3521 #delete_audited_body
3522 }
3523 }
3524 } else {
3525 quote!()
3526 };
3527 let make_entry = |op_path: TokenStream2, written: Option<TokenStream2>| -> TokenStream2 {
3530 if let (Some(v), Some(col)) = (written, fields.soft_delete_column.as_deref()) {
3531 quote! {
3532 let mut _audit_entry = self.__rustango_audit_entry(#op_path);
3533 _audit_entry.set_tracked(#col, #v);
3534 }
3535 } else {
3536 quote!(let _audit_entry = self.__rustango_audit_entry(#op_path);)
3537 }
3538 };
3539 let soft_deleted_json = quote!(#root::__serde_json::to_value(&_now)
3540 .unwrap_or(#root::__serde_json::Value::Null));
3541 let restored_json = quote!(#root::__serde_json::Value::Null);
3542 let make_pool_update = |op_path: TokenStream2, written: TokenStream2| -> TokenStream2 {
3545 if audited_fields.is_some() {
3546 let entry = make_entry(op_path, Some(written));
3547 quote! {
3548 #entry
3549 #root::audit::save_one_with_audit(pool, &_query, &_audit_entry).await
3550 }
3551 } else {
3552 quote!(#root::sql::update_pool(pool, &_query).await)
3553 }
3554 };
3555 let pool_softdelete_run =
3556 make_pool_update(quote!(#root::audit::AuditOp::SoftDelete), soft_deleted_json);
3557 let pool_restore_run = make_pool_update(quote!(#root::audit::AuditOp::Restore), restored_json);
3558
3559 let save_ret = quote!(::core::result::Result<u64, #root::sql::ExecError>);
3563 let save_keep = quote!(matches!(_saved, ::core::result::Result::Ok(n) if n > 0));
3564 let save_partial_methods = |pk_ident: &syn::Ident,
3565 pk_column_lit: &str,
3566 run: TokenStream2|
3567 -> TokenStream2 {
3568 let assignments = &fields.update_assignments;
3569 let assignment_cols = &fields.upsert_update_columns;
3571 let write = restore_auto_now_unless(
3572 &fields.auto_now_idents,
3573 &save_ret,
3574 &save_keep,
3575 "e! {
3576 let mut _filtered: ::std::vec::Vec<#root::core::Assignment> =
3579 ::std::vec::Vec::with_capacity(_wanted_cols.len());
3580 #(
3581 if _wanted_cols.contains(#assignment_cols) {
3582 _filtered.push(#assignments);
3583 }
3584 )*
3585 if _filtered.is_empty() {
3586 #root::__tracing::warn!(
3591 target: "rustango::save_partial",
3592 model = _schema.name,
3593 "save_partial: every named field maps to a non-assignable column — no-op"
3594 );
3595 return ::core::result::Result::Ok(0);
3596 }
3597 let _query = #root::core::UpdateQuery::new(
3598 _schema,
3599 _filtered,
3600 #root::core::WhereExpr::Predicate(#root::core::Filter::new(
3601 #pk_column_lit,
3602 #root::core::Op::Eq,
3603 ::core::convert::Into::<#root::core::SqlValue>::into(
3604 ::core::clone::Clone::clone(&self.#pk_ident)
3605 ),
3606 )),
3607 );
3608 #run
3609 },
3610 );
3611 quote! {
3612 pub async fn save_partial(
3640 &mut self,
3641 fields: &[&str],
3642 pool: &#root::sql::Pool,
3643 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
3644 if fields.is_empty() {
3645 #root::__tracing::warn!(
3646 target: "rustango::save_partial",
3647 model = <Self as #root::core::Model>::SCHEMA.name,
3648 "save_partial called with empty field list — no-op"
3649 );
3650 return ::core::result::Result::Ok(0);
3651 }
3652 let _schema = <Self as #root::core::Model>::SCHEMA;
3653 let mut _wanted_cols: ::std::collections::HashSet<&'static str> =
3655 ::std::collections::HashSet::with_capacity(fields.len());
3656 for f in fields {
3657 match _schema.field(f) {
3658 ::core::option::Option::Some(fs) => {
3659 _wanted_cols.insert(fs.column);
3660 }
3661 ::core::option::Option::None => {
3662 return ::core::result::Result::Err(
3663 #root::sql::ExecError::Query(
3664 #root::core::QueryError::UnknownField {
3665 model: _schema.name,
3666 field: (*f).to_owned(),
3667 }
3668 )
3669 );
3670 }
3671 }
3672 }
3673 #write
3674 }
3675
3676 pub async fn save_partial_typed<
3696 L: #root::core::TypedFieldList<Self>,
3697 >(
3698 &mut self,
3699 fields: L,
3700 pool: &#root::sql::Pool,
3701 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
3702 let _names = fields.rust_field_names();
3703 let _refs: ::std::vec::Vec<&str> =
3704 _names.iter().copied().collect();
3705 self.save_partial(&_refs, pool).await
3706 }
3707 }
3708 };
3709 let pool_save_method = if let Some((pk_ident, pk_col)) = primary_key {
3710 let pk_column_lit = pk_col.as_str();
3711 if audited_fields.is_some() {
3712 quote!()
3713 } else {
3714 let partial = save_partial_methods(
3715 pk_ident,
3716 pk_column_lit,
3717 quote!(#root::sql::update_pool(pool, &_query).await),
3718 );
3719 quote! {
3720 pub async fn save(
3727 &mut self,
3728 pool: &#root::sql::Pool,
3729 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
3730 self.__rustango_save_exec(pool).await
3731 }
3732
3733 #partial
3734 }
3735 }
3736 } else {
3737 quote!()
3738 };
3739
3740 let pool_insert_method = if audited_fields.is_some() {
3747 if let Some(_) = primary_key {
3748 let returning_cols: Vec<proc_macro2::TokenStream> = if !fields.returning_cols.is_empty()
3750 {
3751 fields.returning_cols.clone()
3752 } else {
3753 primary_key
3760 .map(|(_, col)| {
3761 let lit = col.as_str();
3762 vec![quote!(#lit)]
3763 })
3764 .unwrap_or_default()
3765 };
3766 let assigned: Vec<syn::Ident> = fields
3768 .auto_field_idents
3769 .iter()
3770 .map(|(ident, _, _)| ident.clone())
3771 .collect();
3772 let body = restore_auto_now_unless(
3773 &assigned,
3774 "e!(::core::result::Result<(), #root::sql::ExecError>),
3775 "e!(_saved.is_ok()),
3776 "e! {
3777 let mut _query = self.__rustango_insert_query();
3778 if _query.returning.is_empty() {
3780 _query.returning = ::std::vec![ #( #returning_cols ),* ];
3781 }
3782 #root::audit::insert_one_with_audit(pool, &_query, self, |_m: &Self, _op| {
3783 _m.__rustango_audit_entry(_op)
3784 })
3785 .await
3786 },
3787 );
3788 quote! {
3789 pub async fn insert(
3797 &mut self,
3798 pool: &#root::sql::Pool,
3799 ) -> ::core::result::Result<(), #root::sql::ExecError> {
3800 #body
3801 }
3802 }
3803 } else {
3804 quote!()
3805 }
3806 } else {
3807 pool_insert_method
3809 };
3810
3811 let pool_save_method = if let Some(tracked) = audited_fields {
3832 if let Some((pk_ident, pk_col)) = primary_key {
3833 let pk_column_lit = pk_col.as_str();
3834 let partial = save_partial_methods(
3835 pk_ident,
3836 pk_column_lit,
3837 quote! {
3838 self.__rustango_save_with_diff(
3839 pool,
3840 &_query,
3841 ::core::option::Option::Some(&_wanted_cols),
3842 )
3843 .await
3844 },
3845 );
3846 let mk_before_pairs =
3851 |getter: &dyn Fn(&ColumnEntry) -> TokenStream2| -> Vec<TokenStream2> {
3852 tracked
3853 .iter()
3854 .map(|c| {
3855 let getter = getter(c);
3856 let column_lit = c.column.as_str();
3857 let value_ty = &c.value_ty;
3858 quote! {
3859 (
3860 #column_lit,
3861 match #getter::<#value_ty>(
3862 _audit_before_row, #column_lit,
3863 ) {
3864 ::core::result::Result::Ok(v) => {
3865 #root::core::__json_value(&v)
3866 }
3867 ::core::result::Result::Err(_) => #root::__serde_json::Value::Null,
3868 },
3869 )
3870 }
3871 })
3872 .collect()
3873 };
3874 let before_pairs_pg = mk_before_pairs(&|_| quote!(#root::sql::try_get_returning));
3875 let before_pairs_my = mk_before_pairs(&|c| {
3876 if c.uuid_column {
3877 quote!(#root::sql::try_get_flat_my)
3878 } else {
3879 quote!(#root::sql::try_get_returning_my)
3880 }
3881 });
3882 let before_pairs_sqlite =
3883 mk_before_pairs(&|_| quote!(#root::sql::try_get_returning_sqlite));
3884 let tracked_cols: Vec<&str> = tracked.iter().map(|c| c.column.as_str()).collect();
3885 let pk_value_for_bind = if fields.pk_is_auto {
3886 quote!(self.#pk_ident.get().copied().unwrap_or_default())
3887 } else {
3888 quote!(::core::clone::Clone::clone(&self.#pk_ident))
3889 };
3890 let assignments = &fields.update_assignments;
3891 let unset_dispatch = if fields.has_auto {
3892 quote! {
3893 if matches!(self.#pk_ident, #root::sql::Auto::Unset) {
3894 return self.insert(pool).await.map(|()| 1u64);
3895 }
3896 }
3897 } else {
3898 quote!()
3899 };
3900 let write = restore_auto_now_unless(
3901 &fields.auto_now_idents,
3902 &save_ret,
3903 &save_keep,
3904 "e! {
3905 let _query = #root::core::UpdateQuery::new(
3906 <Self as #root::core::Model>::SCHEMA,
3907 ::std::vec![ #( #assignments ),* ],
3908 #root::core::WhereExpr::Predicate(#root::core::Filter::new(
3909 #pk_column_lit,
3910 #root::core::Op::Eq,
3911 ::core::convert::Into::<#root::core::SqlValue>::into(
3912 ::core::clone::Clone::clone(&self.#pk_ident)
3913 ),
3914 )),
3915 );
3916 self.__rustango_save_with_diff(pool, &_query, ::core::option::Option::None)
3917 .await
3918 },
3919 );
3920 quote! {
3921 pub async fn save(
3933 &mut self,
3934 pool: &#root::sql::Pool,
3935 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
3936 #unset_dispatch
3937 #write
3938 }
3939
3940 #[doc(hidden)]
3942 async fn __rustango_save_with_diff(
3943 &self,
3944 pool: &#root::sql::Pool,
3945 query: &#root::core::UpdateQuery,
3946 only: ::core::option::Option<&::std::collections::HashSet<&'static str>>,
3947 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
3948 let mut _after_pairs = self.__rustango_audit_pairs();
3949 if let ::core::option::Option::Some(only) = only {
3950 _after_pairs.retain(|(col, _)| only.contains(col));
3951 }
3952 let _before_query = #root::audit::before_image_query(
3953 <Self as #root::core::Model>::SCHEMA,
3954 #pk_column_lit,
3955 ::core::convert::Into::<#root::core::SqlValue>::into(
3956 #pk_value_for_bind,
3957 ),
3958 &[ #( #tracked_cols ),* ],
3959 );
3960 #root::audit::save_one_with_diff(
3961 pool,
3962 query,
3963 &_before_query,
3964 <Self as #root::core::Model>::SCHEMA.table,
3965 self.__rustango_audit_pk(),
3966 _after_pairs,
3967 |_audit_before_row| ::std::vec![ #( #before_pairs_pg ),* ],
3968 |_audit_before_row| ::std::vec![ #( #before_pairs_my ),* ],
3969 |_audit_before_row| ::std::vec![ #( #before_pairs_sqlite ),* ],
3970 ).await
3971 }
3972
3973 #partial
3974 }
3975 } else {
3976 quote!()
3977 }
3978 } else {
3979 pool_save_method
3980 };
3981
3982 let pool_delete_method = {
3989 let pk_column_lit = primary_key.map(|(_, col)| col.as_str()).unwrap_or("id");
3990 let pk_ident_for_pool = primary_key.map(|(ident, _)| ident);
3991 if let Some(pk_ident) = pk_ident_for_pool {
3992 if audited_fields.is_some() {
3993 quote! {
3994 pub async fn delete(
4001 &self,
4002 pool: &#root::sql::Pool,
4003 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4004 let _query = #root::core::DeleteQuery::by_pk(
4005 <Self as #root::core::Model>::SCHEMA,
4006 #pk_column_lit,
4007 ::core::convert::Into::<#root::core::SqlValue>::into(
4008 ::core::clone::Clone::clone(&self.#pk_ident)
4009 ),
4010 );
4011 let _audit_entry =
4012 self.__rustango_audit_entry(#root::audit::AuditOp::Delete);
4013 #root::audit::delete_one_with_audit(
4014 pool, &_query, &_audit_entry,
4015 ).await
4016 }
4017 }
4018 } else {
4019 quote! {
4020 pub async fn delete(
4028 &self,
4029 pool: &#root::sql::Pool,
4030 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4031 self.__rustango_delete_exec(pool).await
4032 }
4033 }
4034 }
4035 } else {
4036 quote!()
4037 }
4038 };
4039
4040 let (bulk_delete_run, destroy_run, bulk_update_run, truncate_run, truncate_cascade_run) =
4043 if audited_fields.is_some() {
4044 (
4045 quote!(#root::audit::delete_many_with_audit::<Self>(pool, &_query, |_r: &Self| {
4046 _r.__rustango_audit_entry(#root::audit::AuditOp::Delete)
4047 })
4048 .await),
4049 quote!(#root::audit::delete_each_with_audit::<Self>(pool, &_queries, |_r: &Self| {
4050 _r.__rustango_audit_entry(#root::audit::AuditOp::Delete)
4051 })
4052 .await),
4053 quote!(#root::audit::update_many_with_audit::<Self>(pool, &_query, |_r: &Self| {
4054 _r.__rustango_audit_entry(#root::audit::AuditOp::Update)
4055 })
4056 .await),
4057 quote!(#root::audit::truncate_with_audit(
4058 pool,
4059 <Self as #root::core::Model>::SCHEMA.table,
4060 #root::sql::TruncateMode::Plain,
4061 )
4062 .await),
4063 quote!(#root::audit::truncate_with_audit(
4064 pool,
4065 <Self as #root::core::Model>::SCHEMA.table,
4066 #root::sql::TruncateMode::Cascade,
4067 )
4068 .await
4069 .map(drop)),
4070 )
4071 } else {
4072 (
4073 quote!(#root::sql::delete_pool(pool, &_query).await),
4074 quote!(#root::sql::__rustango_delete_all_or_none(pool, _queries).await),
4075 quote!(#root::sql::update_pool(pool, &_query).await),
4076 quote!(#root::sql::truncate(pool, <Self as #root::core::Model>::SCHEMA).await),
4077 quote!(#root::sql::truncate_cascade(pool, <Self as #root::core::Model>::SCHEMA).await),
4078 )
4079 };
4080
4081 let refresh_replicate_methods = if let Some((pk_ident, _)) = primary_key {
4094 let other_field_clones: Vec<TokenStream2> = fields
4095 .column_entries
4096 .iter()
4097 .filter(|c| &c.ident != pk_ident)
4098 .map(|c| {
4099 let ident = &c.ident;
4100 quote! {
4101 #ident: ::core::clone::Clone::clone(&self.#ident)
4102 }
4103 })
4104 .collect();
4105 let pk_clone_token = if fields.pk_is_auto {
4106 quote! { #pk_ident: #root::sql::Auto::Unset }
4107 } else {
4108 quote! { #pk_ident: ::core::clone::Clone::clone(&self.#pk_ident) }
4109 };
4110 let replicate_doc = if fields.pk_is_auto {
4111 quote! {
4112 }
4124 } else {
4125 quote! {
4126 }
4132 };
4133 let column_names: ::std::collections::HashSet<String> = fields
4147 .column_entries
4148 .iter()
4149 .map(|c| ident_name(&c.ident))
4150 .collect();
4151 let emit_if_no_field_collision = |name: &str, tokens: TokenStream2| -> TokenStream2 {
4152 if column_names.contains(name) {
4153 quote! {}
4154 } else {
4155 tokens
4156 }
4157 };
4158 let count_method = emit_if_no_field_collision(
4159 "count",
4160 quote! {
4161 pub async fn count(
4173 pool: &#root::sql::Pool,
4174 ) -> ::core::result::Result<i64, #root::sql::ExecError> {
4175 use #root::sql::CounterPool as _;
4176 #root::query::QuerySet::<Self>::default()
4177 .count(pool)
4178 .await
4179 }
4180 },
4181 );
4182 let value_method = emit_if_no_field_collision(
4183 "value",
4184 quote! {
4185 pub async fn value<U>(
4195 col: &str,
4196 pool: &#root::sql::Pool,
4197 ) -> ::core::result::Result<
4198 ::core::option::Option<U>,
4199 #root::sql::ExecError,
4200 >
4201 where
4202 U: #root::sql::FlatScalar,
4203 {
4204 let _col_static: &'static str = Self::__resolve_col(col)?;
4205 #root::query::QuerySet::<Self>::default()
4206 .values_list_flat(_col_static)
4207 .first::<U>(pool)
4208 .await
4209 }
4210 },
4211 );
4212 let sum_method = emit_if_no_field_collision(
4213 "sum",
4214 quote! {
4215 pub async fn sum<U>(
4222 col: &str,
4223 pool: &#root::sql::Pool,
4224 ) -> ::core::result::Result<
4225 ::core::option::Option<U>,
4226 #root::sql::ExecError,
4227 >
4228 where
4229 (::core::option::Option<U>,): #root::sql::MaybePgFromRow
4230 + #root::sql::MaybeMyFromRow
4231 + #root::sql::MaybeSqliteFromRow
4232 + ::core::marker::Send
4233 + ::core::marker::Unpin,
4234 {
4235 #root::query::QuerySet::<Self>::default()
4236 .sum::<U>(col, pool)
4237 .await
4238 }
4239 },
4240 );
4241 let avg_method = emit_if_no_field_collision(
4242 "avg",
4243 quote! {
4244 pub async fn avg<U>(
4250 col: &str,
4251 pool: &#root::sql::Pool,
4252 ) -> ::core::result::Result<
4253 ::core::option::Option<U>,
4254 #root::sql::ExecError,
4255 >
4256 where
4257 (::core::option::Option<U>,): #root::sql::MaybePgFromRow
4258 + #root::sql::MaybeMyFromRow
4259 + #root::sql::MaybeSqliteFromRow
4260 + ::core::marker::Send
4261 + ::core::marker::Unpin,
4262 {
4263 #root::query::QuerySet::<Self>::default()
4264 .avg::<U>(col, pool)
4265 .await
4266 }
4267 },
4268 );
4269 let min_method = emit_if_no_field_collision(
4270 "min",
4271 quote! {
4272 pub async fn min<U>(
4278 col: &str,
4279 pool: &#root::sql::Pool,
4280 ) -> ::core::result::Result<
4281 ::core::option::Option<U>,
4282 #root::sql::ExecError,
4283 >
4284 where
4285 (::core::option::Option<U>,): #root::sql::MaybePgFromRow
4286 + #root::sql::MaybeMyFromRow
4287 + #root::sql::MaybeSqliteFromRow
4288 + ::core::marker::Send
4289 + ::core::marker::Unpin,
4290 {
4291 #root::query::QuerySet::<Self>::default()
4292 .min::<U>(col, pool)
4293 .await
4294 }
4295 },
4296 );
4297 let max_method = emit_if_no_field_collision(
4298 "max",
4299 quote! {
4300 pub async fn max<U>(
4306 col: &str,
4307 pool: &#root::sql::Pool,
4308 ) -> ::core::result::Result<
4309 ::core::option::Option<U>,
4310 #root::sql::ExecError,
4311 >
4312 where
4313 (::core::option::Option<U>,): #root::sql::MaybePgFromRow
4314 + #root::sql::MaybeMyFromRow
4315 + #root::sql::MaybeSqliteFromRow
4316 + ::core::marker::Send
4317 + ::core::marker::Unpin,
4318 {
4319 #root::query::QuerySet::<Self>::default()
4320 .max::<U>(col, pool)
4321 .await
4322 }
4323 },
4324 );
4325 let first_method = emit_if_no_field_collision(
4326 "first",
4327 quote! {
4328 pub async fn first(
4335 pool: &#root::sql::Pool,
4336 ) -> ::core::result::Result<
4337 ::core::option::Option<Self>,
4338 #root::sql::ExecError,
4339 > {
4340 #root::query::QuerySet::<Self>::default()
4341 .first(pool)
4342 .await
4343 }
4344 },
4345 );
4346 let last_method = emit_if_no_field_collision(
4347 "last",
4348 quote! {
4349 pub async fn last(
4362 pool: &#root::sql::Pool,
4363 ) -> ::core::result::Result<
4364 ::core::option::Option<Self>,
4365 #root::sql::ExecError,
4366 > {
4367 #root::query::QuerySet::<Self>::default()
4368 .last(pool)
4369 .await
4370 }
4371 },
4372 );
4373 quote! {
4374 pub async fn refresh_from_db(
4393 &mut self,
4394 pool: &#root::sql::Pool,
4395 ) -> ::core::result::Result<(), #root::sql::ExecError> {
4396 use #root::sql::FetcherPool as _;
4397 let _pk_val: #root::core::SqlValue = ::core::convert::Into::into(
4398 ::core::clone::Clone::clone(&self.#pk_ident),
4399 );
4400 let mut _rows: ::std::vec::Vec<Self> =
4401 #root::query::QuerySet::<Self>::default()
4402 .filter(::core::stringify!(#pk_ident), _pk_val)
4403 .limit(1)
4404 .fetch(pool)
4405 .await?;
4406 match _rows.into_iter().next() {
4407 ::core::option::Option::Some(_fresh) => {
4408 *self = _fresh;
4409 ::core::result::Result::Ok(())
4410 }
4411 ::core::option::Option::None => ::core::result::Result::Err(
4412 #root::sql::ExecError::Driver(
4413 #root::sql::sqlx::Error::RowNotFound,
4414 ),
4415 ),
4416 }
4417 }
4418
4419 pub async fn increment(
4439 &self,
4440 col: &str,
4441 by: i64,
4442 pool: &#root::sql::Pool,
4443 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4444 Self::__increment_one(self, col, by, pool).await
4445 }
4446
4447 pub async fn decrement(
4456 &self,
4457 col: &str,
4458 by: i64,
4459 pool: &#root::sql::Pool,
4460 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4461 Self::__increment_one(self, col, -by, pool).await
4462 }
4463
4464 pub async fn increment_each(
4474 col: &str,
4475 by: i64,
4476 pool: &#root::sql::Pool,
4477 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4478 Self::__increment_all(col, by, pool).await
4479 }
4480
4481 pub async fn decrement_each(
4486 col: &str,
4487 by: i64,
4488 pool: &#root::sql::Pool,
4489 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4490 Self::__increment_all(col, -by, pool).await
4491 }
4492
4493 #[doc(hidden)]
4499 pub async fn __increment_one(
4500 this: &Self,
4501 col: &str,
4502 by: i64,
4503 pool: &#root::sql::Pool,
4504 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4505 let _pk_val: #root::core::SqlValue = ::core::convert::Into::into(
4506 ::core::clone::Clone::clone(&this.#pk_ident),
4507 );
4508 #root::sql::model_shortcuts::increment_one_pool::<Self>(
4509 ::core::stringify!(#pk_ident),
4510 _pk_val,
4511 col,
4512 by,
4513 pool,
4514 )
4515 .await
4516 }
4517
4518 #[doc(hidden)]
4521 pub async fn __increment_all(
4522 col: &str,
4523 by: i64,
4524 pool: &#root::sql::Pool,
4525 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4526 let _query =
4527 #root::sql::model_shortcuts::increment_all_query::<Self>(col, by)?;
4528 #bulk_update_run
4529 }
4530
4531 #[doc(hidden)]
4534 pub fn __resolve_col(
4535 col: &str,
4536 ) -> ::core::result::Result<&'static str, #root::sql::ExecError> {
4537 #root::sql::model_shortcuts::resolve_col::<Self>(col)
4538 }
4539
4540 #[doc(hidden)]
4543 #[must_use]
4544 pub fn __add_signed_expr(
4545 col_static: &'static str,
4546 signed_by: i64,
4547 ) -> #root::core::Expr {
4548 #root::sql::model_shortcuts::add_signed_expr(col_static, signed_by)
4549 }
4550
4551 pub async fn fresh(
4571 &self,
4572 pool: &#root::sql::Pool,
4573 ) -> ::core::result::Result<
4574 ::core::option::Option<Self>,
4575 #root::sql::ExecError,
4576 > {
4577 use #root::sql::FetcherPool as _;
4578 let _pk_val: #root::core::SqlValue = ::core::convert::Into::into(
4579 ::core::clone::Clone::clone(&self.#pk_ident),
4580 );
4581 let _rows: ::std::vec::Vec<Self> =
4582 #root::query::QuerySet::<Self>::default()
4583 .filter(::core::stringify!(#pk_ident), _pk_val)
4584 .limit(1)
4585 .fetch(pool)
4586 .await?;
4587 ::core::result::Result::Ok(_rows.into_iter().next())
4588 }
4589
4590 #replicate_doc
4591 #[must_use]
4592 pub fn replicate(&self) -> Self {
4593 Self {
4594 #pk_clone_token,
4595 #( #other_field_clones, )*
4596 }
4597 }
4598
4599 #first_method
4600
4601 #last_method
4602
4603 pub async fn first_or_fail(
4613 pool: &#root::sql::Pool,
4614 ) -> ::core::result::Result<Self, #root::sql::ExecError> {
4615 match #root::query::QuerySet::<Self>::default().first(pool).await? {
4619 ::core::option::Option::Some(_row) => ::core::result::Result::Ok(_row),
4620 ::core::option::Option::None => ::core::result::Result::Err(
4621 #root::sql::ExecError::Driver(
4622 #root::sql::sqlx::Error::RowNotFound,
4623 ),
4624 ),
4625 }
4626 }
4627
4628 pub async fn pluck<U>(
4640 col: &'static str,
4641 pool: &#root::sql::Pool,
4642 ) -> ::core::result::Result<::std::vec::Vec<U>, #root::sql::ExecError>
4643 where
4644 U: #root::sql::FlatScalar,
4645 {
4646 #root::query::QuerySet::<Self>::default()
4647 .values_list_flat(col)
4648 .fetch::<U>(pool)
4649 .await
4650 }
4651
4652 pub async fn chunk<F, Fut>(
4678 n: i64,
4679 pool: &#root::sql::Pool,
4680 mut cb: F,
4681 ) -> ::core::result::Result<(), #root::sql::ExecError>
4682 where
4683 F: ::core::ops::FnMut(::std::vec::Vec<Self>) -> Fut,
4684 Fut: ::core::future::Future<
4685 Output = ::core::result::Result<(), #root::sql::ExecError>,
4686 >,
4687 {
4688 use #root::sql::FetcherPool as _;
4689 let pk_col = match <Self as #root::core::Model>::SCHEMA.primary_key() {
4691 ::core::option::Option::Some(f) => f.name,
4692 ::core::option::Option::None => {
4693 return ::core::result::Result::Ok(());
4694 }
4695 };
4696 let mut offset: i64 = 0;
4697 loop {
4698 let rows: ::std::vec::Vec<Self> =
4699 #root::query::QuerySet::<Self>::default()
4700 .order_by(&[(pk_col, false)])
4701 .limit(n)
4702 .offset(offset)
4703 .fetch(pool)
4704 .await?;
4705 if rows.is_empty() {
4706 return ::core::result::Result::Ok(());
4707 }
4708 let len = rows.len() as i64;
4709 cb(rows).await?;
4710 if len < n {
4711 return ::core::result::Result::Ok(());
4712 }
4713 offset += n;
4714 }
4715 }
4716
4717 pub async fn chunk_by_id<F, Fut>(
4729 n: i64,
4730 pool: &#root::sql::Pool,
4731 mut cb: F,
4732 ) -> ::core::result::Result<(), #root::sql::ExecError>
4733 where
4734 F: ::core::ops::FnMut(::std::vec::Vec<Self>) -> Fut,
4735 Fut: ::core::future::Future<
4736 Output = ::core::result::Result<(), #root::sql::ExecError>,
4737 >,
4738 {
4739 use #root::sql::FetcherPool as _;
4740 let pk_col = match <Self as #root::core::Model>::SCHEMA.primary_key() {
4742 ::core::option::Option::Some(f) => f.name,
4743 ::core::option::Option::None => {
4744 return ::core::result::Result::Ok(());
4745 }
4746 };
4747 let key = ::std::format!("{}__gt", pk_col);
4750 let mut last_seen: ::core::option::Option<#root::core::SqlValue> =
4751 ::core::option::Option::None;
4752 loop {
4753 let mut qs = #root::query::QuerySet::<Self>::default();
4754 if let ::core::option::Option::Some(v) = last_seen.take() {
4755 qs = qs.filter(key.as_str(), v);
4756 }
4757 let rows: ::std::vec::Vec<Self> = qs
4758 .order_by(&[(pk_col, false)])
4759 .limit(n)
4760 .fetch(pool)
4761 .await?;
4762 let len = rows.len() as i64;
4763 let ::core::option::Option::Some(max_pk) =
4766 rows.last().map(|r| r.__rustango_pk_value())
4767 else {
4768 return ::core::result::Result::Ok(());
4769 };
4770 cb(rows).await?;
4771 if len < n {
4772 return ::core::result::Result::Ok(());
4773 }
4774 last_seen = ::core::option::Option::Some(max_pk);
4775 }
4776 }
4777
4778 pub async fn each<F, Fut>(
4796 batch: i64,
4797 pool: &#root::sql::Pool,
4798 mut cb: F,
4799 ) -> ::core::result::Result<(), #root::sql::ExecError>
4800 where
4801 F: ::core::ops::FnMut(Self) -> Fut,
4802 Fut: ::core::future::Future<
4803 Output = ::core::result::Result<(), #root::sql::ExecError>,
4804 >,
4805 {
4806 use #root::sql::FetcherPool as _;
4807 let pk_col = match <Self as #root::core::Model>::SCHEMA.primary_key() {
4809 ::core::option::Option::Some(f) => f.name,
4810 ::core::option::Option::None => {
4811 return ::core::result::Result::Ok(());
4812 }
4813 };
4814 let key = ::std::format!("{}__gt", pk_col);
4815 let mut last_seen: ::core::option::Option<#root::core::SqlValue> =
4816 ::core::option::Option::None;
4817 loop {
4818 let mut qs = #root::query::QuerySet::<Self>::default();
4819 if let ::core::option::Option::Some(v) = last_seen.take() {
4820 qs = qs.filter(key.as_str(), v);
4821 }
4822 let rows: ::std::vec::Vec<Self> = qs
4823 .order_by(&[(pk_col, false)])
4824 .limit(batch)
4825 .fetch(pool)
4826 .await?;
4827 let len = rows.len() as i64;
4828 let ::core::option::Option::Some(max_pk) =
4829 rows.last().map(|r| r.__rustango_pk_value())
4830 else {
4831 return ::core::result::Result::Ok(());
4832 };
4833 for row in rows {
4834 cb(row).await?;
4835 }
4836 if len < batch {
4837 return ::core::result::Result::Ok(());
4838 }
4839 last_seen = ::core::option::Option::Some(max_pk);
4840 }
4841 }
4842
4843 pub async fn truncate(
4859 pool: &#root::sql::Pool,
4860 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4861 #truncate_run
4862 }
4863
4864 pub async fn truncate_cascade(
4873 pool: &#root::sql::Pool,
4874 ) -> ::core::result::Result<(), #root::sql::ExecError> {
4875 #truncate_cascade_run
4876 }
4877
4878 pub async fn destroy<V>(
4893 pks: impl ::core::iter::IntoIterator<Item = V>,
4894 pool: &#root::sql::Pool,
4895 ) -> ::core::result::Result<u64, #root::sql::ExecError>
4896 where
4897 V: ::core::convert::Into<#root::core::SqlValue>,
4898 {
4899 let _values: ::std::vec::Vec<#root::core::SqlValue> =
4900 pks.into_iter().map(::core::convert::Into::into).collect();
4901 if _values.is_empty() {
4902 return ::core::result::Result::Ok(0);
4903 }
4904 let _pk = <Self as #root::core::Model>::SCHEMA
4905 .primary_key()
4906 .ok_or_else(|| {
4907 #root::sql::ExecError::Sql(#root::sql::SqlError::MissingPrimaryKey)
4908 })?;
4909 let mut _queries = ::std::vec::Vec::new();
4911 for _keys in #root::sql::__rustango_in_batches::<Self>(pool, _values)? {
4912 _queries.push(
4913 #root::query::QuerySet::<Self>::default()
4914 .filter_op(
4915 _pk.name,
4916 #root::core::Op::In,
4917 #root::core::SqlValue::List(_keys),
4918 )
4919 .compile_delete()?,
4920 );
4921 }
4922 #destroy_run
4923 }
4924
4925 pub async fn where_(
4943 col: &str,
4944 val: impl ::core::convert::Into<#root::core::SqlValue>,
4945 pool: &#root::sql::Pool,
4946 ) -> ::core::result::Result<
4947 ::std::vec::Vec<Self>,
4948 #root::sql::ExecError,
4949 > {
4950 use #root::sql::FetcherPool as _;
4951 #root::query::QuerySet::<Self>::default()
4952 .filter(col, val)
4953 .fetch(pool)
4954 .await
4955 }
4956
4957 pub async fn where_in<V>(
4971 col: &str,
4972 vals: impl ::core::iter::IntoIterator<Item = V>,
4973 pool: &#root::sql::Pool,
4974 ) -> ::core::result::Result<
4975 ::std::vec::Vec<Self>,
4976 #root::sql::ExecError,
4977 >
4978 where
4979 V: ::core::convert::Into<#root::core::SqlValue>,
4980 {
4981 use #root::sql::FetcherPool as _;
4982 let _values: ::std::vec::Vec<#root::core::SqlValue> =
4983 vals.into_iter().map(::core::convert::Into::into).collect();
4984 if _values.is_empty() {
4985 return ::core::result::Result::Ok(::std::vec::Vec::new());
4986 }
4987 let _key = ::std::format!("{}__in", col);
4988 let _batches = #root::sql::__rustango_in_batches::<Self>(pool, _values)?;
4989 let _split = _batches.len() > 1;
4990 let mut _rows = ::std::vec::Vec::new();
4991 for _keys in _batches {
4992 _rows.extend(
4993 #root::query::QuerySet::<Self>::default()
4994 .filter(&_key, #root::core::SqlValue::List(_keys))
4995 .fetch(pool)
4996 .await?,
4997 );
4998 }
4999 if _split {
5000 _rows = #root::sql::__rustango_dedupe_by_pk(_rows);
5001 }
5002 ::core::result::Result::Ok(_rows)
5003 }
5004
5005 pub async fn where_not_in<V>(
5016 col: &str,
5017 vals: impl ::core::iter::IntoIterator<Item = V>,
5018 pool: &#root::sql::Pool,
5019 ) -> ::core::result::Result<
5020 ::std::vec::Vec<Self>,
5021 #root::sql::ExecError,
5022 >
5023 where
5024 V: ::core::convert::Into<#root::core::SqlValue>,
5025 {
5026 use #root::sql::FetcherPool as _;
5027 let _values: ::std::vec::Vec<#root::core::SqlValue> =
5028 vals.into_iter().map(::core::convert::Into::into).collect();
5029 if _values.is_empty() {
5030 return #root::query::QuerySet::<Self>::default()
5031 .fetch(pool)
5032 .await;
5033 }
5034 let _key = ::std::format!("{}__not_in", col);
5035 let _values = #root::sql::__rustango_one_in_list::<Self>(pool, _values)?;
5037 #root::query::QuerySet::<Self>::default()
5038 .filter(&_key, #root::core::SqlValue::List(_values))
5039 .fetch(pool)
5040 .await
5041 }
5042
5043 pub async fn where_null(
5051 col: &str,
5052 pool: &#root::sql::Pool,
5053 ) -> ::core::result::Result<
5054 ::std::vec::Vec<Self>,
5055 #root::sql::ExecError,
5056 > {
5057 use #root::sql::FetcherPool as _;
5058 let _key = ::std::format!("{}__isnull", col);
5059 #root::query::QuerySet::<Self>::default()
5060 .filter(&_key, #root::core::SqlValue::Bool(true))
5061 .fetch(pool)
5062 .await
5063 }
5064
5065 pub async fn where_not_null(
5073 col: &str,
5074 pool: &#root::sql::Pool,
5075 ) -> ::core::result::Result<
5076 ::std::vec::Vec<Self>,
5077 #root::sql::ExecError,
5078 > {
5079 use #root::sql::FetcherPool as _;
5080 let _key = ::std::format!("{}__isnull", col);
5081 #root::query::QuerySet::<Self>::default()
5082 .filter(&_key, #root::core::SqlValue::Bool(false))
5083 .fetch(pool)
5084 .await
5085 }
5086
5087 pub async fn random_n(
5100 n: i64,
5101 pool: &#root::sql::Pool,
5102 ) -> ::core::result::Result<
5103 ::std::vec::Vec<Self>,
5104 #root::sql::ExecError,
5105 > {
5106 use #root::sql::FetcherPool as _;
5107 #root::query::QuerySet::<Self>::default()
5108 .order_random()
5109 .limit(n)
5110 .fetch(pool)
5111 .await
5112 }
5113
5114 pub async fn random(
5123 pool: &#root::sql::Pool,
5124 ) -> ::core::result::Result<
5125 ::core::option::Option<Self>,
5126 #root::sql::ExecError,
5127 > {
5128 ::core::result::Result::Ok(
5129 Self::random_n(1, pool).await?.into_iter().next(),
5130 )
5131 }
5132
5133 pub async fn oldest(
5142 field: &str,
5143 pool: &#root::sql::Pool,
5144 ) -> ::core::result::Result<
5145 ::std::vec::Vec<Self>,
5146 #root::sql::ExecError,
5147 > {
5148 use #root::sql::FetcherPool as _;
5149 #root::query::QuerySet::<Self>::default()
5150 .order_by(&[(field, false)])
5151 .fetch(pool)
5152 .await
5153 }
5154
5155 pub async fn newest(
5164 field: &str,
5165 pool: &#root::sql::Pool,
5166 ) -> ::core::result::Result<
5167 ::std::vec::Vec<Self>,
5168 #root::sql::ExecError,
5169 > {
5170 use #root::sql::FetcherPool as _;
5171 #root::query::QuerySet::<Self>::default()
5172 .order_by(&[(field, true)])
5173 .fetch(pool)
5174 .await
5175 }
5176
5177 pub async fn where_year(
5187 col: &str,
5188 year: i64,
5189 pool: &#root::sql::Pool,
5190 ) -> ::core::result::Result<
5191 ::std::vec::Vec<Self>,
5192 #root::sql::ExecError,
5193 > {
5194 use #root::sql::FetcherPool as _;
5195 let _key = ::std::format!("{}__year", col);
5196 #root::query::QuerySet::<Self>::default()
5197 .filter(&_key, #root::core::SqlValue::I64(year))
5198 .fetch(pool)
5199 .await
5200 }
5201
5202 pub async fn where_month(
5211 col: &str,
5212 month: i64,
5213 pool: &#root::sql::Pool,
5214 ) -> ::core::result::Result<
5215 ::std::vec::Vec<Self>,
5216 #root::sql::ExecError,
5217 > {
5218 use #root::sql::FetcherPool as _;
5219 let _key = ::std::format!("{}__month", col);
5220 #root::query::QuerySet::<Self>::default()
5221 .filter(&_key, #root::core::SqlValue::I64(month))
5222 .fetch(pool)
5223 .await
5224 }
5225
5226 pub async fn where_day(
5235 col: &str,
5236 day: i64,
5237 pool: &#root::sql::Pool,
5238 ) -> ::core::result::Result<
5239 ::std::vec::Vec<Self>,
5240 #root::sql::ExecError,
5241 > {
5242 use #root::sql::FetcherPool as _;
5243 let _key = ::std::format!("{}__day", col);
5244 #root::query::QuerySet::<Self>::default()
5245 .filter(&_key, #root::core::SqlValue::I64(day))
5246 .fetch(pool)
5247 .await
5248 }
5249
5250 pub async fn where_hour(
5259 col: &str,
5260 hour: i64,
5261 pool: &#root::sql::Pool,
5262 ) -> ::core::result::Result<
5263 ::std::vec::Vec<Self>,
5264 #root::sql::ExecError,
5265 > {
5266 use #root::sql::FetcherPool as _;
5267 let _key = ::std::format!("{}__hour", col);
5268 #root::query::QuerySet::<Self>::default()
5269 .filter(&_key, #root::core::SqlValue::I64(hour))
5270 .fetch(pool)
5271 .await
5272 }
5273
5274 pub async fn where_minute(
5283 col: &str,
5284 minute: i64,
5285 pool: &#root::sql::Pool,
5286 ) -> ::core::result::Result<
5287 ::std::vec::Vec<Self>,
5288 #root::sql::ExecError,
5289 > {
5290 use #root::sql::FetcherPool as _;
5291 let _key = ::std::format!("{}__minute", col);
5292 #root::query::QuerySet::<Self>::default()
5293 .filter(&_key, #root::core::SqlValue::I64(minute))
5294 .fetch(pool)
5295 .await
5296 }
5297
5298 pub async fn where_like(
5308 col: &str,
5309 pattern: impl ::core::convert::Into<::std::string::String>,
5310 pool: &#root::sql::Pool,
5311 ) -> ::core::result::Result<
5312 ::std::vec::Vec<Self>,
5313 #root::sql::ExecError,
5314 > {
5315 use #root::sql::FetcherPool as _;
5316 let _key = ::std::format!("{}__like", col);
5317 #root::query::QuerySet::<Self>::default()
5318 .filter(
5319 &_key,
5320 #root::core::SqlValue::String(pattern.into()),
5321 )
5322 .fetch(pool)
5323 .await
5324 }
5325
5326 pub async fn where_ilike(
5335 col: &str,
5336 pattern: impl ::core::convert::Into<::std::string::String>,
5337 pool: &#root::sql::Pool,
5338 ) -> ::core::result::Result<
5339 ::std::vec::Vec<Self>,
5340 #root::sql::ExecError,
5341 > {
5342 use #root::sql::FetcherPool as _;
5343 let _key = ::std::format!("{}__ilike", col);
5344 #root::query::QuerySet::<Self>::default()
5345 .filter(
5346 &_key,
5347 #root::core::SqlValue::String(pattern.into()),
5348 )
5349 .fetch(pool)
5350 .await
5351 }
5352
5353 pub async fn where_starts_with(
5362 col: &str,
5363 prefix: impl ::core::convert::Into<::std::string::String>,
5364 pool: &#root::sql::Pool,
5365 ) -> ::core::result::Result<
5366 ::std::vec::Vec<Self>,
5367 #root::sql::ExecError,
5368 > {
5369 use #root::sql::FetcherPool as _;
5370 let _key = ::std::format!("{}__startswith", col);
5371 #root::query::QuerySet::<Self>::default()
5372 .filter(
5373 &_key,
5374 #root::core::SqlValue::String(prefix.into()),
5375 )
5376 .fetch(pool)
5377 .await
5378 }
5379
5380 pub async fn where_ends_with(
5389 col: &str,
5390 suffix: impl ::core::convert::Into<::std::string::String>,
5391 pool: &#root::sql::Pool,
5392 ) -> ::core::result::Result<
5393 ::std::vec::Vec<Self>,
5394 #root::sql::ExecError,
5395 > {
5396 use #root::sql::FetcherPool as _;
5397 let _key = ::std::format!("{}__endswith", col);
5398 #root::query::QuerySet::<Self>::default()
5399 .filter(
5400 &_key,
5401 #root::core::SqlValue::String(suffix.into()),
5402 )
5403 .fetch(pool)
5404 .await
5405 }
5406
5407 pub async fn where_contains(
5416 col: &str,
5417 substr: impl ::core::convert::Into<::std::string::String>,
5418 pool: &#root::sql::Pool,
5419 ) -> ::core::result::Result<
5420 ::std::vec::Vec<Self>,
5421 #root::sql::ExecError,
5422 > {
5423 use #root::sql::FetcherPool as _;
5424 let _key = ::std::format!("{}__contains", col);
5425 #root::query::QuerySet::<Self>::default()
5426 .filter(
5427 &_key,
5428 #root::core::SqlValue::String(substr.into()),
5429 )
5430 .fetch(pool)
5431 .await
5432 }
5433
5434 pub async fn where_gt(
5442 col: &str,
5443 val: impl ::core::convert::Into<#root::core::SqlValue>,
5444 pool: &#root::sql::Pool,
5445 ) -> ::core::result::Result<
5446 ::std::vec::Vec<Self>,
5447 #root::sql::ExecError,
5448 > {
5449 use #root::sql::FetcherPool as _;
5450 let _key = ::std::format!("{}__gt", col);
5451 #root::query::QuerySet::<Self>::default()
5452 .filter(&_key, val)
5453 .fetch(pool)
5454 .await
5455 }
5456
5457 pub async fn where_gte(
5465 col: &str,
5466 val: impl ::core::convert::Into<#root::core::SqlValue>,
5467 pool: &#root::sql::Pool,
5468 ) -> ::core::result::Result<
5469 ::std::vec::Vec<Self>,
5470 #root::sql::ExecError,
5471 > {
5472 use #root::sql::FetcherPool as _;
5473 let _key = ::std::format!("{}__gte", col);
5474 #root::query::QuerySet::<Self>::default()
5475 .filter(&_key, val)
5476 .fetch(pool)
5477 .await
5478 }
5479
5480 pub async fn where_lt(
5488 col: &str,
5489 val: impl ::core::convert::Into<#root::core::SqlValue>,
5490 pool: &#root::sql::Pool,
5491 ) -> ::core::result::Result<
5492 ::std::vec::Vec<Self>,
5493 #root::sql::ExecError,
5494 > {
5495 use #root::sql::FetcherPool as _;
5496 let _key = ::std::format!("{}__lt", col);
5497 #root::query::QuerySet::<Self>::default()
5498 .filter(&_key, val)
5499 .fetch(pool)
5500 .await
5501 }
5502
5503 pub async fn where_lte(
5511 col: &str,
5512 val: impl ::core::convert::Into<#root::core::SqlValue>,
5513 pool: &#root::sql::Pool,
5514 ) -> ::core::result::Result<
5515 ::std::vec::Vec<Self>,
5516 #root::sql::ExecError,
5517 > {
5518 use #root::sql::FetcherPool as _;
5519 let _key = ::std::format!("{}__lte", col);
5520 #root::query::QuerySet::<Self>::default()
5521 .filter(&_key, val)
5522 .fetch(pool)
5523 .await
5524 }
5525
5526 pub async fn where_ne(
5534 col: &str,
5535 val: impl ::core::convert::Into<#root::core::SqlValue>,
5536 pool: &#root::sql::Pool,
5537 ) -> ::core::result::Result<
5538 ::std::vec::Vec<Self>,
5539 #root::sql::ExecError,
5540 > {
5541 use #root::sql::FetcherPool as _;
5542 let _key = ::std::format!("{}__ne", col);
5543 #root::query::QuerySet::<Self>::default()
5544 .filter(&_key, val)
5545 .fetch(pool)
5546 .await
5547 }
5548
5549 pub async fn where_any(
5563 cols: &[&str],
5564 val: impl ::core::convert::Into<#root::core::SqlValue>,
5565 pool: &#root::sql::Pool,
5566 ) -> ::core::result::Result<
5567 ::std::vec::Vec<Self>,
5568 #root::sql::ExecError,
5569 > {
5570 Self::__where_multi(cols, val, false, pool).await
5571 }
5572
5573 pub async fn where_all(
5580 cols: &[&str],
5581 val: impl ::core::convert::Into<#root::core::SqlValue>,
5582 pool: &#root::sql::Pool,
5583 ) -> ::core::result::Result<
5584 ::std::vec::Vec<Self>,
5585 #root::sql::ExecError,
5586 > {
5587 Self::__where_multi(cols, val, true, pool).await
5588 }
5589
5590 #[doc(hidden)]
5594 pub async fn __where_multi(
5595 cols: &[&str],
5596 val: impl ::core::convert::Into<#root::core::SqlValue>,
5597 all: bool,
5598 pool: &#root::sql::Pool,
5599 ) -> ::core::result::Result<
5600 ::std::vec::Vec<Self>,
5601 #root::sql::ExecError,
5602 > {
5603 #root::sql::model_shortcuts::where_multi_pool::<Self>(cols, val, all, pool)
5604 .await
5605 }
5606
5607 pub async fn take(
5617 n: i64,
5618 pool: &#root::sql::Pool,
5619 ) -> ::core::result::Result<
5620 ::std::vec::Vec<Self>,
5621 #root::sql::ExecError,
5622 > {
5623 use #root::sql::FetcherPool as _;
5624 #root::query::QuerySet::<Self>::default()
5625 .limit(n)
5626 .fetch(pool)
5627 .await
5628 }
5629
5630 pub async fn for_page(
5642 page: i64,
5643 per_page: i64,
5644 pool: &#root::sql::Pool,
5645 ) -> ::core::result::Result<
5646 ::std::vec::Vec<Self>,
5647 #root::sql::ExecError,
5648 > {
5649 use #root::sql::FetcherPool as _;
5650 let _offset = #root::list_params::page_offset(page, per_page);
5651 #root::query::QuerySet::<Self>::default()
5652 .limit(per_page)
5653 .offset(_offset)
5654 .fetch(pool)
5655 .await
5656 }
5657
5658 pub async fn paginate(
5674 page: i64,
5675 per_page: i64,
5676 pool: &#root::sql::Pool,
5677 ) -> ::core::result::Result<
5678 (::std::vec::Vec<Self>, i64),
5679 #root::sql::ExecError,
5680 > {
5681 let total = {
5687 use #root::sql::CounterPool as _;
5688 #root::query::QuerySet::<Self>::default()
5689 .count(pool)
5690 .await?
5691 };
5692 let rows = Self::for_page(page, per_page, pool).await?;
5693 ::core::result::Result::Ok((rows, total))
5694 }
5695
5696 pub async fn update_where(
5710 where_col: &str,
5711 where_val: impl ::core::convert::Into<#root::core::SqlValue>,
5712 set_col: &str,
5713 set_val: impl ::core::convert::Into<#root::core::SqlValue>,
5714 pool: &#root::sql::Pool,
5715 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
5716 let _query = #root::query::QuerySet::<Self>::default()
5717 .filter(where_col, where_val)
5718 .update()
5719 .set(set_col, set_val)
5720 .compile()?;
5721 #bulk_update_run
5722 }
5723
5724 pub async fn delete_where(
5732 where_col: &str,
5733 where_val: impl ::core::convert::Into<#root::core::SqlValue>,
5734 pool: &#root::sql::Pool,
5735 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
5736 let _query = #root::query::QuerySet::<Self>::default()
5737 .filter_op(where_col, #root::core::Op::Eq, where_val)
5738 .compile_delete()?;
5739 #bulk_delete_run
5740 }
5741
5742 pub async fn update_all(
5755 set_col: &str,
5756 set_val: impl ::core::convert::Into<#root::core::SqlValue>,
5757 pool: &#root::sql::Pool,
5758 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
5759 let _query = #root::query::QuerySet::<Self>::default()
5760 .update()
5761 .set(set_col, set_val)
5762 .compile()?;
5763 #bulk_update_run
5764 }
5765
5766 pub async fn where_not_like(
5775 col: &str,
5776 pattern: impl ::core::convert::Into<::std::string::String>,
5777 pool: &#root::sql::Pool,
5778 ) -> ::core::result::Result<
5779 ::std::vec::Vec<Self>,
5780 #root::sql::ExecError,
5781 > {
5782 use #root::sql::FetcherPool as _;
5783 let _key = ::std::format!("{}__not_like", col);
5784 #root::query::QuerySet::<Self>::default()
5785 .filter(
5786 &_key,
5787 #root::core::SqlValue::String(pattern.into()),
5788 )
5789 .fetch(pool)
5790 .await
5791 }
5792
5793 pub async fn where_not_ilike(
5802 col: &str,
5803 pattern: impl ::core::convert::Into<::std::string::String>,
5804 pool: &#root::sql::Pool,
5805 ) -> ::core::result::Result<
5806 ::std::vec::Vec<Self>,
5807 #root::sql::ExecError,
5808 > {
5809 use #root::sql::FetcherPool as _;
5810 let _key = ::std::format!("{}__not_ilike", col);
5811 #root::query::QuerySet::<Self>::default()
5812 .filter(
5813 &_key,
5814 #root::core::SqlValue::String(pattern.into()),
5815 )
5816 .fetch(pool)
5817 .await
5818 }
5819
5820 pub async fn where_not_between(
5829 col: &str,
5830 lo: impl ::core::convert::Into<#root::core::SqlValue>,
5831 hi: impl ::core::convert::Into<#root::core::SqlValue>,
5832 pool: &#root::sql::Pool,
5833 ) -> ::core::result::Result<
5834 ::std::vec::Vec<Self>,
5835 #root::sql::ExecError,
5836 > {
5837 use #root::sql::FetcherPool as _;
5838 let _key = ::std::format!("{}__not_between", col);
5839 let _vals = #root::core::SqlValue::List(::std::vec![
5840 ::core::convert::Into::into(lo),
5841 ::core::convert::Into::into(hi),
5842 ]);
5843 #root::query::QuerySet::<Self>::default()
5844 .filter(&_key, _vals)
5845 .fetch(pool)
5846 .await
5847 }
5848
5849 #[must_use]
5852 pub fn table_name() -> &'static str {
5853 <Self as #root::core::Model>::SCHEMA.table
5854 }
5855
5856 #[must_use]
5861 pub fn primary_key_column() -> ::core::option::Option<&'static str> {
5862 <Self as #root::core::Model>::SCHEMA
5863 .primary_key()
5864 .map(|f| f.column)
5865 }
5866
5867 pub async fn where_between(
5876 col: &str,
5877 lo: impl ::core::convert::Into<#root::core::SqlValue>,
5878 hi: impl ::core::convert::Into<#root::core::SqlValue>,
5879 pool: &#root::sql::Pool,
5880 ) -> ::core::result::Result<
5881 ::std::vec::Vec<Self>,
5882 #root::sql::ExecError,
5883 > {
5884 use #root::sql::FetcherPool as _;
5885 let _key = ::std::format!("{}__between", col);
5886 let _vals = #root::core::SqlValue::List(::std::vec![
5887 ::core::convert::Into::into(lo),
5888 ::core::convert::Into::into(hi),
5889 ]);
5890 #root::query::QuerySet::<Self>::default()
5891 .filter(&_key, _vals)
5892 .fetch(pool)
5893 .await
5894 }
5895
5896 pub async fn first_where(
5911 col: &str,
5912 val: impl ::core::convert::Into<#root::core::SqlValue>,
5913 pool: &#root::sql::Pool,
5914 ) -> ::core::result::Result<
5915 ::core::option::Option<Self>,
5916 #root::sql::ExecError,
5917 > {
5918 #root::query::QuerySet::<Self>::default()
5919 .filter(col, val)
5920 .first(pool)
5921 .await
5922 }
5923
5924 #value_method
5925
5926 pub async fn latest(
5940 field: &str,
5941 pool: &#root::sql::Pool,
5942 ) -> ::core::result::Result<
5943 ::core::option::Option<Self>,
5944 #root::sql::ExecError,
5945 > {
5946 #root::query::QuerySet::<Self>::default()
5947 .latest(field, pool)
5948 .await
5949 }
5950
5951 pub async fn earliest(
5957 field: &str,
5958 pool: &#root::sql::Pool,
5959 ) -> ::core::result::Result<
5960 ::core::option::Option<Self>,
5961 #root::sql::ExecError,
5962 > {
5963 #root::query::QuerySet::<Self>::default()
5964 .earliest(field, pool)
5965 .await
5966 }
5967
5968 #count_method
5969
5970 pub async fn exists(
5979 pool: &#root::sql::Pool,
5980 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
5981 use #root::sql::ExistsPool as _;
5982 #root::query::QuerySet::<Self>::default()
5983 .exists(pool)
5984 .await
5985 }
5986
5987 pub async fn doesnt_exist(
5994 pool: &#root::sql::Pool,
5995 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
5996 Self::exists(pool).await.map(|e| !e)
5997 }
5998
5999 pub async fn contains_pk(
6013 pk: impl ::core::convert::Into<#root::core::SqlValue> + ::core::marker::Send,
6014 pool: &#root::sql::Pool,
6015 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
6016 use #root::sql::ExistsPool as _;
6017 #root::query::QuerySet::<Self>::default()
6018 .contains_pk(pool, pk)
6019 .await
6020 }
6021
6022 #sum_method
6023 #avg_method
6024 #min_method
6025 #max_method
6026
6027 pub async fn all(
6043 pool: &#root::sql::Pool,
6044 ) -> ::core::result::Result<::std::vec::Vec<Self>, #root::sql::ExecError>
6045 {
6046 use #root::sql::FetcherPool as _;
6047 #root::query::QuerySet::<Self>::default()
6048 .fetch(pool)
6049 .await
6050 }
6051
6052 pub async fn find_many<V>(
6075 pks: impl ::core::iter::IntoIterator<Item = V>,
6076 pool: &#root::sql::Pool,
6077 ) -> ::core::result::Result<
6078 ::std::vec::Vec<Self>,
6079 #root::sql::ExecError,
6080 >
6081 where
6082 V: ::core::convert::Into<#root::core::SqlValue>,
6083 {
6084 use #root::sql::FetcherPool as _;
6085 let _values: ::std::vec::Vec<#root::core::SqlValue> =
6086 pks.into_iter().map(::core::convert::Into::into).collect();
6087 if _values.is_empty() {
6088 return ::core::result::Result::Ok(::std::vec::Vec::new());
6089 }
6090 let _key = ::std::format!("{}__in", ::core::stringify!(#pk_ident));
6091 let _batches = #root::sql::__rustango_in_batches::<Self>(pool, _values)?;
6092 let _split = _batches.len() > 1;
6093 let mut _rows = ::std::vec::Vec::new();
6094 for _keys in _batches {
6095 _rows.extend(
6096 #root::query::QuerySet::<Self>::default()
6097 .filter(&_key, #root::core::SqlValue::List(_keys))
6098 .fetch(pool)
6099 .await?,
6100 );
6101 }
6102 if _split {
6103 _rows = #root::sql::__rustango_dedupe_by_pk(_rows);
6104 }
6105 ::core::result::Result::Ok(_rows)
6106 }
6107
6108 pub async fn find(
6123 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6124 pool: &#root::sql::Pool,
6125 ) -> ::core::result::Result<::core::option::Option<Self>, #root::sql::ExecError>
6126 {
6127 use #root::sql::FetcherPool as _;
6128 let _pk_val: #root::core::SqlValue = pk.into();
6129 let mut _rows: ::std::vec::Vec<Self> =
6130 #root::query::QuerySet::<Self>::default()
6131 .filter(::core::stringify!(#pk_ident), _pk_val)
6132 .limit(1)
6133 .fetch(pool)
6134 .await?;
6135 ::core::result::Result::Ok(_rows.into_iter().next())
6136 }
6137
6138 pub async fn find_or_fail(
6155 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6156 pool: &#root::sql::Pool,
6157 ) -> ::core::result::Result<Self, #root::sql::ExecError> {
6158 match Self::find(pk, pool).await? {
6159 ::core::option::Option::Some(_row) => ::core::result::Result::Ok(_row),
6160 ::core::option::Option::None => ::core::result::Result::Err(
6161 #root::sql::ExecError::Driver(
6162 #root::sql::sqlx::Error::RowNotFound,
6163 ),
6164 ),
6165 }
6166 }
6167
6168 pub async fn find_many_or_fail<V>(
6188 pks: impl ::core::iter::IntoIterator<Item = V>,
6189 pool: &#root::sql::Pool,
6190 ) -> ::core::result::Result<
6191 ::std::vec::Vec<Self>,
6192 #root::sql::ExecError,
6193 >
6194 where
6195 V: ::core::convert::Into<#root::core::SqlValue>,
6196 {
6197 let _values: ::std::vec::Vec<#root::core::SqlValue> =
6198 pks.into_iter().map(::core::convert::Into::into).collect();
6199 if _values.is_empty() {
6200 return ::core::result::Result::Ok(::std::vec::Vec::new());
6201 }
6202 use #root::sql::ExistsPool as _;
6203 let mut _seen: ::std::collections::HashSet<
6204 ::std::string::String,
6205 > = ::std::collections::HashSet::new();
6206 let mut _keys: ::std::vec::Vec<#root::core::SqlValue> =
6207 ::std::vec::Vec::new();
6208 for v in &_values {
6209 if _seen.insert(v.to_display_string()) {
6210 _keys.push(::core::clone::Clone::clone(v));
6211 }
6212 }
6213 let _rows = Self::find_many(_values, pool).await?;
6214 let _found: ::std::collections::HashSet<::std::string::String> = _rows
6215 .iter()
6216 .map(|r| r.__rustango_pk_value().to_display_string())
6217 .collect();
6218 for _key in _keys {
6221 if _found.contains(&_key.to_display_string()) {
6222 continue;
6223 }
6224 let _hit = #root::query::QuerySet::<Self>::default()
6225 .filter(::core::stringify!(#pk_ident), _key)
6226 .exists(pool)
6227 .await?;
6228 if !_hit {
6229 return ::core::result::Result::Err(
6230 #root::sql::ExecError::Driver(
6231 #root::sql::sqlx::Error::RowNotFound,
6232 ),
6233 );
6234 }
6235 }
6236 ::core::result::Result::Ok(_rows)
6237 }
6238
6239 pub async fn find_or<F>(
6254 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6255 pool: &#root::sql::Pool,
6256 fallback: F,
6257 ) -> ::core::result::Result<Self, #root::sql::ExecError>
6258 where
6259 F: ::core::ops::FnOnce() -> Self,
6260 {
6261 ::core::result::Result::Ok(
6262 Self::find(pk, pool).await?.unwrap_or_else(fallback),
6263 )
6264 }
6265
6266 pub async fn find_or_new<F>(
6280 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6281 pool: &#root::sql::Pool,
6282 fallback: F,
6283 ) -> ::core::result::Result<(Self, bool), #root::sql::ExecError>
6284 where
6285 F: ::core::ops::FnOnce() -> Self,
6286 {
6287 match Self::find(pk, pool).await? {
6288 ::core::option::Option::Some(_row) => ::core::result::Result::Ok((_row, true)),
6289 ::core::option::Option::None => {
6290 ::core::result::Result::Ok((fallback(), false))
6291 }
6292 }
6293 }
6294
6295 pub async fn find_or_insert<F>(
6319 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6320 pool: &#root::sql::Pool,
6321 fallback: F,
6322 ) -> ::core::result::Result<(Self, bool), #root::sql::ExecError>
6323 where
6324 F: ::core::ops::FnOnce() -> Self,
6325 {
6326 if let ::core::option::Option::Some(_row) = Self::find(pk, pool).await? {
6327 return ::core::result::Result::Ok((_row, true));
6328 }
6329 let mut _new = fallback();
6330 _new.save(pool).await?;
6331 ::core::result::Result::Ok((_new, false))
6332 }
6333
6334 pub async fn first_or<F>(
6341 pool: &#root::sql::Pool,
6342 fallback: F,
6343 ) -> ::core::result::Result<Self, #root::sql::ExecError>
6344 where
6345 F: ::core::ops::FnOnce() -> Self,
6346 {
6347 ::core::result::Result::Ok(
6351 #root::query::QuerySet::<Self>::default()
6352 .first(pool)
6353 .await?
6354 .unwrap_or_else(fallback),
6355 )
6356 }
6357
6358 pub async fn sole(
6368 col: &str,
6369 val: impl ::core::convert::Into<#root::core::SqlValue>,
6370 pool: &#root::sql::Pool,
6371 ) -> ::core::result::Result<Self, #root::sql::ExecError> {
6372 let mut _rows = Self::where_(col, val, pool).await?;
6373 match _rows.len() {
6374 0 => ::core::result::Result::Err(
6375 #root::sql::ExecError::Driver(
6376 #root::sql::sqlx::Error::RowNotFound,
6377 ),
6378 ),
6379 1 => ::core::result::Result::Ok(_rows.remove(0)),
6380 n => ::core::result::Result::Err(
6381 #root::sql::ExecError::MultipleRowsReturned {
6382 op: "sole",
6383 table: <Self as #root::core::Model>::SCHEMA.name,
6384 count: n,
6385 },
6386 ),
6387 }
6388 }
6389 }
6390 } else {
6391 quote!()
6392 };
6393
6394 let tx_emit = |op: TokenStream2| -> TokenStream2 {
6397 if audited_fields.is_some() && primary_key.is_some() {
6398 quote! {
6399 #root::audit::__emit_many_tx(
6400 tx,
6401 &[self.__rustango_audit_entry(#root::audit::AuditOp::#op)],
6402 )
6403 .await?;
6404 }
6405 } else {
6406 quote!()
6407 }
6408 };
6409 let tx_insert_method = {
6410 let self_ref = if fields.has_auto {
6411 quote!(&mut self)
6412 } else {
6413 quote!(&self)
6414 };
6415 let emit = tx_emit(quote!(Create));
6416 let refuse_pkless = if audited_fields.is_some() && primary_key.is_none() {
6417 quote! {
6418 return ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
6419 table: <Self as #root::core::Model>::SCHEMA.table,
6420 });
6421 }
6422 } else {
6423 quote!()
6424 };
6425 quote! {
6426 #[allow(unreachable_code)]
6432 pub async fn insert_tx(
6433 #self_ref,
6434 tx: &mut #root::sql::PoolTx<'_>,
6435 ) -> ::core::result::Result<(), #root::sql::ExecError> {
6436 #refuse_pkless
6437 self.__rustango_insert_exec(&mut *tx).await?;
6438 #emit
6439 ::core::result::Result::Ok(())
6440 }
6441 }
6442 };
6443 let tx_save_method = if let Some((pk_ident, _)) = primary_key {
6444 let emit_create = tx_emit(quote!(Create));
6445 let emit_update = tx_emit(quote!(Update));
6446 let was_unset = if fields.pk_is_auto {
6447 quote!(matches!(self.#pk_ident, #root::sql::Auto::Unset))
6448 } else {
6449 quote!(false)
6450 };
6451 quote! {
6452 pub async fn save_tx(
6458 &mut self,
6459 tx: &mut #root::sql::PoolTx<'_>,
6460 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6461 let _was_unset = #was_unset;
6462 let _n = self.__rustango_save_exec(&mut *tx).await?;
6463 if _n > 0 {
6464 if _was_unset {
6465 #emit_create
6466 } else {
6467 #emit_update
6468 }
6469 }
6470 ::core::result::Result::Ok(_n)
6471 }
6472 }
6473 } else {
6474 quote!()
6475 };
6476 let tx_delete_method = if primary_key.is_some() {
6477 let emit = tx_emit(quote!(Delete));
6478 quote! {
6479 pub async fn delete_tx(
6485 &self,
6486 tx: &mut #root::sql::PoolTx<'_>,
6487 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6488 let _n = self.__rustango_delete_exec(&mut *tx).await?;
6489 if _n > 0 {
6490 #emit
6491 }
6492 ::core::result::Result::Ok(_n)
6493 }
6494 }
6495 } else {
6496 quote!()
6497 };
6498
6499 let refuse = |method: &str| -> TokenStream2 {
6503 let reason = format!("`{method}` cannot audit; use the `&Pool` method");
6504 quote!(#root::sql::__macro_internals::refuse_audited::<Self>(#reason)?;)
6505 };
6506 let refuse_save = refuse("save_on");
6507 let save_method = if let Some((pk_ident, pk_column)) = primary_key {
6508 let pk_column_lit = pk_column.as_str();
6509 let assignments = &fields.update_assignments;
6510 let dispatch_unset = if fields.pk_is_auto {
6511 quote! {
6512 if matches!(self.#pk_ident, #root::sql::Auto::Unset) {
6513 return self.__rustango_insert_exec(_executor).await.map(|()| 1u64);
6514 }
6515 }
6516 } else {
6517 quote!()
6518 };
6519 let write = restore_auto_now_unless(
6520 &fields.auto_now_idents,
6521 &save_ret,
6522 &save_keep,
6523 "e! {
6524 let _query = #root::core::UpdateQuery::new(
6525 <Self as #root::core::Model>::SCHEMA,
6526 ::std::vec![ #( #assignments ),* ],
6527 #root::core::WhereExpr::Predicate(#root::core::Filter::new(
6528 #pk_column_lit,
6529 #root::core::Op::Eq,
6530 ::core::convert::Into::<#root::core::SqlValue>::into(
6531 ::core::clone::Clone::clone(&self.#pk_ident)
6532 ),
6533 )),
6534 );
6535 #root::sql::__macro_internals::update_on(_executor, &_query).await
6536 },
6537 );
6538 let upsert = if fields.pk_is_auto {
6539 let upsert_cols = &fields.upsert_update_columns;
6540 let upsert_pushes = &fields.insert_pushes;
6541 let (gen_idents, gen_cols): (Vec<_>, Vec<_>) =
6542 fields.uuid_v7_fields.iter().cloned().unzip();
6543 let upsert_returning = &fields.returning_cols;
6544 let auto_now_idents = &fields.auto_now_idents;
6545 let upsert_target_columns: Vec<String> =
6548 upsert_unique.map_or_else(|| vec![pk_column.clone()], <[String]>::to_vec);
6549 let upsert_target_lits = upsert_target_columns
6550 .iter()
6551 .map(String::as_str)
6552 .collect::<Vec<_>>();
6553 let conflict_clause = if fields.upsert_update_columns.is_empty() {
6554 quote!(#root::core::ConflictClause::DoNothing)
6555 } else {
6556 quote!(#root::core::ConflictClause::DoUpdate {
6557 target: ::std::vec![ #( #upsert_target_lits ),* ],
6558 update_columns: ::std::vec![ #( #upsert_cols ),* ],
6559 })
6560 };
6561 let unit_ret = quote!(::core::result::Result<(), #root::sql::ExecError>);
6562 let unit_keep = quote!(_saved.is_ok());
6563 let exec_body = restore_auto_now_unless(
6564 auto_now_idents,
6565 &unit_ret,
6566 &unit_keep,
6567 "e! {
6568 let _query = self.__rustango_upsert_query();
6569 let _row = #root::sql::__macro_internals::insert_returning_on(
6570 _executor, &_query,
6571 )
6572 .await?;
6573 #root::sql::apply_auto_pk(_row, self)
6574 },
6575 );
6576 let pool_body = if audited_fields.is_some() {
6577 restore_auto_now_unless(
6578 auto_now_idents,
6579 &unit_ret,
6580 &unit_keep,
6581 "e! {
6582 let _query = self.__rustango_upsert_query();
6583 #root::audit::insert_one_with_audit(pool, &_query, self, |_m: &Self, _op| {
6584 _m.__rustango_audit_entry(_op)
6585 })
6586 .await
6587 },
6588 )
6589 } else {
6590 quote!(self.__rustango_upsert_exec(pool).await)
6591 };
6592 let refuse_upsert = refuse("upsert_on");
6593 quote! {
6594 pub async fn upsert(
6602 &mut self,
6603 pool: &#root::sql::Pool,
6604 ) -> ::core::result::Result<(), #root::sql::ExecError> {
6605 #pool_body
6606 }
6607
6608 pub async fn upsert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6614 &mut self,
6615 _executor: _E,
6616 ) -> ::core::result::Result<(), #root::sql::ExecError> {
6617 #refuse_upsert
6618 self.__rustango_upsert_exec(_executor).await
6619 }
6620
6621 #[doc(hidden)]
6622 pub async fn __rustango_upsert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6623 &mut self,
6624 _executor: _E,
6625 ) -> ::core::result::Result<(), #root::sql::ExecError> {
6626 #exec_body
6627 }
6628
6629 #[doc(hidden)]
6631 pub fn __rustango_upsert_query(&mut self) -> #root::core::InsertQuery {
6632 let mut _columns: ::std::vec::Vec<&'static str> = ::std::vec::Vec::new();
6633 let mut _values: ::std::vec::Vec<#root::core::SqlValue> =
6634 ::std::vec::Vec::new();
6635 #[allow(unused_mut)]
6636 let mut _generated: ::std::vec::Vec<&'static str> = ::std::vec::Vec::new();
6637 #(
6638 if matches!(&self.#gen_idents, #root::sql::Auto::Unset) {
6639 _generated.push(#gen_cols);
6640 }
6641 )*
6642 #( self.#auto_now_idents = #root::sql::Auto::Set(#root::__chrono::Utc::now()); )*
6643 #( #upsert_pushes )*
6644 #root::core::InsertQuery::new(
6645 <Self as #root::core::Model>::SCHEMA,
6646 _columns,
6647 _values,
6648 )
6649 .returning(::std::vec![ #( #upsert_returning ),* ])
6650 .on_conflict(#conflict_clause)
6651 .generated(_generated)
6652 }
6653 }
6654 } else {
6655 quote!()
6656 };
6657 Some(quote! {
6658 pub async fn save_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6665 &mut self,
6666 _executor: _E,
6667 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6668 #refuse_save
6669 self.__rustango_save_exec(_executor).await
6670 }
6671
6672 #[doc(hidden)]
6673 pub async fn __rustango_save_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6674 &mut self,
6675 _executor: _E,
6676 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6677 #dispatch_unset
6678 #write
6679 }
6680
6681 #[deprecated(since = "0.62.0", note = "use `save`")]
6686 pub async fn save_pool(
6687 &mut self,
6688 pool: &#root::sql::Pool,
6689 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6690 self.save(pool).await
6691 }
6692
6693 #upsert
6694 })
6695 } else {
6696 None
6697 };
6698
6699 let pk_methods = primary_key.map(|(pk_ident, pk_column)| {
6700 let pk_column_lit = pk_column.as_str();
6701 let refuse_delete = refuse("delete_on");
6702 let soft_delete_methods = if let Some(col) = fields.soft_delete_column.as_deref() {
6709 let col_lit = col;
6710 let sd_field_ident = fields
6711 .soft_delete_field_ident
6712 .clone()
6713 .expect("soft_delete_column without ident");
6714 let refuse_soft_delete = refuse("soft_delete_on");
6715 let refuse_restore = refuse("restore_on");
6716 quote! {
6717 pub async fn soft_delete_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6724 &self,
6725 _executor: _E,
6726 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6727 #refuse_soft_delete
6728 let _query = #root::soft_delete::__mark_query(
6729 <Self as #root::core::Model>::SCHEMA,
6730 #col_lit,
6731 #pk_column_lit,
6732 ::core::convert::Into::<#root::core::SqlValue>::into(
6733 ::core::clone::Clone::clone(&self.#pk_ident)
6734 ),
6735 ::core::option::Option::Some(#root::__chrono::Utc::now()),
6736 );
6737 #root::sql::__macro_internals::update_on(_executor, &_query).await
6738 }
6739
6740 pub async fn restore_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6746 &self,
6747 _executor: _E,
6748 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6749 #refuse_restore
6750 let _query = #root::soft_delete::__mark_query(
6751 <Self as #root::core::Model>::SCHEMA,
6752 #col_lit,
6753 #pk_column_lit,
6754 ::core::convert::Into::<#root::core::SqlValue>::into(
6755 ::core::clone::Clone::clone(&self.#pk_ident)
6756 ),
6757 ::core::option::Option::None,
6758 );
6759 #root::sql::__macro_internals::update_on(_executor, &_query).await
6760 }
6761
6762 pub async fn soft_delete(
6776 &self,
6777 pool: &#root::sql::Pool,
6778 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6779 let _now = #root::__chrono::Utc::now();
6780 let _query = #root::soft_delete::__mark_query(
6781 <Self as #root::core::Model>::SCHEMA,
6782 #col_lit,
6783 #pk_column_lit,
6784 ::core::convert::Into::<#root::core::SqlValue>::into(
6785 ::core::clone::Clone::clone(&self.#pk_ident)
6786 ),
6787 ::core::option::Option::Some(_now),
6788 );
6789 #pool_softdelete_run
6790 }
6791
6792 pub async fn restore(
6801 &self,
6802 pool: &#root::sql::Pool,
6803 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6804 let _query = #root::soft_delete::__mark_query(
6805 <Self as #root::core::Model>::SCHEMA,
6806 #col_lit,
6807 #pk_column_lit,
6808 ::core::convert::Into::<#root::core::SqlValue>::into(
6809 ::core::clone::Clone::clone(&self.#pk_ident)
6810 ),
6811 ::core::option::Option::None,
6812 );
6813 #pool_restore_run
6814 }
6815
6816 pub async fn force_delete(
6830 &self,
6831 pool: &#root::sql::Pool,
6832 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6833 Self::delete(self, pool).await
6834 }
6835
6836 pub fn trashed(&self) -> bool {
6845 ::core::option::Option::is_some(&self.#sd_field_ident)
6846 }
6847
6848 pub async fn active(
6861 pool: &#root::sql::Pool,
6862 ) -> ::core::result::Result<
6863 ::std::vec::Vec<Self>,
6864 #root::sql::ExecError,
6865 > {
6866 use #root::sql::FetcherPool as _;
6867 #root::query::QuerySet::<Self>::default()
6868 .active()
6869 .fetch(pool)
6870 .await
6871 }
6872
6873 pub async fn only_trashed(
6885 pool: &#root::sql::Pool,
6886 ) -> ::core::result::Result<
6887 ::std::vec::Vec<Self>,
6888 #root::sql::ExecError,
6889 > {
6890 use #root::sql::FetcherPool as _;
6891 #root::query::QuerySet::<Self>::default()
6892 .only_trashed()
6893 .fetch(pool)
6894 .await
6895 }
6896
6897 pub async fn with_trashed(
6913 pool: &#root::sql::Pool,
6914 ) -> ::core::result::Result<
6915 ::std::vec::Vec<Self>,
6916 #root::sql::ExecError,
6917 > {
6918 use #root::sql::FetcherPool as _;
6919 #root::query::QuerySet::<Self>::default()
6920 .with_trashed()
6921 .fetch(pool)
6922 .await
6923 }
6924
6925 }
6926 } else {
6927 quote!()
6928 };
6929 quote! {
6930
6931
6932 pub async fn delete_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6938 &self,
6939 _executor: _E,
6940 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6941 #refuse_delete
6942 self.__rustango_delete_exec(_executor).await
6943 }
6944
6945 #[doc(hidden)]
6946 pub async fn __rustango_delete_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6947 &self,
6948 _executor: _E,
6949 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6950 let _query = #root::core::DeleteQuery::by_pk(
6951 <Self as #root::core::Model>::SCHEMA,
6952 #pk_column_lit,
6953 ::core::convert::Into::<#root::core::SqlValue>::into(
6954 ::core::clone::Clone::clone(&self.#pk_ident)
6955 ),
6956 );
6957 #root::sql::__macro_internals::delete_on(_executor, &_query).await
6958 }
6959
6960 #[deprecated(since = "0.62.0", note = "use `delete`")]
6965 pub async fn delete_pool(
6966 &self,
6967 pool: &#root::sql::Pool,
6968 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6969 self.delete(pool).await
6970 }
6971
6972
6973 #pool_delete_method
6974 #pool_insert_method
6975 #audit_entry_method
6976 #pool_save_method
6977 #refresh_replicate_methods
6978 #tx_delete_method
6979 #tx_insert_method
6980 #tx_save_method
6981 #soft_delete_methods
6982
6983 pub fn is(&self, other: &Self) -> bool {
6992 self.#pk_ident == other.#pk_ident
6993 }
6994
6995 pub fn is_not(&self, other: &Self) -> bool {
6998 self.#pk_ident != other.#pk_ident
6999 }
7000
7001 #[must_use]
7009 pub fn get_key(&self) -> #root::core::SqlValue {
7010 ::core::convert::Into::into(::core::clone::Clone::clone(&self.#pk_ident))
7011 }
7012
7013 }
7014 });
7015
7016 let refuse_insert = refuse("insert_on");
7017 let audited_insert = audited_fields.is_some() && primary_key.is_some();
7020 let insert_pool_body = if audited_fields.is_some() {
7021 quote! {
7022 ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
7023 table: <Self as #root::core::Model>::SCHEMA.table,
7024 })
7025 }
7026 } else {
7027 quote!(self.__rustango_insert_exec(pool).await)
7028 };
7029 let plain_insert_auto = if audited_insert {
7030 quote!()
7031 } else {
7032 quote! {
7033 pub async fn insert(
7039 &mut self,
7040 pool: &#root::sql::Pool,
7041 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7042 #insert_pool_body
7043 }
7044 }
7045 };
7046 let plain_insert_plain = if audited_insert {
7047 quote!()
7048 } else {
7049 quote! {
7050 pub async fn insert(
7055 &self,
7056 pool: &#root::sql::Pool,
7057 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7058 #insert_pool_body
7059 }
7060 }
7061 };
7062 let insert_method = if fields.has_auto {
7063 let pushes = &fields.insert_pushes;
7064 let returning_cols = &fields.returning_cols;
7065 quote! {
7066 #plain_insert_auto
7067 pub async fn insert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7073 &mut self,
7074 _executor: _E,
7075 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7076 #refuse_insert
7077 self.__rustango_insert_exec(_executor).await
7078 }
7079
7080 #[doc(hidden)]
7081 pub async fn __rustango_insert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7082 &mut self,
7083 _executor: _E,
7084 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7085 let _query = self.__rustango_insert_query();
7086 if _query.returning.is_empty() {
7088 return #root::sql::__macro_internals::insert_on(_executor, &_query).await;
7089 }
7090 let _row = #root::sql::__macro_internals::insert_returning_on(
7091 _executor, &_query,
7092 )
7093 .await?;
7094 #root::sql::apply_auto_pk(_row, self)
7095 }
7096
7097 #[doc(hidden)]
7099 pub fn __rustango_insert_query(&mut self) -> #root::core::InsertQuery {
7100 let mut _columns: ::std::vec::Vec<&'static str> = ::std::vec::Vec::new();
7101 let mut _values: ::std::vec::Vec<#root::core::SqlValue> = ::std::vec::Vec::new();
7102 #( #pushes )*
7103 #root::core::InsertQuery::new(
7104 <Self as #root::core::Model>::SCHEMA,
7105 _columns,
7106 _values,
7107 )
7108 .returning(::std::vec![ #( #returning_cols ),* ])
7109 }
7110
7111 #[deprecated(since = "0.62.0", note = "use `insert`")]
7116 pub async fn insert_pool(
7117 &mut self,
7118 pool: &#root::sql::Pool,
7119 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7120 self.insert(pool).await
7121 }
7122 }
7123 } else {
7124 let insert_columns = &fields.insert_columns;
7125 let insert_values = &fields.insert_values;
7126 let pool_self = if audited_fields.is_some() {
7128 quote!(&mut self)
7129 } else {
7130 quote!(&self)
7131 };
7132 quote! {
7133 #plain_insert_plain
7134 pub async fn insert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7140 &self,
7141 _executor: _E,
7142 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7143 #refuse_insert
7144 self.__rustango_insert_exec(_executor).await
7145 }
7146
7147 #[doc(hidden)]
7148 pub async fn __rustango_insert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7149 &self,
7150 _executor: _E,
7151 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7152 #root::sql::__macro_internals::insert_on(_executor, &self.__rustango_insert_query())
7153 .await
7154 }
7155
7156 #[doc(hidden)]
7158 pub fn __rustango_insert_query(&self) -> #root::core::InsertQuery {
7159 #root::core::InsertQuery::new(
7160 <Self as #root::core::Model>::SCHEMA,
7161 ::std::vec![ #( #insert_columns ),* ],
7162 ::std::vec![ #( #insert_values ),* ],
7163 )
7164 }
7165
7166 #[deprecated(since = "0.62.0", note = "use `insert`")]
7171 pub async fn insert_pool(
7172 #pool_self,
7173 pool: &#root::sql::Pool,
7174 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7175 self.insert(pool).await
7176 }
7177 }
7178 };
7179
7180 let bulk_pool_body = if audited_fields.is_some() {
7183 quote! {
7184 let mut _tx = #root::sql::write_transaction(pool).await?;
7185 Self::__rustango_bulk_insert_exec(rows, &mut _tx).await?;
7186 if rows.iter().any(|_row| _row.__rustango_audit_pk().is_empty()) {
7188 return ::core::result::Result::Err(#root::sql::ExecError::AuditUnsupported {
7189 table: <Self as #root::core::Model>::SCHEMA.table,
7190 reason: "MySQL has no RETURNING to read the written rows",
7191 });
7192 }
7193 let _entries: ::std::vec::Vec<#root::audit::PendingEntry> = rows
7194 .iter()
7195 .map(|_row| _row.__rustango_audit_entry(#root::audit::AuditOp::Create))
7196 .collect();
7197 #root::audit::__emit_many_tx(&mut _tx, &_entries).await?;
7198 _tx.commit().await?;
7199 ::core::result::Result::Ok(())
7200 }
7201 } else {
7202 quote!(Self::__rustango_bulk_insert_exec(rows, pool).await)
7203 };
7204 let refuse_bulk = refuse("bulk_insert_on");
7205 let bulk_insert_method = if fields.has_auto {
7206 let cols_no_auto = &fields.bulk_columns_no_auto;
7207 let cols_all = &fields.bulk_columns_all;
7208 let pushes_no_auto = &fields.bulk_pushes_no_auto;
7209 let pushes_all = &fields.bulk_pushes_all;
7210 let returning_cols = &fields.returning_cols;
7211 let uniformity = &fields.bulk_auto_uniformity;
7212 let fill_in_place = &fields.bulk_fill_in_place;
7213 let first_auto_ident = fields
7215 .first_db_auto
7216 .as_ref()
7217 .map(|(ident, _)| ident)
7218 .or(fields.first_auto_ident.as_ref())
7219 .expect("has_auto implies first_auto_ident is Some");
7220 quote! {
7221 pub async fn bulk_insert(
7231 rows: &mut [Self],
7232 pool: &#root::sql::Pool,
7233 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7234 #bulk_pool_body
7235 }
7236
7237 pub async fn bulk_insert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7243 rows: &mut [Self],
7244 _executor: _E,
7245 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7246 #refuse_bulk
7247 Self::__rustango_bulk_insert_exec(rows, _executor).await
7248 }
7249
7250 #[doc(hidden)]
7251 pub async fn __rustango_bulk_insert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7252 rows: &mut [Self],
7253 _executor: _E,
7254 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7255 if rows.is_empty() {
7256 return ::core::result::Result::Ok(());
7257 }
7258 let _first_unset = matches!(
7259 rows[0].#first_auto_ident,
7260 #root::sql::Auto::Unset
7261 );
7262 #( #uniformity )*
7263 for _row in rows.iter_mut() {
7264 #( #fill_in_place )*
7265 }
7266 let mut _all_rows: ::std::vec::Vec<
7267 ::std::vec::Vec<#root::core::SqlValue>,
7268 > = ::std::vec::Vec::with_capacity(rows.len());
7269 let _columns: ::std::vec::Vec<&'static str> = if _first_unset {
7270 for _row in rows.iter() {
7271 let mut _row_vals: ::std::vec::Vec<#root::core::SqlValue> =
7272 ::std::vec::Vec::new();
7273 #( #pushes_no_auto )*
7274 _all_rows.push(_row_vals);
7275 }
7276 ::std::vec![ #( #cols_no_auto ),* ]
7277 } else {
7278 for _row in rows.iter() {
7279 let mut _row_vals: ::std::vec::Vec<#root::core::SqlValue> =
7280 ::std::vec::Vec::new();
7281 #( #pushes_all )*
7282 _all_rows.push(_row_vals);
7283 }
7284 ::std::vec![ #( #cols_all ),* ]
7285 };
7286 let _query = #root::core::BulkInsertQuery::new(
7287 <Self as #root::core::Model>::SCHEMA,
7288 _columns,
7289 _all_rows,
7290 )
7291 .returning(::std::vec![ #( #returning_cols ),* ]);
7292 let _returned =
7293 #root::sql::__macro_internals::bulk_insert_on(_executor, &_query).await?;
7294 if _returned.is_empty() {
7295 return ::core::result::Result::Ok(());
7296 }
7297 if _returned.len() != rows.len() {
7298 return ::core::result::Result::Err(#root::sql::ExecError::Sql(
7299 #root::sql::SqlError::BulkInsertReturningMismatch {
7300 expected: rows.len(),
7301 actual: _returned.len(),
7302 },
7303 ));
7304 }
7305 for (_row, _row_mut) in _returned.into_iter().zip(rows.iter_mut()) {
7306 #root::sql::apply_auto_pk(_row, _row_mut)?;
7307 }
7308 ::core::result::Result::Ok(())
7309 }
7310 }
7311 } else {
7312 let cols_all = &fields.bulk_columns_all;
7313 let pushes_all = &fields.bulk_pushes_all;
7314 quote! {
7315 pub async fn bulk_insert(
7322 rows: &[Self],
7323 pool: &#root::sql::Pool,
7324 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7325 #bulk_pool_body
7326 }
7327
7328 pub async fn bulk_insert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7334 rows: &[Self],
7335 _executor: _E,
7336 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7337 #refuse_bulk
7338 Self::__rustango_bulk_insert_exec(rows, _executor).await
7339 }
7340
7341 #[doc(hidden)]
7342 pub async fn __rustango_bulk_insert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7343 rows: &[Self],
7344 _executor: _E,
7345 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7346 if rows.is_empty() {
7347 return ::core::result::Result::Ok(());
7348 }
7349 let mut _all_rows: ::std::vec::Vec<
7350 ::std::vec::Vec<#root::core::SqlValue>,
7351 > = ::std::vec::Vec::with_capacity(rows.len());
7352 for _row in rows.iter() {
7353 let mut _row_vals: ::std::vec::Vec<#root::core::SqlValue> =
7354 ::std::vec::Vec::new();
7355 #( #pushes_all )*
7356 _all_rows.push(_row_vals);
7357 }
7358 let _query = #root::core::BulkInsertQuery::new(
7359 <Self as #root::core::Model>::SCHEMA,
7360 ::std::vec![ #( #cols_all ),* ],
7361 _all_rows,
7362 );
7363 #root::sql::__macro_internals::bulk_insert_on(_executor, &_query)
7364 .await
7365 .map(drop)
7366 }
7367 }
7368 };
7369
7370 let run_conflict_bulk_insert = match (audited_fields.is_some(), primary_key.is_some()) {
7380 (false, _) => quote!(#root::sql::bulk_insert_pool(pool, &_query).await),
7381 (true, true) => quote! {
7382 #root::audit::bulk_insert_with_audit::<Self>(pool, &_query, |_r: &Self, _op| {
7383 _r.__rustango_audit_entry(_op)
7384 })
7385 .await
7386 },
7387 (true, false) => quote! {
7388 ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
7389 table: <Self as #root::core::Model>::SCHEMA.table,
7390 })
7391 },
7392 };
7393 let bulk_upsert_pool_method = {
7394 let (gen_idents, gen_cols): (Vec<_>, Vec<_>) =
7395 fields.uuid_v7_fields.iter().cloned().unzip();
7396 let (upsert_cols, upsert_pushes): (Vec<_>, Vec<_>) = if fields.has_auto {
7399 (
7400 fields.bulk_columns_no_auto.clone(),
7401 fields.bulk_pushes_no_auto.clone(),
7402 )
7403 } else {
7404 (
7405 fields.bulk_columns_all.clone(),
7406 fields.bulk_pushes_all.clone(),
7407 )
7408 };
7409 quote! {
7410 pub async fn bulk_upsert_pool(
7439 rows: &[Self],
7440 target: &[&'static str],
7441 update_cols: &[&'static str],
7442 pool: &#root::sql::Pool,
7443 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7444 if rows.is_empty() {
7445 return ::core::result::Result::Ok(());
7446 }
7447 let mut _all_rows: ::std::vec::Vec<
7448 ::std::vec::Vec<#root::core::SqlValue>,
7449 > = ::std::vec::Vec::with_capacity(rows.len());
7450 for _row in rows.iter() {
7451 let mut _row_vals: ::std::vec::Vec<#root::core::SqlValue> =
7452 ::std::vec::Vec::new();
7453 #( #upsert_pushes )*
7454 _all_rows.push(_row_vals);
7455 }
7456 #[allow(unused_mut)]
7457 let mut _generated: ::std::vec::Vec<&'static str> = ::std::vec::Vec::new();
7458 #(
7459 if rows.iter().all(|_r| matches!(&_r.#gen_idents, #root::sql::Auto::Unset)) {
7460 _generated.push(#gen_cols);
7461 }
7462 )*
7463 let _query = #root::core::BulkInsertQuery::new(
7464 <Self as #root::core::Model>::SCHEMA,
7465 ::std::vec![ #( #upsert_cols ),* ],
7466 _all_rows,
7467 )
7468 .on_conflict_do_update(target, update_cols)
7469 .generated(_generated);
7470 #run_conflict_bulk_insert
7471 }
7472
7473 pub async fn bulk_insert_or_ignore_pool(
7483 rows: &[Self],
7484 pool: &#root::sql::Pool,
7485 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7486 if rows.is_empty() {
7487 return ::core::result::Result::Ok(());
7488 }
7489 let mut _all_rows: ::std::vec::Vec<
7490 ::std::vec::Vec<#root::core::SqlValue>,
7491 > = ::std::vec::Vec::with_capacity(rows.len());
7492 for _row in rows.iter() {
7493 let mut _row_vals: ::std::vec::Vec<#root::core::SqlValue> =
7494 ::std::vec::Vec::new();
7495 #( #upsert_pushes )*
7496 _all_rows.push(_row_vals);
7497 }
7498 let _query = #root::core::BulkInsertQuery::new(
7499 <Self as #root::core::Model>::SCHEMA,
7500 ::std::vec![ #( #upsert_cols ),* ],
7501 _all_rows,
7502 )
7503 .on_conflict_do_nothing();
7504 #run_conflict_bulk_insert
7505 }
7506 }
7507 };
7508
7509 let bulk_update_rows_run = if audited_fields.is_some() {
7518 quote!(#root::audit::bulk_update_with_audit::<Self>(pool, &_query, |_r: &Self| {
7519 _r.__rustango_audit_entry(#root::audit::AuditOp::Update)
7520 })
7521 .await)
7522 } else {
7523 quote!(#root::sql::bulk_update_pool(pool, &_query).await)
7524 };
7525 let bulk_update_method = match &fields.primary_key {
7526 None => quote! {},
7527 Some((pk_ident, pk_col)) => {
7528 let mut col_arms: Vec<TokenStream2> = Vec::new();
7532 let mut val_arms: Vec<TokenStream2> = Vec::new();
7533 for entry in &fields.column_entries {
7534 if &entry.column == pk_col {
7535 continue;
7536 }
7537 let col = &entry.column;
7538 let ident = &entry.ident;
7539 col_arms.push(quote! { #col => #col, });
7540 val_arms.push(quote! {
7541 #col => _row_vals.push(
7542 ::core::convert::Into::<#root::core::SqlValue>::into(
7543 ::core::clone::Clone::clone(&_o.#ident)
7544 )
7545 ),
7546 });
7547 }
7548 quote! {
7549 pub async fn bulk_update(
7575 objs: &[Self],
7576 fields: &[&str],
7577 pool: &#root::sql::Pool,
7578 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
7579 if objs.is_empty() || fields.is_empty() {
7580 return ::core::result::Result::Ok(0);
7581 }
7582 let _model_name = <Self as #root::core::Model>::SCHEMA.name;
7583 let mut _update_columns: ::std::vec::Vec<&'static str> =
7584 ::std::vec::Vec::with_capacity(fields.len());
7585 for &_f in fields {
7586 let _col: &'static str = match _f {
7587 #pk_col => {
7588 return ::core::result::Result::Err(
7589 ::core::convert::Into::into(
7590 #root::core::QueryError::BulkUpdatePrimaryKey {
7591 model: _model_name,
7592 field: ::std::string::ToString::to_string(_f),
7593 }
7594 )
7595 );
7596 }
7597 #( #col_arms )*
7598 _ => {
7599 return ::core::result::Result::Err(
7600 ::core::convert::Into::into(
7601 #root::core::QueryError::UnknownField {
7602 model: _model_name,
7603 field: ::std::string::ToString::to_string(_f),
7604 }
7605 )
7606 );
7607 }
7608 };
7609 #[allow(unreachable_code)]
7618 _update_columns.push(_col);
7619 }
7620 let mut _rows: ::std::vec::Vec<
7621 ::std::vec::Vec<#root::core::SqlValue>,
7622 > = ::std::vec::Vec::with_capacity(objs.len());
7623 for _o in objs.iter() {
7624 let mut _row_vals: ::std::vec::Vec<#root::core::SqlValue> =
7625 ::std::vec::Vec::with_capacity(fields.len() + 1);
7626 _row_vals.push(
7628 ::core::convert::Into::<#root::core::SqlValue>::into(
7629 ::core::clone::Clone::clone(&_o.#pk_ident)
7630 )
7631 );
7632 for &_f in fields {
7633 match _f {
7634 #( #val_arms )*
7635 _ => {}
7638 }
7639 }
7640 _rows.push(_row_vals);
7641 }
7642 let _query = #root::core::BulkUpdateQuery::new(
7643 <Self as #root::core::Model>::SCHEMA,
7644 _update_columns,
7645 _rows,
7646 );
7647 #bulk_update_rows_run
7648 }
7649 }
7650 }
7651 };
7652
7653 let pk_value_helper = primary_key.map(|(pk_ident, _)| {
7654 quote! {
7655 #[doc(hidden)]
7660 pub fn __rustango_pk_value(&self) -> #root::core::SqlValue {
7661 ::core::convert::Into::<#root::core::SqlValue>::into(
7662 ::core::clone::Clone::clone(&self.#pk_ident)
7663 )
7664 }
7665 }
7666 });
7667
7668 let has_pk_value_impl = primary_key.map(|(pk_ident, _)| {
7669 let column_arms = fields.column_entries.iter().map(|entry| {
7670 let col = &entry.column;
7671 let ident = &entry.ident;
7672 quote! {
7673 #col => ::core::option::Option::Some(
7674 ::core::convert::Into::<#root::core::SqlValue>::into(
7675 ::core::clone::Clone::clone(&self.#ident)
7676 )
7677 ),
7678 }
7679 });
7680 quote! {
7681 impl #root::sql::HasPkValue for #struct_name {
7682 fn __rustango_pk_value_impl(&self) -> #root::core::SqlValue {
7683 ::core::convert::Into::<#root::core::SqlValue>::into(
7684 ::core::clone::Clone::clone(&self.#pk_ident)
7685 )
7686 }
7687 fn __rustango_column_value(
7688 &self,
7689 column: &str,
7690 ) -> ::core::option::Option<#root::core::SqlValue> {
7691 match column {
7692 #( #column_arms )*
7693 _ => ::core::option::Option::None,
7694 }
7695 }
7696 }
7697 }
7698 });
7699
7700 let fk_pk_access_impl = fk_pk_access_impl_tokens(struct_name, &fields.fk_relations);
7701
7702 let assign_auto_pk_pool_impl = {
7708 let auto_assigns = &fields.auto_assigns;
7709 let auto_assigns_sqlite: Vec<TokenStream2> = fields
7715 .auto_field_idents
7716 .iter()
7717 .map(|(ident, column, uuid)| {
7718 if *uuid {
7720 return quote! {
7721 self.#ident = #root::sql::Auto::Set(#root::sql::try_get_flat_sqlite(
7722 _returning_row, #column
7723 )?);
7724 };
7725 }
7726 quote! {
7727 self.#ident = #root::sql::try_get_returning_sqlite(
7728 _returning_row, #column
7729 )?;
7730 }
7731 })
7732 .collect();
7733 let generated_assigns: Vec<TokenStream2> = fields
7737 .generated_field_idents
7738 .iter()
7739 .map(|(ident, column)| {
7740 quote! {
7741 self.#ident = #root::sql::try_get_returning(_returning_row, #column)?;
7742 }
7743 })
7744 .collect();
7745 let generated_assigns_sqlite: Vec<TokenStream2> = fields
7746 .generated_field_idents
7747 .iter()
7748 .map(|(ident, column)| {
7749 quote! {
7750 self.#ident = #root::sql::try_get_returning_sqlite(
7751 _returning_row, #column
7752 )?;
7753 }
7754 })
7755 .collect();
7756 let mysql_body = if let Some((first, value_ty)) = fields.first_db_auto.as_ref() {
7757 quote! {
7775 let _converted = <#value_ty as #root::sql::MysqlAutoIdSet>
7776 ::rustango_from_mysql_auto_id(_id)?;
7777 self.#first = #root::sql::Auto::Set(_converted);
7778 ::core::result::Result::Ok(())
7779 }
7780 } else {
7781 quote! {
7782 let _ = _id;
7783 ::core::result::Result::Ok(())
7784 }
7785 };
7786 quote! {
7787 impl #root::sql::AssignAutoPkPool for #struct_name {
7788 fn __rustango_assign_from_pg_row(
7789 &mut self,
7790 _returning_row: &#root::sql::PgReturningRow,
7791 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7792 #( #auto_assigns )*
7793 #( #generated_assigns )*
7794 ::core::result::Result::Ok(())
7795 }
7796 fn __rustango_assign_from_mysql_id(
7797 &mut self,
7798 _id: i64,
7799 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7800 #mysql_body
7801 }
7802 fn __rustango_assign_from_sqlite_row(
7803 &mut self,
7804 _returning_row: &#root::sql::SqliteReturningRow,
7805 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7806 #( #auto_assigns_sqlite )*
7807 #( #generated_assigns_sqlite )*
7808 ::core::result::Result::Ok(())
7809 }
7810 }
7811 }
7812 };
7813
7814 let from_aliased_row_inits = &fields.from_aliased_row_inits;
7815 let aliased_row_helper = quote! {
7816 #[doc(hidden)]
7822 #[cfg(feature = "postgres")]
7823 pub fn __rustango_from_aliased_row(
7824 row: &#root::sql::sqlx::postgres::PgRow,
7825 prefix: &str,
7826 ) -> ::core::result::Result<Self, #root::sql::sqlx::Error> {
7827 ::core::result::Result::Ok(Self {
7828 #( #from_aliased_row_inits ),*
7829 })
7830 }
7831 };
7832 let from_aliased_row_inits_my = &fields.from_aliased_row_inits_my;
7835 let aliased_row_helper_my = quote! {
7836 #root::__impl_my_aliased_row_decoder!(#struct_name, |row, prefix| {
7837 #( #from_aliased_row_inits_my ),*
7838 });
7839 };
7840
7841 let aliased_row_helper_sqlite = quote! {
7844 #root::__impl_sqlite_aliased_row_decoder!(#struct_name, |row, prefix| {
7845 #( #from_aliased_row_inits ),*
7846 });
7847 };
7848
7849 let load_related_impl = load_related_impl_tokens(struct_name, &fields.fk_relations);
7850 let load_related_impl_my = load_related_impl_my_tokens(struct_name, &fields.fk_relations);
7851 let load_related_impl_sqlite =
7852 load_related_impl_sqlite_tokens(struct_name, &fields.fk_relations);
7853
7854 let extra_manager_fns: Vec<TokenStream2> = manager_fns
7860 .iter()
7861 .map(|fn_ident| {
7862 let model_name_str = struct_name.to_string();
7863 let fn_name_str = fn_ident.to_string();
7864 let doc = format!(
7865 "Custom-named QuerySet accessor for [`{model_name_str}`]. \
7866 Generated by `#[rustango(manager_fn = \"{fn_name_str}\")]` — \
7867 equivalent to `Self::objects()`. Chains with any \
7868 `impl ... for QuerySet<{model_name_str}> {{ ... }}` \
7869 extension methods."
7870 );
7871 quote! {
7872 #[doc = #doc]
7873 #[must_use]
7874 pub fn #fn_ident() -> #root::query::QuerySet<#struct_name> {
7875 #root::query::QuerySet::new()
7876 }
7877 }
7878 })
7879 .collect();
7880
7881 quote! {
7882 impl #struct_name {
7883 #[must_use]
7885 pub fn objects() -> #root::query::QuerySet<#struct_name> {
7886 #root::query::QuerySet::new()
7887 }
7888
7889 #[must_use]
7902 pub fn query() -> #root::query::QuerySet<#struct_name> {
7903 #root::query::QuerySet::new()
7904 }
7905
7906 #( #extra_manager_fns )*
7907
7908 #insert_method
7909
7910 #bulk_insert_method
7911
7912 #bulk_upsert_pool_method
7913
7914 #bulk_update_method
7915
7916 #save_method
7917
7918 #pk_methods
7919
7920 #pk_value_helper
7921
7922 #aliased_row_helper
7923
7924 #column_consts
7925 }
7926
7927 #aliased_row_helper_my
7928
7929 #aliased_row_helper_sqlite
7930
7931 #load_related_impl
7932
7933 #load_related_impl_my
7934
7935 #load_related_impl_sqlite
7936
7937 #has_pk_value_impl
7938
7939 #fk_pk_access_impl
7940
7941 #assign_auto_pk_pool_impl
7942 }
7943}
7944
7945fn column_const_tokens(module_ident: &syn::Ident, entries: &[ColumnEntry]) -> TokenStream2 {
7947 let lines = entries.iter().map(|e| {
7948 let ident = &e.ident;
7949 let col_ty = column_type_ident(ident);
7950 quote! {
7951 #[allow(non_upper_case_globals)]
7952 pub const #ident: #module_ident::#col_ty = #module_ident::#col_ty;
7953 }
7954 });
7955 quote! { #(#lines)* }
7956}
7957
7958fn column_module_tokens(
7961 module_ident: &syn::Ident,
7962 struct_name: &syn::Ident,
7963 entries: &[ColumnEntry],
7964) -> TokenStream2 {
7965 let root = rustango_root();
7966 let items = entries.iter().map(|e| {
7967 let col_ty = column_type_ident(&e.ident);
7968 let value_ty = &e.value_ty;
7969 let name = &e.name;
7970 let column = &e.column;
7971 let field_type_tokens = &e.field_type_tokens;
7972 quote! {
7973 #[derive(::core::clone::Clone, ::core::marker::Copy)]
7974 pub struct #col_ty;
7975
7976 impl #root::core::Column for #col_ty {
7977 type Model = super::#struct_name;
7978 type Value = #value_ty;
7979 const NAME: &'static str = #name;
7980 const COLUMN: &'static str = #column;
7981 const FIELD_TYPE: #root::core::FieldType = #field_type_tokens;
7982 }
7983 }
7984 });
7985 quote! {
7986 #[doc(hidden)]
7987 #[allow(non_camel_case_types, non_snake_case)]
7988 pub mod #module_ident {
7989 #[allow(unused_imports)]
7994 use super::*;
7995 #(#items)*
7996 }
7997 }
7998}
7999
8000fn column_type_ident(field_ident: &syn::Ident) -> syn::Ident {
8001 let name = ident_name(field_ident);
8002 syn::Ident::new(&format!("{name}_col"), field_ident.span())
8003}
8004
8005fn restore_auto_now_unless(
8008 idents: &[syn::Ident],
8009 ret: &TokenStream2,
8010 keep: &TokenStream2,
8011 body: &TokenStream2,
8012) -> TokenStream2 {
8013 if idents.is_empty() {
8014 return body.clone();
8015 }
8016 let prev: Vec<syn::Ident> = (0..idents.len())
8017 .map(|i| quote::format_ident!("__rustango_auto_now_prev_{}", i))
8018 .collect();
8019 quote! {
8020 #( let #prev = ::core::clone::Clone::clone(&self.#idents); )*
8021 let _saved: #ret = async { #body }.await;
8022 if !(#keep) {
8023 #( self.#idents = #prev; )*
8024 }
8025 _saved
8026 }
8027}
8028
8029fn ident_name(ident: &syn::Ident) -> String {
8031 syn::ext::IdentExt::unraw(ident).to_string()
8032}
8033
8034fn ident_from_name(name: &str, span: proc_macro2::Span) -> syn::Ident {
8036 match syn::parse_str::<syn::Ident>(name) {
8037 Ok(mut ident) => {
8038 ident.set_span(span);
8039 ident
8040 }
8041 Err(_) => syn::Ident::new_raw(name, span),
8042 }
8043}
8044
8045fn column_module_ident(struct_name: &syn::Ident) -> syn::Ident {
8046 syn::Ident::new(
8047 &format!("__rustango_cols_{struct_name}"),
8048 struct_name.span(),
8049 )
8050}
8051
8052fn from_row_impl_tokens(
8053 struct_name: &syn::Ident,
8054 from_row_inits: &[TokenStream2],
8055 from_row_inits_my: &[TokenStream2],
8056) -> TokenStream2 {
8057 let root = rustango_root();
8058 quote! {
8069 #[cfg(feature = "postgres")]
8070 impl<'r> #root::sql::sqlx::FromRow<'r, #root::sql::sqlx::postgres::PgRow>
8071 for #struct_name
8072 {
8073 fn from_row(
8074 row: &'r #root::sql::sqlx::postgres::PgRow,
8075 ) -> ::core::result::Result<Self, #root::sql::sqlx::Error> {
8076 ::core::result::Result::Ok(Self {
8077 #( #from_row_inits ),*
8078 })
8079 }
8080 }
8081
8082 #root::__impl_my_from_row!(#struct_name, |row| {
8083 #( #from_row_inits_my ),*
8084 });
8085
8086 #root::__impl_sqlite_from_row!(#struct_name, |row| {
8087 #( #from_row_inits ),*
8088 });
8089 }
8090}
8091
8092struct ContainerAttrs {
8093 table: Option<String>,
8094 display: Option<(String, proc_macro2::Span)>,
8095 app: Option<String>,
8102 admin: Option<AdminAttrs>,
8107 audit: Option<AuditAttrs>,
8113 permissions: bool,
8117 m2m: Vec<M2MAttr>,
8121 generic_m2m: Vec<GenericM2MAttr>,
8126 indexes: Vec<IndexAttr>,
8132 checks: Vec<CheckAttr>,
8135 excludes: Vec<ExcludeAttr>,
8141 composite_fks: Vec<CompositeFkAttr>,
8145 generic_fks: Vec<GenericFkAttr>,
8149 scope: Option<String>,
8155 manager_fns: Vec<syn::Ident>,
8160 default_order: Vec<(String, bool, proc_macro2::Span)>,
8166 is_view: bool,
8171 managed: bool,
8177 base_manager_name: Option<String>,
8181 order_with_respect_to: Option<String>,
8186 proxy: bool,
8192 required_db_features: Vec<String>,
8197 required_db_vendor: Option<String>,
8203 default_related_name: Option<String>,
8209 db_table_comment: Option<String>,
8214 get_latest_by: Option<(String, bool, proc_macro2::Span)>,
8220 extra_permissions: Vec<(String, String)>,
8225 default_permissions: Vec<String>,
8232 verbose_name: Option<String>,
8238 verbose_name_plural: Option<String>,
8242 global_scopes: Vec<GlobalScopeAttr>,
8248 through_relations: Vec<ThroughAttr>,
8257 reverse_has_relations: Vec<ReverseHasAttr>,
8266 generic_has_relations: Vec<GenericHasAttr>,
8271}
8272
8273struct GlobalScopeAttr {
8279 name: String,
8280 apply: syn::Path,
8281}
8282
8283struct ThroughAttr {
8313 name: String,
8315 far: syn::Ident,
8319 far_fk_column: String,
8325 intermediate: syn::Ident,
8329 intermediate_fk_column: String,
8333 intermediate_pk_column: String,
8338}
8339
8340struct ReverseHasAttr {
8379 name: String,
8382 child: syn::Ident,
8386 child_fk_column: String,
8390 self_pk_column: Option<String>,
8393 self_pk_span: Option<proc_macro2::Span>,
8395}
8396
8397impl ReverseHasAttr {
8398 fn self_pk_column(&self) -> &str {
8399 self.self_pk_column.as_deref().unwrap_or("id")
8400 }
8401}
8402
8403struct GenericHasAttr {
8409 name: String,
8411 child: syn::Ident,
8414 ct_column: String,
8417 pk_column: String,
8420 self_pk_column: Option<String>,
8423 self_pk_span: Option<proc_macro2::Span>,
8425}
8426
8427impl GenericHasAttr {
8428 fn self_pk_column(&self) -> &str {
8429 self.self_pk_column.as_deref().unwrap_or("id")
8430 }
8431}
8432
8433struct IndexAttr {
8435 span: proc_macro2::Span,
8437 name: Option<String>,
8439 columns: Vec<String>,
8441 unique: bool,
8443 method: String,
8449 where_clause: Option<String>,
8453 include: Vec<String>,
8456}
8457
8458struct CheckAttr {
8460 name: String,
8461 expr: String,
8462}
8463
8464struct ExcludeAttr {
8469 name: String,
8471 using: String,
8473 elements: Vec<(String, String)>,
8476 where_clause: Option<String>,
8478}
8479
8480struct CompositeFkAttr {
8486 name: String,
8488 to: String,
8490 from: Vec<String>,
8492 on: Vec<String>,
8494}
8495
8496struct GenericFkAttr {
8501 name: String,
8503 ct_column: String,
8505 pk_column: String,
8507}
8508
8509struct M2MAttr {
8511 name: String,
8513 to: String,
8515 through: String,
8517 src: String,
8519 dst: String,
8521 auto_create: bool,
8526}
8527
8528struct GenericM2MAttr {
8533 name: String,
8535 through: String,
8537 pk_column: String,
8539 ct_column: String,
8542 related_column: String,
8544}
8545
8546#[derive(Default)]
8552struct AuditAttrs {
8553 track: Option<(Vec<String>, proc_macro2::Span)>,
8557}
8558
8559#[derive(Default)]
8564struct AdminAttrs {
8565 list_display: Option<(Vec<String>, proc_macro2::Span)>,
8566 search_fields: Option<(Vec<String>, proc_macro2::Span)>,
8567 list_per_page: Option<usize>,
8568 ordering: Option<(Vec<(String, bool)>, proc_macro2::Span)>,
8569 readonly_fields: Option<(Vec<String>, proc_macro2::Span)>,
8570 list_filter: Option<(Vec<String>, proc_macro2::Span)>,
8571 actions: Option<(Vec<String>, proc_macro2::Span)>,
8574 fieldsets: Option<(Vec<(String, Vec<String>)>, proc_macro2::Span)>,
8578 list_display_links: Option<(Vec<String>, proc_macro2::Span)>,
8582 search_help_text: Option<String>,
8586 actions_on_top: Option<bool>,
8589 actions_on_bottom: Option<bool>,
8593 date_hierarchy: Option<String>,
8598 prepopulated_fields: Option<(Vec<(String, Vec<String>)>, proc_macro2::Span)>,
8604 raw_id_fields: Option<(Vec<String>, proc_macro2::Span)>,
8608 autocomplete_fields: Option<(Vec<String>, proc_macro2::Span)>,
8613 list_select_related: Option<String>,
8618 formfield_overrides: Option<(Vec<(String, String)>, proc_macro2::Span)>,
8624}
8625
8626#[derive(Clone, Copy, PartialEq, Eq)]
8629enum ModelAttrName {
8630 Rustango,
8631 Rgo,
8632}
8633
8634impl ModelAttrName {
8635 fn of(attr: &syn::Attribute) -> Option<Self> {
8636 let path = attr.path();
8637 if path.is_ident("rustango") {
8638 Some(Self::Rustango)
8639 } else if path.is_ident("rgo") {
8640 Some(Self::Rgo)
8641 } else {
8642 None
8643 }
8644 }
8645
8646 fn as_str(self) -> &'static str {
8647 match self {
8648 Self::Rustango => "rustango",
8649 Self::Rgo => "rgo",
8650 }
8651 }
8652}
8653
8654fn model_attrs(attrs: &[syn::Attribute]) -> syn::Result<Vec<&syn::Attribute>> {
8657 let mut first: Option<ModelAttrName> = None;
8658 let mut out = Vec::new();
8659 for attr in attrs {
8660 let Some(name) = ModelAttrName::of(attr) else {
8661 continue;
8662 };
8663 match first {
8664 Some(seen) if seen != name => {
8665 return Err(syn::Error::new_spanned(
8666 attr.path(),
8667 format!(
8668 "`#[{}(...)]` and `#[{}(...)]` are the same attribute; \
8669 use one name on an item or field",
8670 seen.as_str(),
8671 name.as_str(),
8672 ),
8673 ));
8674 }
8675 _ => first = Some(name),
8676 }
8677 out.push(attr);
8678 }
8679 Ok(out)
8680}
8681
8682fn parse_container_attrs(input: &DeriveInput) -> syn::Result<ContainerAttrs> {
8683 let mut out = ContainerAttrs {
8684 table: None,
8685 display: None,
8686 app: None,
8687 admin: None,
8688 audit: None,
8689 permissions: true,
8698 m2m: Vec::new(),
8699 generic_m2m: Vec::new(),
8700 indexes: Vec::new(),
8701 checks: Vec::new(),
8702 excludes: Vec::new(),
8703 composite_fks: Vec::new(),
8704 generic_fks: Vec::new(),
8705 scope: None,
8706 manager_fns: Vec::new(),
8707 default_order: Vec::new(),
8708 is_view: false,
8709 managed: true,
8710 verbose_name: None,
8711 verbose_name_plural: None,
8712 base_manager_name: None,
8713 order_with_respect_to: None,
8714 proxy: false,
8715 required_db_features: Vec::new(),
8716 required_db_vendor: None,
8717 default_related_name: None,
8718 db_table_comment: None,
8719 get_latest_by: None,
8720 extra_permissions: Vec::new(),
8721 default_permissions: Vec::new(),
8722 global_scopes: Vec::new(),
8723 through_relations: Vec::new(),
8724 reverse_has_relations: Vec::new(),
8725 generic_has_relations: Vec::new(),
8726 };
8727 for attr in model_attrs(&input.attrs)? {
8728 let written = ModelAttrName::of(attr).map_or("rustango", ModelAttrName::as_str);
8730 attr.parse_nested_meta(|meta| {
8731 if meta.path.is_ident("table") {
8732 let s: LitStr = meta.value()?.parse()?;
8733 let name = s.value();
8734 validate_table_name(&name, s.span())?;
8744 out.table = Some(name);
8745 return Ok(());
8746 }
8747 if meta.path.is_ident("display") {
8748 let s: LitStr = meta.value()?.parse()?;
8749 out.display = Some((s.value(), s.span()));
8750 return Ok(());
8751 }
8752 if meta.path.is_ident("app") {
8753 let s: LitStr = meta.value()?.parse()?;
8754 out.app = Some(s.value());
8755 return Ok(());
8756 }
8757 if meta.path.is_ident("scope") {
8758 let s: LitStr = meta.value()?.parse()?;
8759 let val = s.value();
8760 if !matches!(val.to_ascii_lowercase().as_str(), "registry" | "tenant") {
8761 return Err(meta.error(format!(
8762 "`scope` must be \"registry\" or \"tenant\", got {val:?}"
8763 )));
8764 }
8765 out.scope = Some(val);
8766 return Ok(());
8767 }
8768 if meta.path.is_ident("admin") {
8769 let mut admin = AdminAttrs::default();
8770 meta.parse_nested_meta(|inner| {
8771 if inner.path.is_ident("list_display") {
8772 let s: LitStr = inner.value()?.parse()?;
8773 admin.list_display =
8774 Some((split_field_list(&s.value()), s.span()));
8775 return Ok(());
8776 }
8777 if inner.path.is_ident("search_fields") {
8778 let s: LitStr = inner.value()?.parse()?;
8779 admin.search_fields =
8780 Some((split_field_list(&s.value()), s.span()));
8781 return Ok(());
8782 }
8783 if inner.path.is_ident("readonly_fields") {
8784 let s: LitStr = inner.value()?.parse()?;
8785 admin.readonly_fields =
8786 Some((split_field_list(&s.value()), s.span()));
8787 return Ok(());
8788 }
8789 if inner.path.is_ident("list_per_page") {
8790 let lit: syn::LitInt = inner.value()?.parse()?;
8791 admin.list_per_page = Some(lit.base10_parse::<usize>()?);
8792 return Ok(());
8793 }
8794 if inner.path.is_ident("ordering") {
8795 let s: LitStr = inner.value()?.parse()?;
8796 admin.ordering = Some((
8797 parse_ordering_list(&s.value()),
8798 s.span(),
8799 ));
8800 return Ok(());
8801 }
8802 if inner.path.is_ident("list_filter") {
8803 let s: LitStr = inner.value()?.parse()?;
8804 admin.list_filter =
8805 Some((split_field_list(&s.value()), s.span()));
8806 return Ok(());
8807 }
8808 if inner.path.is_ident("actions") {
8809 let s: LitStr = inner.value()?.parse()?;
8810 admin.actions =
8811 Some((split_field_list(&s.value()), s.span()));
8812 return Ok(());
8813 }
8814 if inner.path.is_ident("fieldsets") {
8815 let s: LitStr = inner.value()?.parse()?;
8816 admin.fieldsets =
8817 Some((parse_fieldset_list(&s.value()), s.span()));
8818 return Ok(());
8819 }
8820 if inner.path.is_ident("list_display_links") {
8821 let s: LitStr = inner.value()?.parse()?;
8822 admin.list_display_links =
8823 Some((split_field_list(&s.value()), s.span()));
8824 return Ok(());
8825 }
8826 if inner.path.is_ident("search_help_text") {
8827 let s: LitStr = inner.value()?.parse()?;
8828 admin.search_help_text = Some(s.value());
8829 return Ok(());
8830 }
8831 if inner.path.is_ident("actions_on_top") {
8832 let lit: syn::LitBool = inner.value()?.parse()?;
8833 admin.actions_on_top = Some(lit.value);
8834 return Ok(());
8835 }
8836 if inner.path.is_ident("actions_on_bottom") {
8837 let lit: syn::LitBool = inner.value()?.parse()?;
8838 admin.actions_on_bottom = Some(lit.value);
8839 return Ok(());
8840 }
8841 if inner.path.is_ident("date_hierarchy") {
8842 let s: LitStr = inner.value()?.parse()?;
8843 admin.date_hierarchy = Some(s.value());
8844 return Ok(());
8845 }
8846 if inner.path.is_ident("prepopulated_fields") {
8847 let s: LitStr = inner.value()?.parse()?;
8848 admin.prepopulated_fields =
8849 Some((parse_prepopulated_list(&s.value()), s.span()));
8850 return Ok(());
8851 }
8852 if inner.path.is_ident("raw_id_fields") {
8853 let s: LitStr = inner.value()?.parse()?;
8854 admin.raw_id_fields =
8855 Some((split_field_list(&s.value()), s.span()));
8856 return Ok(());
8857 }
8858 if inner.path.is_ident("autocomplete_fields") {
8859 let s: LitStr = inner.value()?.parse()?;
8860 admin.autocomplete_fields =
8861 Some((split_field_list(&s.value()), s.span()));
8862 return Ok(());
8863 }
8864 if inner.path.is_ident("list_select_related") {
8865 let s: LitStr = inner.value()?.parse()?;
8866 admin.list_select_related = Some(s.value());
8867 return Ok(());
8868 }
8869 if inner.path.is_ident("formfield_overrides") {
8870 let s: LitStr = inner.value()?.parse()?;
8871 admin.formfield_overrides =
8872 Some((parse_formfield_overrides(&s.value()), s.span()));
8873 return Ok(());
8874 }
8875 Err(inner.error(
8876 "unknown admin attribute (supported: \
8877 `list_display`, `list_display_links`, \
8878 `search_fields`, `search_help_text`, \
8879 `readonly_fields`, \
8880 `list_filter`, `list_per_page`, `ordering`, `actions`, \
8881 `actions_on_top`, `actions_on_bottom`, \
8882 `date_hierarchy`, \
8883 `prepopulated_fields`, \
8884 `raw_id_fields`, \
8885 `autocomplete_fields`, \
8886 `list_select_related`, \
8887 `formfield_overrides`, \
8888 `fieldsets`)",
8889 ))
8890 })?;
8891 out.admin = Some(admin);
8892 return Ok(());
8893 }
8894 if meta.path.is_ident("manager") {
8895 return Err(meta.error(
8897 "`manager(ext = ...)` was removed (#2132): declare your own \
8898 `trait FooManagerExt: Sized { … }` and implement it for `QuerySet<Foo>`",
8899 ));
8900 }
8901 if meta.path.is_ident("manager_fn") {
8902 let s: LitStr = meta.value()?.parse()?;
8907 let name = s.value();
8908 if name.is_empty() {
8909 return Err(meta.error("`manager_fn = \"...\"` cannot be empty"));
8910 }
8911 if name == "objects" {
8912 return Err(meta.error(
8913 "`manager_fn = \"objects\"` collides with the default \
8914 accessor — pick a different name",
8915 ));
8916 }
8917 let ident = syn::Ident::new(&name, s.span());
8918 if out.manager_fns.iter().any(|prev| *prev == ident) {
8919 return Err(meta.error(format!(
8920 "duplicate `manager_fn = \"{name}\"`"
8921 )));
8922 }
8923 out.manager_fns.push(ident);
8924 return Ok(());
8925 }
8926 if meta.path.is_ident("default_order") {
8927 let s: LitStr = meta.value()?.parse()?;
8932 let raw = s.value();
8933 let span = s.span();
8934 let mut parsed: Vec<(String, bool, proc_macro2::Span)> =
8935 Vec::new();
8936 for entry in raw.split(',') {
8937 let trimmed = entry.trim();
8938 if trimmed.is_empty() {
8939 return Err(syn::Error::new(
8940 span,
8941 "`default_order = \"...\"` has an empty entry — \
8942 check for a stray comma",
8943 ));
8944 }
8945 let (desc, name) = if let Some(rest) = trimmed.strip_prefix('-') {
8946 (true, rest.trim().to_owned())
8947 } else if let Some(rest) = trimmed.strip_prefix('+') {
8948 (false, rest.trim().to_owned())
8949 } else {
8950 (false, trimmed.to_owned())
8951 };
8952 if name.is_empty() {
8953 return Err(syn::Error::new(
8954 span,
8955 "`default_order` entry has no column name after the prefix",
8956 ));
8957 }
8958 if parsed.iter().any(|(n, _, _)| *n == name) {
8959 return Err(syn::Error::new(
8960 span,
8961 format!("duplicate column `{name}` in `default_order`"),
8962 ));
8963 }
8964 parsed.push((name, desc, span));
8965 }
8966 if parsed.is_empty() {
8967 return Err(syn::Error::new(
8968 span,
8969 "`default_order = \"...\"` cannot be empty",
8970 ));
8971 }
8972 out.default_order = parsed;
8973 return Ok(());
8974 }
8975 if meta.path.is_ident("global_scope") {
8976 let span = meta.path.span();
8982 let mut scope_name: Option<String> = None;
8983 let mut apply_path: Option<syn::Path> = None;
8984 meta.parse_nested_meta(|inner| {
8985 if inner.path.is_ident("name") {
8986 let s: LitStr = inner.value()?.parse()?;
8987 let raw = s.value();
8988 if raw.trim().is_empty() {
8989 return Err(syn::Error::new(
8990 s.span(),
8991 "`global_scope(name = \"...\")` must not be empty",
8992 ));
8993 }
8994 scope_name = Some(raw);
8995 return Ok(());
8996 }
8997 if inner.path.is_ident("apply") {
8998 let p: syn::Path = inner.value()?.parse()?;
8999 apply_path = Some(p);
9000 return Ok(());
9001 }
9002 Err(inner.error(
9003 "unknown `global_scope` attribute (supported: \
9004 `name`, `apply`)",
9005 ))
9006 })?;
9007 let Some(name) = scope_name else {
9008 return Err(syn::Error::new(
9009 span,
9010 "`global_scope` requires `name = \"...\"`",
9011 ));
9012 };
9013 let Some(apply) = apply_path else {
9014 return Err(syn::Error::new(
9015 span,
9016 "`global_scope` requires `apply = fn_path`",
9017 ));
9018 };
9019 if out.global_scopes.iter().any(|s| s.name == name) {
9020 return Err(syn::Error::new(
9021 span,
9022 format!(
9023 "duplicate global scope name `{name}` — \
9024 pick a unique identifier so \
9025 `QuerySet::without_global_scope(\"{name}\")` \
9026 is unambiguous"
9027 ),
9028 ));
9029 }
9030 out.global_scopes.push(GlobalScopeAttr { name, apply });
9031 return Ok(());
9032 }
9033 if meta.path.is_ident("through") {
9034 let span = meta.path.span();
9039 let mut accessor_name: Option<String> = None;
9040 let mut far_ident: Option<syn::Ident> = None;
9041 let mut far_fk_column: Option<String> = None;
9042 let mut intermediate_ident: Option<syn::Ident> = None;
9043 let mut intermediate_fk_column: Option<String> = None;
9044 let mut intermediate_pk_column: Option<String> = None;
9045 fn parse_nonempty_string(
9046 inner: &syn::meta::ParseNestedMeta<'_>,
9047 field: &str,
9048 ) -> syn::Result<String> {
9049 let s: LitStr = inner.value()?.parse()?;
9050 let raw = s.value();
9051 let trimmed = raw.trim();
9052 if trimmed.is_empty() {
9053 return Err(syn::Error::new(
9054 s.span(),
9055 format!("`through({field} = \"...\")` must not be empty"),
9056 ));
9057 }
9058 Ok(trimmed.to_owned())
9059 }
9060 meta.parse_nested_meta(|inner| {
9061 if inner.path.is_ident("name") {
9062 accessor_name = Some(parse_nonempty_string(&inner, "name")?);
9063 return Ok(());
9064 }
9065 if inner.path.is_ident("far") {
9066 let s: LitStr = inner.value()?.parse()?;
9072 let raw = s.value();
9073 let trimmed = raw.trim();
9074 if trimmed.is_empty() {
9075 return Err(syn::Error::new(
9076 s.span(),
9077 "`through(far = \"...\")` must not be empty",
9078 ));
9079 }
9080 far_ident = Some(syn::Ident::new(trimmed, s.span()));
9081 return Ok(());
9082 }
9083 if inner.path.is_ident("far_fk_column") {
9084 far_fk_column =
9085 Some(parse_nonempty_string(&inner, "far_fk_column")?);
9086 return Ok(());
9087 }
9088 if inner.path.is_ident("intermediate") {
9089 let s: LitStr = inner.value()?.parse()?;
9090 let raw = s.value();
9091 let trimmed = raw.trim();
9092 if trimmed.is_empty() {
9093 return Err(syn::Error::new(
9094 s.span(),
9095 "`through(intermediate = \"...\")` must not be empty",
9096 ));
9097 }
9098 intermediate_ident = Some(syn::Ident::new(trimmed, s.span()));
9099 return Ok(());
9100 }
9101 if inner.path.is_ident("intermediate_fk_column") {
9102 intermediate_fk_column =
9103 Some(parse_nonempty_string(&inner, "intermediate_fk_column")?);
9104 return Ok(());
9105 }
9106 if inner.path.is_ident("intermediate_pk_column") {
9107 intermediate_pk_column =
9108 Some(parse_nonempty_string(&inner, "intermediate_pk_column")?);
9109 return Ok(());
9110 }
9111 Err(inner.error(
9112 "unknown `through` attribute (supported: \
9113 `name`, `far`, `far_fk_column`, \
9114 `intermediate`, `intermediate_fk_column`, \
9115 `intermediate_pk_column`)",
9116 ))
9117 })?;
9118 let Some(name) = accessor_name else {
9119 return Err(syn::Error::new(
9120 span,
9121 "`through` requires `name = \"...\"`",
9122 ));
9123 };
9124 let Some(far) = far_ident else {
9125 return Err(syn::Error::new(
9126 span,
9127 "`through` requires `far = \"FarModelType\"`",
9128 ));
9129 };
9130 let Some(far_fk_column) = far_fk_column else {
9131 return Err(syn::Error::new(
9132 span,
9133 "`through` requires `far_fk_column = \"<column>\"`",
9134 ));
9135 };
9136 let Some(intermediate) = intermediate_ident else {
9137 return Err(syn::Error::new(
9138 span,
9139 "`through` requires `intermediate = \"IntermediateModelType\"`",
9140 ));
9141 };
9142 let Some(intermediate_fk_column) = intermediate_fk_column else {
9143 return Err(syn::Error::new(
9144 span,
9145 "`through` requires `intermediate_fk_column = \"<column>\"`",
9146 ));
9147 };
9148 let intermediate_pk_column =
9149 intermediate_pk_column.unwrap_or_else(|| "id".to_owned());
9150 if out.through_relations.iter().any(|t| t.name == name) {
9151 return Err(syn::Error::new(
9152 span,
9153 format!(
9154 "duplicate `through(name = \"{name}\")` — \
9155 pick a unique accessor name"
9156 ),
9157 ));
9158 }
9159 out.through_relations.push(ThroughAttr {
9160 name,
9161 far,
9162 far_fk_column,
9163 intermediate,
9164 intermediate_fk_column,
9165 intermediate_pk_column,
9166 });
9167 return Ok(());
9168 }
9169 if meta.path.is_ident("reverse_has") {
9170 let span = meta.path.span();
9174 let mut accessor_name: Option<String> = None;
9175 let mut child_ident: Option<syn::Ident> = None;
9176 let mut child_fk_column: Option<String> = None;
9177 let mut self_pk_column: Option<String> = None;
9178 let mut self_pk_span: Option<proc_macro2::Span> = None;
9179 meta.parse_nested_meta(|inner| {
9180 if inner.path.is_ident("name") {
9181 let s: LitStr = inner.value()?.parse()?;
9182 let raw = s.value();
9183 if raw.trim().is_empty() {
9184 return Err(syn::Error::new(
9185 s.span(),
9186 "`reverse_has(name = \"...\")` must not be empty",
9187 ));
9188 }
9189 accessor_name = Some(raw);
9190 return Ok(());
9191 }
9192 if inner.path.is_ident("child") {
9193 let s: LitStr = inner.value()?.parse()?;
9194 let raw = s.value();
9195 let trimmed = raw.trim();
9196 if trimmed.is_empty() {
9197 return Err(syn::Error::new(
9198 s.span(),
9199 "`reverse_has(child = \"...\")` must not be empty",
9200 ));
9201 }
9202 child_ident = Some(syn::Ident::new(trimmed, s.span()));
9203 return Ok(());
9204 }
9205 if inner.path.is_ident("child_fk_column") {
9206 let s: LitStr = inner.value()?.parse()?;
9207 let raw = s.value();
9208 let trimmed = raw.trim();
9209 if trimmed.is_empty() {
9210 return Err(syn::Error::new(
9211 s.span(),
9212 "`reverse_has(child_fk_column = \"...\")` must not be empty",
9213 ));
9214 }
9215 child_fk_column = Some(trimmed.to_owned());
9216 return Ok(());
9217 }
9218 if inner.path.is_ident("self_pk_column") {
9219 let s: LitStr = inner.value()?.parse()?;
9220 let raw = s.value();
9221 let trimmed = raw.trim();
9222 if trimmed.is_empty() {
9223 return Err(syn::Error::new(
9224 s.span(),
9225 "`reverse_has(self_pk_column = \"...\")` must not be empty",
9226 ));
9227 }
9228 self_pk_column = Some(trimmed.to_owned());
9229 self_pk_span = Some(s.span());
9230 return Ok(());
9231 }
9232 Err(inner.error(
9233 "unknown `reverse_has` attribute (supported: \
9234 `name`, `child`, `child_fk_column`, \
9235 `self_pk_column`)",
9236 ))
9237 })?;
9238 let Some(name) = accessor_name else {
9239 return Err(syn::Error::new(
9240 span,
9241 "`reverse_has` requires `name = \"...\"`",
9242 ));
9243 };
9244 let Some(child) = child_ident else {
9245 return Err(syn::Error::new(
9246 span,
9247 "`reverse_has` requires `child = \"ChildModelType\"`",
9248 ));
9249 };
9250 let Some(child_fk_column) = child_fk_column else {
9251 return Err(syn::Error::new(
9252 span,
9253 "`reverse_has` requires `child_fk_column = \"<column>\"`",
9254 ));
9255 };
9256 if out.reverse_has_relations.iter().any(|r| r.name == name) {
9257 return Err(syn::Error::new(
9258 span,
9259 format!(
9260 "duplicate `reverse_has(name = \"{name}\")` — \
9261 pick a unique accessor name"
9262 ),
9263 ));
9264 }
9265 out.reverse_has_relations.push(ReverseHasAttr {
9266 name,
9267 child,
9268 child_fk_column,
9269 self_pk_column,
9270 self_pk_span,
9271 });
9272 return Ok(());
9273 }
9274 if meta.path.is_ident("generic_has") {
9275 let span = meta.path.span();
9281 let mut accessor_name: Option<String> = None;
9282 let mut child_ident: Option<syn::Ident> = None;
9283 let mut ct_column: Option<String> = None;
9284 let mut pk_column: Option<String> = None;
9285 let mut self_pk_column: Option<String> = None;
9286 let mut self_pk_span: Option<proc_macro2::Span> = None;
9287 meta.parse_nested_meta(|inner| {
9288 if inner.path.is_ident("name") {
9289 let s: LitStr = inner.value()?.parse()?;
9290 let raw = s.value();
9291 if raw.trim().is_empty() {
9292 return Err(syn::Error::new(
9293 s.span(),
9294 "`generic_has(name = \"...\")` must not be empty",
9295 ));
9296 }
9297 accessor_name = Some(raw);
9298 return Ok(());
9299 }
9300 if inner.path.is_ident("child") {
9301 let s: LitStr = inner.value()?.parse()?;
9302 let trimmed = s.value().trim().to_owned();
9303 if trimmed.is_empty() {
9304 return Err(syn::Error::new(
9305 s.span(),
9306 "`generic_has(child = \"...\")` must not be empty",
9307 ));
9308 }
9309 child_ident = Some(syn::Ident::new(&trimmed, s.span()));
9310 return Ok(());
9311 }
9312 if inner.path.is_ident("ct_column") {
9313 let s: LitStr = inner.value()?.parse()?;
9314 let trimmed = s.value().trim().to_owned();
9315 if trimmed.is_empty() {
9316 return Err(syn::Error::new(
9317 s.span(),
9318 "`generic_has(ct_column = \"...\")` must not be empty",
9319 ));
9320 }
9321 ct_column = Some(trimmed);
9322 return Ok(());
9323 }
9324 if inner.path.is_ident("pk_column") {
9325 let s: LitStr = inner.value()?.parse()?;
9326 let trimmed = s.value().trim().to_owned();
9327 if trimmed.is_empty() {
9328 return Err(syn::Error::new(
9329 s.span(),
9330 "`generic_has(pk_column = \"...\")` must not be empty",
9331 ));
9332 }
9333 pk_column = Some(trimmed);
9334 return Ok(());
9335 }
9336 if inner.path.is_ident("self_pk_column") {
9337 let s: LitStr = inner.value()?.parse()?;
9338 let trimmed = s.value().trim().to_owned();
9339 if trimmed.is_empty() {
9340 return Err(syn::Error::new(
9341 s.span(),
9342 "`generic_has(self_pk_column = \"...\")` must not be empty",
9343 ));
9344 }
9345 self_pk_column = Some(trimmed);
9346 self_pk_span = Some(s.span());
9347 return Ok(());
9348 }
9349 Err(inner.error(
9350 "unknown `generic_has` attribute (supported: \
9351 `name`, `child`, `ct_column`, `pk_column`, \
9352 `self_pk_column`)",
9353 ))
9354 })?;
9355 let Some(name) = accessor_name else {
9356 return Err(syn::Error::new(
9357 span,
9358 "`generic_has` requires `name = \"...\"`",
9359 ));
9360 };
9361 let Some(child) = child_ident else {
9362 return Err(syn::Error::new(
9363 span,
9364 "`generic_has` requires `child = \"ChildModelType\"`",
9365 ));
9366 };
9367 let ct_column = ct_column.unwrap_or_else(|| "content_type_id".to_owned());
9368 let pk_column = pk_column.unwrap_or_else(|| "object_pk".to_owned());
9369 if out.generic_has_relations.iter().any(|r| r.name == name) {
9370 return Err(syn::Error::new(
9371 span,
9372 format!(
9373 "duplicate `generic_has(name = \"{name}\")` — \
9374 pick a unique accessor name"
9375 ),
9376 ));
9377 }
9378 out.generic_has_relations.push(GenericHasAttr {
9379 name,
9380 child,
9381 ct_column,
9382 pk_column,
9383 self_pk_column,
9384 self_pk_span,
9385 });
9386 return Ok(());
9387 }
9388 if meta.path.is_ident("audit") {
9389 let mut audit = AuditAttrs::default();
9390 meta.parse_nested_meta(|inner| {
9391 if inner.path.is_ident("track") {
9392 let s: LitStr = inner.value()?.parse()?;
9393 audit.track =
9394 Some((split_field_list(&s.value()), s.span()));
9395 return Ok(());
9396 }
9397 Err(inner.error(
9398 "unknown audit attribute (supported: `track`)",
9399 ))
9400 })?;
9401 out.audit = Some(audit);
9402 return Ok(());
9403 }
9404 if meta.path.is_ident("permissions") {
9405 if let Ok(v) = meta.value() {
9410 let lit: syn::LitBool = v.parse()?;
9411 out.permissions = lit.value;
9412 } else {
9413 out.permissions = true;
9414 }
9415 return Ok(());
9416 }
9417 if meta.path.is_ident("view") {
9418 if let Ok(v) = meta.value() {
9424 let lit: syn::LitBool = v.parse()?;
9425 out.is_view = lit.value;
9426 } else {
9427 out.is_view = true;
9428 }
9429 return Ok(());
9430 }
9431 if meta.path.is_ident("managed") {
9432 let v = meta.value()?;
9440 let lit: syn::LitBool = v.parse()?;
9441 out.managed = lit.value;
9442 return Ok(());
9443 }
9444 if meta.path.is_ident("verbose_name") {
9445 let s: LitStr = meta.value()?.parse()?;
9446 out.verbose_name = Some(s.value());
9447 return Ok(());
9448 }
9449 if meta.path.is_ident("verbose_name_plural") {
9450 let s: LitStr = meta.value()?.parse()?;
9451 out.verbose_name_plural = Some(s.value());
9452 return Ok(());
9453 }
9454 if meta.path.is_ident("db_table_comment") {
9455 let s: LitStr = meta.value()?.parse()?;
9458 out.db_table_comment = Some(s.value());
9459 return Ok(());
9460 }
9461 if meta.path.is_ident("proxy") {
9462 let value = if meta.input.peek(syn::Token![=]) {
9471 meta.value()?.parse::<syn::LitBool>()?.value
9472 } else {
9473 true
9474 };
9475 out.proxy = value;
9476 return Ok(());
9477 }
9478 if meta.path.is_ident("order_with_respect_to") {
9479 let s: LitStr = meta.value()?.parse()?;
9493 let raw = s.value();
9494 if raw.is_empty() {
9495 return Err(syn::Error::new(
9496 s.span(),
9497 "`order_with_respect_to` must be a non-empty FK field name",
9498 ));
9499 }
9500 let valid = raw
9501 .chars()
9502 .all(|c| c == '_' || c.is_ascii_alphanumeric())
9503 && !raw.chars().next().is_some_and(|c| c.is_ascii_digit());
9504 if !valid {
9505 return Err(syn::Error::new(
9506 s.span(),
9507 format!(
9508 "`order_with_respect_to` must be a valid Rust \
9509 identifier (letters / digits / underscores, \
9510 not starting with a digit); got `{raw}`"
9511 ),
9512 ));
9513 }
9514 out.order_with_respect_to = Some(raw);
9515 return Ok(());
9516 }
9517 if meta.path.is_ident("required_db_features") {
9518 let s: LitStr = meta.value()?.parse()?;
9533 let raw = s.value();
9534 let features: Vec<String> = raw
9535 .split(',')
9536 .map(str::trim)
9537 .filter(|s| !s.is_empty())
9538 .map(str::to_owned)
9539 .collect();
9540 if features.is_empty() {
9541 return Err(syn::Error::new(
9542 s.span(),
9543 "`required_db_features` must list at least one \
9544 comma-separated capability token",
9545 ));
9546 }
9547 out.required_db_features = features;
9548 return Ok(());
9549 }
9550 if meta.path.is_ident("required_db_vendor") {
9551 let s: LitStr = meta.value()?.parse()?;
9559 let raw = s.value().to_ascii_lowercase();
9560 match raw.as_str() {
9561 "postgresql" | "postgres" | "pg" => {
9562 out.required_db_vendor = Some("postgres".to_owned());
9563 }
9564 "mysql" | "mariadb" => {
9565 out.required_db_vendor = Some("mysql".to_owned());
9566 }
9567 "sqlite" | "sqlite3" => {
9568 out.required_db_vendor = Some("sqlite".to_owned());
9569 }
9570 _ => {
9571 return Err(syn::Error::new(
9572 s.span(),
9573 format!(
9574 "unknown required_db_vendor `{raw}` — \
9575 expected `postgres` (aliases: `postgresql`, `pg`), \
9576 `mysql` (alias: `mariadb`), or `sqlite` \
9577 (alias: `sqlite3`)"
9578 ),
9579 ));
9580 }
9581 }
9582 return Ok(());
9583 }
9584 if meta.path.is_ident("base_manager_name") {
9585 let s: LitStr = meta.value()?.parse()?;
9594 let raw = s.value();
9595 if raw.is_empty() {
9596 return Err(syn::Error::new(
9597 s.span(),
9598 "`base_manager_name` must be a non-empty string",
9599 ));
9600 }
9601 let valid = raw
9602 .chars()
9603 .all(|c| c == '_' || c.is_ascii_alphanumeric())
9604 && !raw.chars().next().is_some_and(|c| c.is_ascii_digit());
9605 if !valid {
9606 return Err(syn::Error::new(
9607 s.span(),
9608 format!(
9609 "`base_manager_name` must be a valid Rust \
9610 identifier (letters / digits / underscores, \
9611 not starting with a digit); got `{raw}`"
9612 ),
9613 ));
9614 }
9615 out.base_manager_name = Some(raw);
9616 return Ok(());
9617 }
9618 if meta.path.is_ident("default_related_name") {
9619 let s: LitStr = meta.value()?.parse()?;
9630 let raw = s.value();
9631 if raw.is_empty() {
9632 return Err(syn::Error::new(
9633 s.span(),
9634 "`default_related_name` must be a non-empty string",
9635 ));
9636 }
9637 let valid = raw
9638 .chars()
9639 .all(|c| c == '_' || c.is_ascii_lowercase() || c.is_ascii_digit())
9640 && !raw.chars().next().is_some_and(|c| c.is_ascii_digit());
9641 if !valid {
9642 return Err(syn::Error::new(
9643 s.span(),
9644 format!(
9645 "`default_related_name` must be snake_case ASCII \
9646 (lowercase letters / digits / underscores, not \
9647 starting with a digit); got `{raw}`"
9648 ),
9649 ));
9650 }
9651 out.default_related_name = Some(raw);
9652 return Ok(());
9653 }
9654 if meta.path.is_ident("extra_permissions") {
9655 let s: LitStr = meta.value()?.parse()?;
9657 let raw = s.value();
9658 let mut pairs = Vec::new();
9659 for entry in raw.split(',') {
9660 let entry = entry.trim();
9661 if entry.is_empty() {
9662 continue;
9663 }
9664 let (codename, label) = match entry.split_once(':') {
9665 Some((c, l)) => (c.trim().to_owned(), l.trim().to_owned()),
9666 None => (entry.to_owned(), entry.to_owned()),
9667 };
9668 if codename.is_empty() {
9669 return Err(meta.error(
9670 "`extra_permissions` entries must be `codename:label` pairs",
9671 ));
9672 }
9673 pairs.push((codename, label));
9674 }
9675 if pairs.is_empty() {
9676 return Err(meta
9677 .error("`extra_permissions = \"…\"` must list at least one pair"));
9678 }
9679 out.extra_permissions = pairs;
9680 return Ok(());
9681 }
9682 if meta.path.is_ident("default_permissions") {
9683 let s: LitStr = meta.value()?.parse()?;
9686 let raw = s.value();
9687 let mut actions: Vec<String> = Vec::new();
9688 for entry in raw.split(',') {
9689 let action = entry.trim().to_ascii_lowercase();
9690 if action.is_empty() {
9691 continue;
9692 }
9693 match action.as_str() {
9694 "add" | "change" | "delete" | "view" => {}
9695 other => {
9696 return Err(syn::Error::new(
9697 s.span(),
9698 format!(
9699 "unknown default_permissions action `{other}` — \
9700 expected one of `add`, `change`, `delete`, `view`"
9701 ),
9702 ));
9703 }
9704 }
9705 if !actions.contains(&action) {
9706 actions.push(action);
9707 }
9708 }
9709 if actions.is_empty() {
9710 return Err(syn::Error::new(
9711 s.span(),
9712 "`default_permissions = \"…\"` must list at least one action; \
9713 use `permissions = false` on the container if you want NO \
9714 permissions seeded for this model.",
9715 ));
9716 }
9717 out.default_permissions = actions;
9718 return Ok(());
9719 }
9720 if meta.path.is_ident("get_latest_by") {
9721 let s: LitStr = meta.value()?.parse()?;
9723 let raw = s.value();
9724 let trimmed = raw.trim();
9725 if trimmed.is_empty() {
9726 return Err(meta.error("`get_latest_by` must name a column"));
9727 }
9728 let (col, desc) = if let Some(stripped) = trimmed.strip_prefix('-') {
9729 (stripped.to_owned(), true)
9730 } else if let Some(stripped) = trimmed.strip_prefix('+') {
9731 (stripped.to_owned(), false)
9732 } else {
9733 (trimmed.to_owned(), false)
9734 };
9735 if col.is_empty() {
9736 return Err(meta.error("`get_latest_by` must name a column"));
9737 }
9738 out.get_latest_by = Some((col, desc, s.span()));
9739 return Ok(());
9740 }
9741 if meta.path.is_ident("unique_together") {
9742 let (columns, name) = parse_together_attr(&meta, "unique_together", written)?;
9751 out.indexes.push(IndexAttr {
9752 span: meta.path.span(),
9753 name,
9754 columns,
9755 unique: true,
9756 method: "btree".to_owned(),
9757 where_clause: None,
9758 include: Vec::new(),
9759 });
9760 return Ok(());
9761 }
9762 if meta.path.is_ident("index_together") {
9763 let (columns, name) = parse_together_attr(&meta, "index_together", written)?;
9769 out.indexes.push(IndexAttr {
9770 span: meta.path.span(),
9771 name,
9772 columns,
9773 unique: false,
9774 method: "btree".to_owned(),
9775 where_clause: None,
9776 include: Vec::new(),
9777 });
9778 return Ok(());
9779 }
9780 if meta.path.is_ident("unique_when") {
9781 let mut columns: Option<Vec<String>> = None;
9795 let mut condition: Option<String> = None;
9796 let mut name: Option<String> = None;
9797 let mut include: Vec<String> = Vec::new();
9798 meta.parse_nested_meta(|inner| {
9799 if inner.path.is_ident("columns") {
9800 let s: LitStr = inner.value()?.parse()?;
9801 columns = Some(split_field_list(&s.value()));
9802 return Ok(());
9803 }
9804 if inner.path.is_ident("condition") {
9805 let s: LitStr = inner.value()?.parse()?;
9806 condition = Some(s.value());
9807 return Ok(());
9808 }
9809 if inner.path.is_ident("name") {
9810 let s: LitStr = inner.value()?.parse()?;
9811 name = Some(s.value());
9812 return Ok(());
9813 }
9814 if inner.path.is_ident("include") {
9815 let s: LitStr = inner.value()?.parse()?;
9819 include = split_field_list(&s.value());
9820 return Ok(());
9821 }
9822 Err(inner.error(
9823 "unknown unique_when attribute (supported: \
9824 `columns = \"...\"`, `condition = \"...\"`, \
9825 `name = \"...\"`, `include = \"...\"`)",
9826 ))
9827 })?;
9828 let columns = columns.ok_or_else(|| {
9829 meta.error("`unique_when(...)` requires `columns = \"...\"`")
9830 })?;
9831 let condition = condition.ok_or_else(|| {
9832 meta.error("`unique_when(...)` requires `condition = \"...\"`")
9833 })?;
9834 if columns.is_empty() {
9835 return Err(meta.error("`unique_when(columns = \"\")` is empty"));
9836 }
9837 out.indexes.push(IndexAttr {
9838 span: meta.path.span(),
9839 name,
9840 columns,
9841 unique: true,
9842 method: "btree".to_owned(),
9843 where_clause: Some(condition),
9844 include,
9845 });
9846 return Ok(());
9847 }
9848 if meta.path.is_ident("index_when") {
9849 let mut columns: Option<Vec<String>> = None;
9866 let mut condition: Option<String> = None;
9867 let mut name: Option<String> = None;
9868 let mut method: String = "btree".to_owned();
9869 let mut include: Vec<String> = Vec::new();
9870 meta.parse_nested_meta(|inner| {
9871 if inner.path.is_ident("columns") {
9872 let s: LitStr = inner.value()?.parse()?;
9873 columns = Some(split_field_list(&s.value()));
9874 return Ok(());
9875 }
9876 if inner.path.is_ident("condition") {
9877 let s: LitStr = inner.value()?.parse()?;
9878 condition = Some(s.value());
9879 return Ok(());
9880 }
9881 if inner.path.is_ident("name") {
9882 let s: LitStr = inner.value()?.parse()?;
9883 name = Some(s.value());
9884 return Ok(());
9885 }
9886 if inner.path.is_ident("method") {
9887 let s: LitStr = inner.value()?.parse()?;
9888 method = s.value();
9889 return Ok(());
9890 }
9891 if inner.path.is_ident("include") {
9892 let s: LitStr = inner.value()?.parse()?;
9896 include = split_field_list(&s.value());
9897 return Ok(());
9898 }
9899 Err(inner.error(
9900 "unknown index_when attribute (supported: \
9901 `columns = \"...\"`, `condition = \"...\"`, \
9902 `name = \"...\"`, `method = \"btree|gin|gist|...\"`, \
9903 `include = \"...\"`)",
9904 ))
9905 })?;
9906 let columns = columns
9907 .ok_or_else(|| meta.error("`index_when(...)` requires `columns = \"...\"`"))?;
9908 let condition = condition.ok_or_else(|| {
9909 meta.error("`index_when(...)` requires `condition = \"...\"`")
9910 })?;
9911 if columns.is_empty() {
9912 return Err(meta.error("`index_when(columns = \"\")` is empty"));
9913 }
9914 out.indexes.push(IndexAttr {
9915 span: meta.path.span(),
9916 name,
9917 columns,
9918 unique: false,
9919 method,
9920 where_clause: Some(condition),
9921 include,
9922 });
9923 return Ok(());
9924 }
9925 if meta.path.is_ident("index") {
9926 let cols_lit: LitStr;
9937 let mut unique = false;
9938 let mut name: Option<String> = None;
9939 let mut method = "btree".to_owned();
9940 if meta.input.peek(syn::token::Paren) {
9941 let content;
9942 syn::parenthesized!(content in meta.input);
9943 cols_lit = content.parse()?;
9944 while content.peek(syn::Token![,]) {
9945 content.parse::<syn::Token![,]>()?;
9946 if content.is_empty() {
9947 break;
9948 }
9949 let flag: syn::Ident = content.parse()?;
9950 if flag == "unique" {
9951 unique = true;
9952 } else if flag == "name" {
9953 content.parse::<syn::Token![=]>()?;
9954 let s: LitStr = content.parse()?;
9955 name = Some(s.value());
9956 } else if flag == "method" {
9957 content.parse::<syn::Token![=]>()?;
9958 let s: LitStr = content.parse()?;
9959 let v = s.value();
9960 match v.as_str() {
9961 "btree" | "gin" | "gist" | "brin" | "spgist" | "hash"
9962 | "bloom" => method = v,
9963 other => {
9964 return Err(syn::Error::new(
9965 s.span(),
9966 format!("unknown index method `{other}` (supported: btree, gin, gist, brin, spgist, hash, bloom)"),
9967 ));
9968 }
9969 }
9970 } else {
9971 return Err(syn::Error::new(
9972 flag.span(),
9973 "unknown index flag (supported: `unique`, `name`, `method`)",
9974 ));
9975 }
9976 }
9977 } else {
9978 cols_lit = meta.value()?.parse()?;
9979 }
9980 let columns = split_field_list(&cols_lit.value());
9981 out.indexes.push(IndexAttr {
9982 span: meta.path.span(),
9983 name,
9984 columns,
9985 unique,
9986 method,
9987 where_clause: None,
9988 include: Vec::new(),
9989 });
9990 return Ok(());
9991 }
9992 if meta.path.is_ident("check") {
9993 let mut name: Option<String> = None;
9995 let mut expr: Option<String> = None;
9996 meta.parse_nested_meta(|inner| {
9997 if inner.path.is_ident("name") {
9998 let s: LitStr = inner.value()?.parse()?;
9999 name = Some(s.value());
10000 return Ok(());
10001 }
10002 if inner.path.is_ident("expr") {
10003 let s: LitStr = inner.value()?.parse()?;
10004 expr = Some(s.value());
10005 return Ok(());
10006 }
10007 Err(inner.error("unknown check attribute (supported: `name`, `expr`)"))
10008 })?;
10009 let name = name.ok_or_else(|| meta.error("check requires `name = \"...\"`"))?;
10010 let expr = expr.ok_or_else(|| meta.error("check requires `expr = \"...\"`"))?;
10011 out.checks.push(CheckAttr { name, expr });
10012 return Ok(());
10013 }
10014 if meta.path.is_ident("exclude") {
10015 let mut name: Option<String> = None;
10019 let mut using: Option<String> = None;
10020 let mut elements_raw: Option<(String, proc_macro2::Span)> = None;
10021 let mut where_clause: Option<String> = None;
10022 meta.parse_nested_meta(|inner| {
10023 if inner.path.is_ident("name") {
10024 let s: LitStr = inner.value()?.parse()?;
10025 name = Some(s.value());
10026 return Ok(());
10027 }
10028 if inner.path.is_ident("using") {
10029 let s: LitStr = inner.value()?.parse()?;
10030 using = Some(s.value());
10031 return Ok(());
10032 }
10033 if inner.path.is_ident("elements") {
10034 let s: LitStr = inner.value()?.parse()?;
10035 elements_raw = Some((s.value(), s.span()));
10036 return Ok(());
10037 }
10038 if inner.path.is_ident("where") || inner.path.is_ident("where_clause") {
10039 let s: LitStr = inner.value()?.parse()?;
10040 where_clause = Some(s.value());
10041 return Ok(());
10042 }
10043 Err(inner.error(
10044 "unknown exclude attribute (supported: `name`, `using`, `elements`, `where`)",
10045 ))
10046 })?;
10047 let name = name.ok_or_else(|| meta.error("exclude requires `name = \"...\"`"))?;
10048 let using = using.unwrap_or_else(|| "gist".to_owned());
10049 let (elements_str, elements_span) = elements_raw.ok_or_else(|| {
10050 meta.error(
10051 "exclude requires `elements = \"col WITH op, col WITH op\"`",
10052 )
10053 })?;
10054 let mut elements: Vec<(String, String)> = Vec::new();
10056 for pair in elements_str.split(',') {
10057 let pair = pair.trim();
10058 if pair.is_empty() {
10059 continue;
10060 }
10061 let mut split = pair.splitn(2, |c: char| c.is_whitespace());
10062 let col = split.next().unwrap_or("").trim();
10063 let rest = split.next().unwrap_or("").trim();
10064 let rest_lc = rest.to_ascii_lowercase();
10066 let op = rest_lc
10067 .strip_prefix("with")
10068 .map(|r| r.trim_start())
10069 .filter(|r| !r.is_empty())
10070 .map(|_| {
10071 rest[4..].trim_start().to_owned()
10074 });
10075 let Some(op) = op else {
10076 return Err(syn::Error::new(
10077 elements_span,
10078 format!(
10079 "exclude elements: `{pair}` must be `<col> WITH <op>` \
10080 (e.g. `room_id WITH =` or `during WITH &&`)"
10081 ),
10082 ));
10083 };
10084 if col.is_empty() || op.is_empty() {
10085 return Err(syn::Error::new(
10086 elements_span,
10087 format!(
10088 "exclude elements: `{pair}` must be `<col> WITH <op>` \
10089 (both sides non-empty)"
10090 ),
10091 ));
10092 }
10093 elements.push((col.to_owned(), op));
10094 }
10095 if elements.is_empty() {
10096 return Err(syn::Error::new(
10097 elements_span,
10098 "exclude requires at least one `col WITH op` element",
10099 ));
10100 }
10101 out.excludes.push(ExcludeAttr {
10102 name,
10103 using,
10104 elements,
10105 where_clause,
10106 });
10107 return Ok(());
10108 }
10109 if meta.path.is_ident("generic_fk") {
10110 let mut gfk = GenericFkAttr {
10111 name: String::new(),
10112 ct_column: String::new(),
10113 pk_column: String::new(),
10114 };
10115 meta.parse_nested_meta(|inner| {
10116 if inner.path.is_ident("name") {
10117 let s: LitStr = inner.value()?.parse()?;
10118 gfk.name = s.value();
10119 return Ok(());
10120 }
10121 if inner.path.is_ident("ct_column") {
10122 let s: LitStr = inner.value()?.parse()?;
10123 gfk.ct_column = s.value();
10124 return Ok(());
10125 }
10126 if inner.path.is_ident("pk_column") {
10127 let s: LitStr = inner.value()?.parse()?;
10128 gfk.pk_column = s.value();
10129 return Ok(());
10130 }
10131 Err(inner.error(
10132 "unknown generic_fk attribute (supported: `name`, `ct_column`, `pk_column`)",
10133 ))
10134 })?;
10135 if gfk.name.is_empty() {
10136 return Err(meta.error("generic_fk requires `name = \"...\"`"));
10137 }
10138 if gfk.ct_column.is_empty() {
10139 return Err(meta.error("generic_fk requires `ct_column = \"...\"`"));
10140 }
10141 if gfk.pk_column.is_empty() {
10142 return Err(meta.error("generic_fk requires `pk_column = \"...\"`"));
10143 }
10144 out.generic_fks.push(gfk);
10145 return Ok(());
10146 }
10147 if meta.path.is_ident("fk_composite") {
10148 let mut fk = CompositeFkAttr {
10149 name: String::new(),
10150 to: String::new(),
10151 from: Vec::new(),
10152 on: Vec::new(),
10153 };
10154 meta.parse_nested_meta(|inner| {
10155 if inner.path.is_ident("name") {
10156 let s: LitStr = inner.value()?.parse()?;
10157 fk.name = s.value();
10158 return Ok(());
10159 }
10160 if inner.path.is_ident("to") {
10161 let s: LitStr = inner.value()?.parse()?;
10162 fk.to = s.value();
10163 return Ok(());
10164 }
10165 if inner.path.is_ident("on") || inner.path.is_ident("from") {
10168 let value = inner.value()?;
10169 let content;
10170 syn::parenthesized!(content in value);
10171 let lits: syn::punctuated::Punctuated<syn::LitStr, syn::Token![,]> =
10172 content.parse_terminated(
10173 |p| p.parse::<syn::LitStr>(),
10174 syn::Token![,],
10175 )?;
10176 let cols: Vec<String> = lits.iter().map(syn::LitStr::value).collect();
10177 if inner.path.is_ident("on") {
10178 fk.on = cols;
10179 } else {
10180 fk.from = cols;
10181 }
10182 return Ok(());
10183 }
10184 Err(inner.error(
10185 "unknown fk_composite attribute (supported: `name`, `to`, `on`, `from`)",
10186 ))
10187 })?;
10188 if fk.name.is_empty() {
10189 return Err(meta.error("fk_composite requires `name = \"...\"`"));
10190 }
10191 if fk.to.is_empty() {
10192 return Err(meta.error("fk_composite requires `to = \"...\"`"));
10193 }
10194 if fk.from.is_empty() || fk.on.is_empty() {
10195 return Err(meta.error(
10196 "fk_composite requires non-empty `from = (...)` and `on = (...)` tuples",
10197 ));
10198 }
10199 if fk.from.len() != fk.on.len() {
10200 return Err(meta.error(format!(
10201 "fk_composite `from` ({} cols) and `on` ({} cols) must be the same length",
10202 fk.from.len(),
10203 fk.on.len(),
10204 )));
10205 }
10206 out.composite_fks.push(fk);
10207 return Ok(());
10208 }
10209 if meta.path.is_ident("m2m") {
10210 let mut m2m = M2MAttr {
10211 name: String::new(),
10212 to: String::new(),
10213 through: String::new(),
10214 src: String::new(),
10215 dst: String::new(),
10216 auto_create: true,
10217 };
10218 meta.parse_nested_meta(|inner| {
10219 if inner.path.is_ident("name") {
10220 let s: LitStr = inner.value()?.parse()?;
10221 m2m.name = s.value();
10222 return Ok(());
10223 }
10224 if inner.path.is_ident("to") {
10225 let s: LitStr = inner.value()?.parse()?;
10226 m2m.to = s.value();
10227 return Ok(());
10228 }
10229 if inner.path.is_ident("through") {
10230 let s: LitStr = inner.value()?.parse()?;
10231 m2m.through = s.value();
10232 return Ok(());
10233 }
10234 if inner.path.is_ident("src") {
10235 let s: LitStr = inner.value()?.parse()?;
10236 m2m.src = s.value();
10237 return Ok(());
10238 }
10239 if inner.path.is_ident("dst") {
10240 let s: LitStr = inner.value()?.parse()?;
10241 m2m.dst = s.value();
10242 return Ok(());
10243 }
10244 if inner.path.is_ident("auto_create") {
10245 let lit: syn::LitBool = inner.value()?.parse()?;
10246 m2m.auto_create = lit.value;
10247 return Ok(());
10248 }
10249 Err(inner.error("unknown m2m attribute (supported: `name`, `to`, `through`, `src`, `dst`, `auto_create`)"))
10250 })?;
10251 if m2m.name.is_empty() {
10252 return Err(meta.error("m2m requires `name = \"...\"`"));
10253 }
10254 if m2m.to.is_empty() {
10255 return Err(meta.error("m2m requires `to = \"...\"`"));
10256 }
10257 if m2m.through.is_empty() {
10258 return Err(meta.error("m2m requires `through = \"...\"`"));
10259 }
10260 if m2m.src.is_empty() {
10261 return Err(meta.error("m2m requires `src = \"...\"`"));
10262 }
10263 if m2m.dst.is_empty() {
10264 return Err(meta.error("m2m requires `dst = \"...\"`"));
10265 }
10266 out.m2m.push(m2m);
10267 return Ok(());
10268 }
10269 if meta.path.is_ident("generic_m2m") {
10270 let mut gm = GenericM2MAttr {
10271 name: String::new(),
10272 through: String::new(),
10273 pk_column: String::new(),
10274 ct_column: String::new(),
10275 related_column: String::new(),
10276 };
10277 meta.parse_nested_meta(|inner| {
10278 let field = |inner: &syn::meta::ParseNestedMeta| -> syn::Result<String> {
10279 let s: LitStr = inner.value()?.parse()?;
10280 Ok(s.value())
10281 };
10282 if inner.path.is_ident("name") {
10283 gm.name = field(&inner)?;
10284 return Ok(());
10285 }
10286 if inner.path.is_ident("through") {
10287 gm.through = field(&inner)?;
10288 return Ok(());
10289 }
10290 if inner.path.is_ident("pk_column") {
10291 gm.pk_column = field(&inner)?;
10292 return Ok(());
10293 }
10294 if inner.path.is_ident("ct_column") {
10295 gm.ct_column = field(&inner)?;
10296 return Ok(());
10297 }
10298 if inner.path.is_ident("related_column") {
10299 gm.related_column = field(&inner)?;
10300 return Ok(());
10301 }
10302 Err(inner.error("unknown generic_m2m attribute (supported: `name`, `through`, `pk_column`, `ct_column`, `related_column`)"))
10303 })?;
10304 for (val, label) in [
10305 (&gm.name, "name"),
10306 (&gm.through, "through"),
10307 (&gm.pk_column, "pk_column"),
10308 (&gm.ct_column, "ct_column"),
10309 (&gm.related_column, "related_column"),
10310 ] {
10311 if val.is_empty() {
10312 return Err(meta.error(format!("generic_m2m requires `{label} = \"...\"`")));
10313 }
10314 }
10315 out.generic_m2m.push(gm);
10316 return Ok(());
10317 }
10318 Err(unknown_key_error(&meta, "container", CONTAINER_ATTR_KEYS))
10319 })?;
10320 }
10321 Ok(out)
10322}
10323
10324fn split_field_list(raw: &str) -> Vec<String> {
10328 raw.split(',')
10329 .map(str::trim)
10330 .filter(|s| !s.is_empty())
10331 .map(str::to_owned)
10332 .collect()
10333}
10334
10335fn parse_together_attr(
10343 meta: &syn::meta::ParseNestedMeta<'_>,
10344 attr: &str,
10345 written: &str,
10346) -> syn::Result<(Vec<String>, Option<String>)> {
10347 if meta.input.peek(syn::Token![=]) {
10350 let cols_lit: LitStr = meta.value()?.parse()?;
10351 let columns = split_field_list(&cols_lit.value());
10352 check_together_columns(meta, attr, written, &columns)?;
10353 return Ok((columns, None));
10354 }
10355 let mut columns: Option<Vec<String>> = None;
10356 let mut name: Option<String> = None;
10357 meta.parse_nested_meta(|inner| {
10358 if inner.path.is_ident("columns") {
10359 let s: LitStr = inner.value()?.parse()?;
10360 columns = Some(split_field_list(&s.value()));
10361 return Ok(());
10362 }
10363 if inner.path.is_ident("name") {
10364 let s: LitStr = inner.value()?.parse()?;
10365 name = Some(s.value());
10366 return Ok(());
10367 }
10368 Err(inner.error("unknown sub-attribute (supported: `columns`, `name`)"))
10369 })?;
10370 let columns = columns.ok_or_else(|| {
10371 meta.error(format!(
10372 "{attr}(...) requires a `columns = \"col1, col2\"` argument",
10373 ))
10374 })?;
10375 check_together_columns(meta, attr, written, &columns)?;
10376 Ok((columns, name))
10377}
10378
10379fn check_together_columns(
10380 meta: &syn::meta::ParseNestedMeta<'_>,
10381 attr: &str,
10382 written: &str,
10383 columns: &[String],
10384) -> syn::Result<()> {
10385 if columns.len() < 2 {
10386 let flag = if attr == "unique_together" {
10387 "unique"
10388 } else {
10389 "index"
10390 };
10391 let single = format!("#[{written}({flag})] on the field");
10392 return Err(meta.error(format!(
10393 "{attr} expects two or more columns; for a single-column equivalent use {single}",
10394 )));
10395 }
10396 Ok(())
10397}
10398
10399fn parse_fieldset_list(raw: &str) -> Vec<(String, Vec<String>)> {
10408 raw.split('|')
10409 .map(str::trim)
10410 .filter(|s| !s.is_empty())
10411 .map(|section| {
10412 let (title, rest) = match section.split_once(':') {
10414 Some((title, rest)) if !title.contains(',') => (title.trim().to_owned(), rest),
10415 _ => (String::new(), section),
10416 };
10417 let fields = split_field_list(rest);
10418 (title, fields)
10419 })
10420 .collect()
10421}
10422
10423fn parse_prepopulated_list(raw: &str) -> Vec<(String, Vec<String>)> {
10428 raw.split(',')
10429 .map(str::trim)
10430 .filter(|s| !s.is_empty())
10431 .filter_map(|entry| {
10432 let (target, sources_raw) = entry.split_once(':')?;
10433 let target = target.trim().to_owned();
10434 if target.is_empty() {
10435 return None;
10436 }
10437 let sources: Vec<String> = sources_raw
10438 .split('+')
10439 .map(|s| s.trim().to_owned())
10440 .filter(|s| !s.is_empty())
10441 .collect();
10442 if sources.is_empty() {
10443 return None;
10444 }
10445 Some((target, sources))
10446 })
10447 .collect()
10448}
10449
10450fn parse_formfield_overrides(raw: &str) -> Vec<(String, String)> {
10456 raw.split(',')
10457 .map(str::trim)
10458 .filter(|s| !s.is_empty())
10459 .filter_map(|entry| {
10460 let (field, widget) = entry.split_once(':')?;
10461 let field = field.trim().to_owned();
10462 let widget = widget.trim().to_owned();
10463 if field.is_empty() || widget.is_empty() {
10464 return None;
10465 }
10466 Some((field, widget))
10467 })
10468 .collect()
10469}
10470
10471fn parse_ordering_list(raw: &str) -> Vec<(String, bool)> {
10474 raw.split(',')
10475 .map(str::trim)
10476 .filter(|s| !s.is_empty())
10477 .map(|spec| {
10478 spec.strip_prefix('-')
10479 .map_or((spec.to_owned(), false), |rest| {
10480 (rest.trim().to_owned(), true)
10481 })
10482 })
10483 .collect()
10484}
10485
10486struct FieldAttrs {
10487 column: Option<String>,
10488 primary_key: bool,
10489 fk: Option<String>,
10490 o2o: Option<String>,
10491 on: Option<String>,
10492 on_delete: Option<String>,
10502 related_name: Option<String>,
10510 max_length: Option<u32>,
10511 vector_dims: Option<u32>,
10515 geometry_srid: Option<u32>,
10519 min: Option<i64>,
10520 max: Option<i64>,
10521 default: Option<String>,
10522 auto_uuid: bool,
10528 default_uuid_v7: bool,
10539 auto_now_add: bool,
10544 auto_now: bool,
10550 soft_delete: bool,
10555 unique: bool,
10558 index: bool,
10562 index_unique: bool,
10563 index_name: Option<String>,
10564 index_method: String,
10567 generated_as: Option<String>,
10573 help_text: Option<String>,
10578 choices: Option<Vec<(String, String)>>,
10586 db_comment: Option<String>,
10592 verbose_name: Option<String>,
10598 editable: bool,
10605 blank: bool,
10609 case_insensitive: bool,
10614 validators: Vec<syn::Ident>,
10620 secret: Option<bool>,
10622}
10623
10624const FIELD_ATTR_KEYS: &[&str] = &[
10626 "column",
10627 "primary_key",
10628 "fk",
10629 "o2o",
10630 "on",
10631 "on_delete",
10632 "related_name",
10633 "max_length",
10634 "vector",
10635 "geometry",
10636 "min",
10637 "max",
10638 "default",
10639 "generated_as",
10640 "help_text",
10641 "choices",
10642 "db_comment",
10643 "verbose_name",
10644 "editable",
10645 "secret",
10646 "blank",
10647 "citext",
10648 "validators",
10649 "auto_uuid",
10650 "default_uuid_v7",
10651 "auto_now_add",
10652 "auto_now",
10653 "soft_delete",
10654 "unique",
10655 "index",
10656];
10657
10658const CONTAINER_ATTR_KEYS: &[&str] = &[
10660 "table",
10661 "display",
10662 "app",
10663 "scope",
10664 "admin",
10665 "manager",
10666 "manager_fn",
10667 "default_order",
10668 "global_scope",
10669 "through",
10670 "reverse_has",
10671 "generic_has",
10672 "audit",
10673 "permissions",
10674 "view",
10675 "managed",
10676 "verbose_name",
10677 "verbose_name_plural",
10678 "db_table_comment",
10679 "proxy",
10680 "order_with_respect_to",
10681 "required_db_features",
10682 "required_db_vendor",
10683 "base_manager_name",
10684 "default_related_name",
10685 "extra_permissions",
10686 "default_permissions",
10687 "get_latest_by",
10688 "unique_together",
10689 "index_together",
10690 "unique_when",
10691 "index_when",
10692 "index",
10693 "check",
10694 "exclude",
10695 "generic_fk",
10696 "fk_composite",
10697 "m2m",
10698 "generic_m2m",
10699];
10700
10701fn parse_flag(meta: &syn::meta::ParseNestedMeta<'_>, key: &str) -> syn::Result<()> {
10704 if meta.input.peek(syn::Token![=]) {
10705 let lit: syn::LitBool = meta.value()?.parse()?;
10706 if !lit.value {
10707 return Err(syn::Error::new(
10708 lit.span(),
10709 format!("`{key} = false` is the default; remove the attribute"),
10710 ));
10711 }
10712 }
10713 Ok(())
10714}
10715
10716fn unknown_key_error(
10718 meta: &syn::meta::ParseNestedMeta<'_>,
10719 what: &str,
10720 keys: &[&str],
10721) -> syn::Error {
10722 let path = &meta.path;
10723 let got = path
10724 .get_ident()
10725 .map_or_else(|| quote!(#path).to_string(), ToString::to_string);
10726 let mut msg = format!("unknown rustango {what} attribute `{got}`");
10727 if let Some(near) = keys
10728 .iter()
10729 .map(|k| (edit_distance(&got, k), *k))
10730 .filter(|(d, k)| *d <= 2.max(k.len() / 4))
10731 .min_by_key(|(d, _)| *d)
10732 .map(|(_, k)| k)
10733 {
10734 msg.push_str(&format!("; did you mean `{near}`?"));
10735 }
10736 msg.push_str(&format!(" Supported: {}", keys.join(", ")));
10737 meta.error(msg)
10738}
10739
10740fn edit_distance(a: &str, b: &str) -> usize {
10742 let b: Vec<char> = b.chars().collect();
10743 let mut prev: Vec<usize> = (0..=b.len()).collect();
10744 for (i, ca) in a.chars().enumerate() {
10745 let mut cur = vec![i + 1];
10746 for (j, cb) in b.iter().enumerate() {
10747 let sub = prev[j] + usize::from(ca != *cb);
10748 cur.push(sub.min(prev[j + 1] + 1).min(cur[j] + 1));
10749 }
10750 prev = cur;
10751 }
10752 prev[b.len()]
10753}
10754
10755fn parse_field_attrs(field: &syn::Field) -> syn::Result<FieldAttrs> {
10756 let mut out = FieldAttrs {
10757 column: None,
10758 primary_key: false,
10759 fk: None,
10760 o2o: None,
10761 on: None,
10762 on_delete: None,
10763 related_name: None,
10764 max_length: None,
10765 vector_dims: None,
10766 geometry_srid: None,
10767 min: None,
10768 max: None,
10769 default: None,
10770 auto_uuid: false,
10771 default_uuid_v7: false,
10772 auto_now_add: false,
10773 auto_now: false,
10774 soft_delete: false,
10775 unique: false,
10776 index: false,
10777 index_unique: false,
10778 index_name: None,
10779 index_method: "btree".to_owned(),
10780 generated_as: None,
10781 help_text: None,
10782 choices: None,
10783 db_comment: None,
10784 verbose_name: None,
10785 editable: true,
10786 blank: false,
10787 case_insensitive: false,
10788 validators: Vec::new(),
10789 secret: None,
10790 };
10791 for attr in model_attrs(&field.attrs)? {
10792 attr.parse_nested_meta(|meta| {
10793 if meta.path.is_ident("column") {
10794 let s: LitStr = meta.value()?.parse()?;
10795 let name = s.value();
10796 validate_sql_identifier(&name, "column", s.span())?;
10797 out.column = Some(name);
10798 return Ok(());
10799 }
10800 if meta.path.is_ident("primary_key") {
10801 parse_flag(&meta, "primary_key")?;
10802 out.primary_key = true;
10803 return Ok(());
10804 }
10805 if meta.path.is_ident("fk") {
10806 let s: LitStr = meta.value()?.parse()?;
10807 out.fk = Some(s.value());
10808 return Ok(());
10809 }
10810 if meta.path.is_ident("o2o") {
10811 let s: LitStr = meta.value()?.parse()?;
10812 out.o2o = Some(s.value());
10813 return Ok(());
10814 }
10815 if meta.path.is_ident("on") {
10816 let s: LitStr = meta.value()?.parse()?;
10817 out.on = Some(s.value());
10818 return Ok(());
10819 }
10820 if meta.path.is_ident("on_delete") {
10821 let s: LitStr = meta.value()?.parse()?;
10822 let raw = s.value();
10823 let normalized = raw.trim().to_ascii_lowercase();
10824 match normalized.as_str() {
10827 "cascade" | "restrict" | "set_null" | "set_default" | "no_action" => {}
10828 _ => {
10829 return Err(syn::Error::new(
10830 s.span(),
10831 format!(
10832 "unknown on_delete action `{raw}`; expected one of \
10833 `cascade`, `restrict`, `set_null`, `set_default`, `no_action`"
10834 ),
10835 ));
10836 }
10837 }
10838 out.on_delete = Some(normalized);
10839 return Ok(());
10840 }
10841 if meta.path.is_ident("related_name") {
10842 let s: LitStr = meta.value()?.parse()?;
10843 let raw = s.value();
10844 if raw.trim().is_empty() {
10845 return Err(syn::Error::new(
10846 s.span(),
10847 "`related_name` must be a non-empty identifier",
10848 ));
10849 }
10850 let snake = raw
10855 .chars()
10856 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
10857 && !raw.starts_with(char::is_numeric);
10858 if !snake && raw != NO_REVERSE_ACCESSOR {
10859 return Err(syn::Error::new(
10860 s.span(),
10861 "`related_name` must be snake_case ASCII (lowercase letters, \
10862 digits, underscores; no leading digit), or \"+\" for none",
10863 ));
10864 }
10865 out.related_name = Some(raw);
10866 return Ok(());
10867 }
10868 if meta.path.is_ident("max_length") {
10869 let lit: syn::LitInt = meta.value()?.parse()?;
10870 out.max_length = Some(lit.base10_parse::<u32>()?);
10871 return Ok(());
10872 }
10873 if meta.path.is_ident("vector") {
10877 meta.parse_nested_meta(|inner| {
10878 if inner.path.is_ident("dims") {
10879 let lit: syn::LitInt = inner.value()?.parse()?;
10880 out.vector_dims = Some(lit.base10_parse::<u32>()?);
10881 return Ok(());
10882 }
10883 Err(inner.error("unknown `vector` attribute (supported: `dims`)"))
10884 })?;
10885 return Ok(());
10886 }
10887 if meta.path.is_ident("geometry") {
10890 meta.parse_nested_meta(|inner| {
10891 if inner.path.is_ident("srid") {
10892 let lit: syn::LitInt = inner.value()?.parse()?;
10893 out.geometry_srid = Some(lit.base10_parse::<u32>()?);
10894 return Ok(());
10895 }
10896 Err(inner.error("unknown `geometry` attribute (supported: `srid`)"))
10897 })?;
10898 return Ok(());
10899 }
10900 if meta.path.is_ident("min") {
10901 out.min = Some(parse_signed_i64(&meta)?);
10902 return Ok(());
10903 }
10904 if meta.path.is_ident("max") {
10905 out.max = Some(parse_signed_i64(&meta)?);
10906 return Ok(());
10907 }
10908 if meta.path.is_ident("default") {
10909 let s: LitStr = meta.value()?.parse()?;
10910 out.default = Some(s.value());
10911 return Ok(());
10912 }
10913 if meta.path.is_ident("generated_as") {
10914 let s: LitStr = meta.value()?.parse()?;
10915 out.generated_as = Some(s.value());
10916 return Ok(());
10917 }
10918 if meta.path.is_ident("help_text") {
10919 let s: LitStr = meta.value()?.parse()?;
10920 out.help_text = Some(s.value());
10921 return Ok(());
10922 }
10923 if meta.path.is_ident("choices") {
10924 let s: LitStr = meta.value()?.parse()?;
10925 let raw = s.value();
10926 let mut pairs: Vec<(String, String)> = Vec::new();
10927 for chunk in raw.split(',') {
10928 let chunk = chunk.trim();
10929 if chunk.is_empty() {
10930 continue;
10931 }
10932 let (value, label) = match chunk.split_once(':') {
10933 Some((v, l)) => (v.trim().to_owned(), l.trim().to_owned()),
10934 None => (chunk.to_owned(), chunk.to_owned()),
10935 };
10936 if value.is_empty() {
10937 return Err(syn::Error::new(
10938 s.span(),
10939 "`choices` entry has empty value before `:`",
10940 ));
10941 }
10942 pairs.push((value, label));
10943 }
10944 if pairs.is_empty() {
10945 return Err(syn::Error::new(
10946 s.span(),
10947 "`choices = \"…\"` must contain at least one value",
10948 ));
10949 }
10950 out.choices = Some(pairs);
10951 return Ok(());
10952 }
10953 if meta.path.is_ident("db_comment") {
10954 let s: LitStr = meta.value()?.parse()?;
10955 out.db_comment = Some(s.value());
10956 return Ok(());
10957 }
10958 if meta.path.is_ident("verbose_name") {
10959 let s: LitStr = meta.value()?.parse()?;
10960 out.verbose_name = Some(s.value());
10961 return Ok(());
10962 }
10963 if meta.path.is_ident("secret") {
10964 out.secret = Some(if let Ok(v) = meta.value() {
10966 v.parse::<syn::LitBool>()?.value
10967 } else {
10968 true
10969 });
10970 return Ok(());
10971 }
10972 if meta.path.is_ident("editable") {
10973 if let Ok(v) = meta.value() {
10978 let lit: syn::LitBool = v.parse()?;
10979 out.editable = lit.value;
10980 } else {
10981 out.editable = true;
10982 }
10983 return Ok(());
10984 }
10985 if meta.path.is_ident("blank") {
10986 if let Ok(v) = meta.value() {
10990 let lit: syn::LitBool = v.parse()?;
10991 out.blank = lit.value;
10992 } else {
10993 out.blank = true;
10994 }
10995 return Ok(());
10996 }
10997 if meta.path.is_ident("citext") {
10998 if let Ok(v) = meta.value() {
11007 let lit: syn::LitBool = v.parse()?;
11008 out.case_insensitive = lit.value;
11009 } else {
11010 out.case_insensitive = true;
11011 }
11012 return Ok(());
11013 }
11014 if meta.path.is_ident("validators") {
11015 let s: LitStr = meta.value()?.parse()?;
11016 let raw = s.value();
11017 out.validators = raw
11018 .split(',')
11019 .map(str::trim)
11020 .filter(|s| !s.is_empty())
11021 .map(|name| {
11022 let mut ident = syn::parse_str::<syn::Ident>(name).map_err(|_| {
11023 syn::Error::new(s.span(), format!("unknown validator `{name}`"))
11024 })?;
11025 ident.set_span(s.span());
11026 Ok(ident)
11027 })
11028 .collect::<syn::Result<_>>()?;
11029 if out.validators.is_empty() {
11030 return Err(syn::Error::new(
11031 s.span(),
11032 "`validators = \"…\"` must list at least one name",
11033 ));
11034 }
11035 return Ok(());
11036 }
11037 if meta.path.is_ident("auto_uuid") {
11038 parse_flag(&meta, "auto_uuid")?;
11039 out.auto_uuid = true;
11040 out.primary_key = true;
11044 if out.default.is_none() {
11045 out.default = Some("gen_random_uuid()".into());
11046 }
11047 return Ok(());
11048 }
11049 if meta.path.is_ident("default_uuid_v7") {
11050 parse_flag(&meta, "default_uuid_v7")?;
11051 out.default_uuid_v7 = true;
11057 out.primary_key = true;
11058 return Ok(());
11059 }
11060 if meta.path.is_ident("auto_now_add") {
11061 parse_flag(&meta, "auto_now_add")?;
11062 out.auto_now_add = true;
11063 if out.default.is_none() {
11064 out.default = Some("now()".into());
11065 }
11066 return Ok(());
11067 }
11068 if meta.path.is_ident("auto_now") {
11069 parse_flag(&meta, "auto_now")?;
11070 out.auto_now = true;
11071 if out.default.is_none() {
11072 out.default = Some("now()".into());
11073 }
11074 return Ok(());
11075 }
11076 if meta.path.is_ident("soft_delete") {
11077 parse_flag(&meta, "soft_delete")?;
11078 out.soft_delete = true;
11079 return Ok(());
11080 }
11081 if meta.path.is_ident("unique") {
11082 parse_flag(&meta, "unique")?;
11083 out.unique = true;
11084 return Ok(());
11085 }
11086 if meta.path.is_ident("index") {
11087 parse_flag(&meta, "index")?;
11088 out.index = true;
11089 if meta.input.peek(syn::token::Paren) {
11091 meta.parse_nested_meta(|inner| {
11092 if inner.path.is_ident("unique") {
11093 out.index_unique = true;
11094 return Ok(());
11095 }
11096 if inner.path.is_ident("name") {
11097 let s: LitStr = inner.value()?.parse()?;
11098 out.index_name = Some(s.value());
11099 return Ok(());
11100 }
11101 if inner.path.is_ident("method") {
11102 let s: LitStr = inner.value()?.parse()?;
11103 let v = s.value();
11104 match v.as_str() {
11105 "btree" | "gin" | "gist" | "brin" | "spgist" | "hash" | "bloom" => {
11106 out.index_method = v;
11107 }
11108 other => {
11109 return Err(inner.error(format!(
11110 "unknown index method `{other}` (supported: btree, gin, gist, brin, spgist, hash, bloom)",
11111 )));
11112 }
11113 }
11114 return Ok(());
11115 }
11116 Err(inner.error(
11117 "unknown index sub-attribute (supported: `unique`, `name`, `method`)",
11118 ))
11119 })?;
11120 }
11121 return Ok(());
11122 }
11123 Err(unknown_key_error(&meta, "field", FIELD_ATTR_KEYS))
11124 })?;
11125 }
11126 Ok(out)
11127}
11128
11129fn parse_signed_i64(meta: &syn::meta::ParseNestedMeta<'_>) -> syn::Result<i64> {
11131 let expr: syn::Expr = meta.value()?.parse()?;
11132 match expr {
11133 syn::Expr::Lit(syn::ExprLit {
11134 lit: syn::Lit::Int(lit),
11135 ..
11136 }) => lit.base10_parse::<i64>(),
11137 syn::Expr::Unary(syn::ExprUnary {
11138 op: syn::UnOp::Neg(_),
11139 expr,
11140 ..
11141 }) => {
11142 if let syn::Expr::Lit(syn::ExprLit {
11143 lit: syn::Lit::Int(lit),
11144 ..
11145 }) = *expr
11146 {
11147 let v: i64 = lit.base10_parse()?;
11148 Ok(-v)
11149 } else {
11150 Err(syn::Error::new_spanned(expr, "expected integer literal"))
11151 }
11152 }
11153 other => Err(syn::Error::new_spanned(
11154 other,
11155 "expected integer literal (signed)",
11156 )),
11157 }
11158}
11159
11160struct FieldInfo<'a> {
11161 ident: &'a syn::Ident,
11162 column: String,
11163 primary_key: bool,
11164 auto: bool,
11168 value_ty: &'a Type,
11171 field_type_tokens: TokenStream2,
11173 schema: TokenStream2,
11174 from_row_init: TokenStream2,
11175 from_aliased_row_init: TokenStream2,
11181 from_row_init_my: TokenStream2,
11184 from_aliased_row_init_my: TokenStream2,
11185 uuid_column: bool,
11187 fk_inner: Option<Type>,
11191 fk_pk_kind: DetectedKind,
11197 nullable: bool,
11205 auto_now: bool,
11211 auto_now_add: bool,
11217 soft_delete: bool,
11222 generated_as: Option<String>,
11227 default_uuid_v7: bool,
11234 related_name: Option<String>,
11239}
11240
11241fn validate_table_name(name: &str, span: proc_macro2::Span) -> syn::Result<()> {
11255 validate_sql_identifier(name, "table", span)
11256}
11257
11258fn validate_sql_identifier(name: &str, kind: &str, span: proc_macro2::Span) -> syn::Result<()> {
11263 if name.is_empty() {
11264 return Err(syn::Error::new(
11265 span,
11266 format!("`{kind} = \"\"` is not a valid SQL identifier"),
11267 ));
11268 }
11269 let mut chars = name.chars();
11270 let first = chars.next().unwrap();
11271 if !(first.is_ascii_alphabetic() || first == '_') {
11272 return Err(syn::Error::new(
11273 span,
11274 format!("{kind} name `{name}` must start with a letter or underscore (got {first:?})"),
11275 ));
11276 }
11277 for c in chars {
11278 if !(c.is_ascii_alphanumeric() || c == '_') {
11279 return Err(syn::Error::new(
11280 span,
11281 format!(
11282 "{kind} name `{name}` contains invalid character {c:?} — \
11283 SQL identifiers must match `[a-zA-Z_][a-zA-Z0-9_]*`. \
11284 Hyphens in particular break FK / index name derivation \
11285 downstream; use underscores instead (e.g. `{}`)",
11286 name.replace(|x: char| !x.is_ascii_alphanumeric() && x != '_', "_"),
11287 ),
11288 ));
11289 }
11290 }
11291 Ok(())
11292}
11293
11294fn process_field<'a>(field: &'a syn::Field, table: &str) -> syn::Result<FieldInfo<'a>> {
11295 let root = rustango_root();
11296 let attrs = parse_field_attrs(field)?;
11297 let ident = field
11298 .ident
11299 .as_ref()
11300 .ok_or_else(|| syn::Error::new(field.span(), "tuple structs are not supported"))?;
11301 let name = ident_name(ident);
11302 let column = attrs.column.clone().unwrap_or_else(|| name.clone());
11303 let primary_key = attrs.primary_key;
11304 let DetectedType {
11305 kind,
11306 nullable,
11307 auto: detected_auto,
11308 fk_inner,
11309 } = detect_type(&field.ty)?;
11310 check_bound_compatibility(field, &attrs, kind)?;
11311 let auto = detected_auto;
11312 if attrs.auto_uuid {
11318 if kind != DetectedKind::Uuid {
11319 return Err(syn::Error::new_spanned(
11320 field,
11321 "`#[rustango(auto_uuid)]` requires the field type to be \
11322 `Auto<uuid::Uuid>`",
11323 ));
11324 }
11325 if !detected_auto {
11326 return Err(syn::Error::new_spanned(
11327 field,
11328 "`#[rustango(auto_uuid)]` requires the field type to be \
11329 wrapped in `Auto<...>` so the macro skips the column on \
11330 INSERT and the DB DEFAULT (`gen_random_uuid()`) fires",
11331 ));
11332 }
11333 }
11334 if attrs.default_uuid_v7 {
11335 if kind != DetectedKind::Uuid {
11336 return Err(syn::Error::new_spanned(
11337 field,
11338 "`#[rustango(default_uuid_v7)]` requires the field type to be \
11339 `Auto<uuid::Uuid>`",
11340 ));
11341 }
11342 if !detected_auto {
11343 return Err(syn::Error::new_spanned(
11344 field,
11345 "`#[rustango(default_uuid_v7)]` requires the field type to be \
11346 wrapped in `Auto<...>` so the macro can detect the \
11347 unset-vs-set state and fill a fresh UUIDv7 before INSERT",
11348 ));
11349 }
11350 if attrs.auto_uuid {
11351 return Err(syn::Error::new_spanned(
11352 field,
11353 "`#[rustango(default_uuid_v7)]` is mutually exclusive with \
11354 `#[rustango(auto_uuid)]` — the former generates the UUID \
11355 Rust-side, the latter relies on the DB's `gen_random_uuid()`. \
11356 Pick one.",
11357 ));
11358 }
11359 }
11360 if attrs.auto_now_add || attrs.auto_now {
11361 if kind != DetectedKind::DateTime {
11362 return Err(syn::Error::new_spanned(
11363 field,
11364 "`#[rustango(auto_now_add)]` / `#[rustango(auto_now)]` require \
11365 the field type to be `Auto<chrono::DateTime<chrono::Utc>>`",
11366 ));
11367 }
11368 if !detected_auto {
11369 return Err(syn::Error::new_spanned(
11370 field,
11371 "`#[rustango(auto_now_add)]` / `#[rustango(auto_now)]` require \
11372 the field type to be wrapped in `Auto<...>` so the macro skips \
11373 the column on INSERT and the DB DEFAULT (`now()`) fires",
11374 ));
11375 }
11376 }
11377 if attrs.soft_delete && !(kind == DetectedKind::DateTime && nullable) {
11378 return Err(syn::Error::new_spanned(
11379 field,
11380 "`#[rustango(soft_delete)]` requires the field type to be \
11381 `Option<chrono::DateTime<chrono::Utc>>`",
11382 ));
11383 }
11384 let is_mixin_auto =
11385 attrs.auto_uuid || attrs.default_uuid_v7 || attrs.auto_now_add || attrs.auto_now;
11386 if detected_auto && !primary_key && !is_mixin_auto {
11387 return Err(syn::Error::new_spanned(
11388 field,
11389 "`Auto<T>` is only valid on a `#[rustango(primary_key)]` field, \
11390 or on a field carrying one of `auto_uuid`, `auto_now_add`, or \
11391 `auto_now`",
11392 ));
11393 }
11394 if detected_auto && attrs.default.is_some() && !is_mixin_auto {
11395 return Err(syn::Error::new_spanned(
11396 field,
11397 "`#[rustango(default = \"…\")]` is redundant on an `Auto<T>` field — \
11398 SERIAL / BIGSERIAL already supplies a default sequence.",
11399 ));
11400 }
11401 if fk_inner.is_some() && primary_key {
11402 return Err(syn::Error::new_spanned(
11403 field,
11404 "`ForeignKey<T>` is not allowed on a primary-key field — \
11405 a row's PK is its own identity, not a reference to a parent.",
11406 ));
11407 }
11408 if attrs.generated_as.is_some() {
11409 if primary_key {
11410 return Err(syn::Error::new_spanned(
11411 field,
11412 "`#[rustango(generated_as = \"…\")]` is not allowed on a \
11413 primary-key field — a PK must be writable so the row \
11414 has an identity at INSERT time.",
11415 ));
11416 }
11417 if attrs.default.is_some() {
11418 return Err(syn::Error::new_spanned(
11419 field,
11420 "`#[rustango(generated_as = \"…\")]` cannot combine with \
11421 `default = \"…\"` — Postgres rejects DEFAULT on \
11422 generated columns. The expression IS the default.",
11423 ));
11424 }
11425 if detected_auto {
11426 return Err(syn::Error::new_spanned(
11427 field,
11428 "`#[rustango(generated_as = \"…\")]` is not allowed on \
11429 an `Auto<T>` field — generated columns are computed \
11430 by the DB, not server-assigned via a sequence. Use a \
11431 plain Rust type (e.g. `f64`).",
11432 ));
11433 }
11434 if fk_inner.is_some() {
11435 return Err(syn::Error::new_spanned(
11436 field,
11437 "`#[rustango(generated_as = \"…\")]` is not allowed on a \
11438 ForeignKey field.",
11439 ));
11440 }
11441 }
11442 let relation = relation_tokens(field, &attrs, fk_inner, table)?;
11443 let column_lit = column.as_str();
11444 let field_type_tokens = if kind == DetectedKind::Vector {
11449 let root = rustango_root();
11450 let dims = attrs.vector_dims.unwrap_or(0);
11451 quote!(#root::core::FieldType::Vector(#dims))
11452 } else if kind == DetectedKind::Geometry {
11453 let root = rustango_root();
11457 let srid = attrs.geometry_srid.unwrap_or(0);
11458 quote!(#root::core::FieldType::Geometry(#srid))
11459 } else {
11460 kind.variant_tokens()
11461 };
11462 let max_length = optional_u32(attrs.max_length);
11463 let min = optional_i64(attrs.min);
11464 let max = optional_i64(attrs.max);
11465 let default = optional_str(attrs.default.as_deref());
11466
11467 let unique = attrs.unique;
11468 let generated_as = optional_str(attrs.generated_as.as_deref());
11469 let help_text = optional_str(attrs.help_text.as_deref());
11470 let choices = optional_choices(attrs.choices.as_deref());
11471 let db_comment = optional_str(attrs.db_comment.as_deref());
11472 let verbose_name = optional_str(attrs.verbose_name.as_deref());
11473 let editable = attrs.editable;
11474 let secret = match attrs.secret {
11475 Some(v) => quote!(::core::option::Option::Some(#v)),
11476 None => quote!(::core::option::Option::None),
11477 };
11478 let blank = attrs.blank;
11479 let case_insensitive = attrs.case_insensitive;
11480 let validators = &attrs.validators;
11482 if attrs.on_delete.is_some() && attrs.fk.is_none() && attrs.o2o.is_none() && fk_inner.is_none()
11483 {
11484 return Err(syn::Error::new_spanned(
11485 field,
11486 "`#[rustango(on_delete = \"…\")]` requires a `ForeignKey<T>` field \
11487 or `fk = \"<table>\"` / `o2o = \"<table>\"` on the same field — it \
11488 has no meaning on a non-FK column.",
11489 ));
11490 }
11491 if attrs.on_delete.as_deref() == Some("set_null") && !nullable {
11492 return Err(syn::Error::new_spanned(
11493 field,
11494 "`on_delete = \"set_null\"` needs a nullable column: use \
11495 `Option<ForeignKey<T>>` or `Option<…>` (spelled out; a type \
11496 alias for `Option` is not seen).",
11497 ));
11498 }
11499 let fk_on_delete = match attrs.on_delete.as_deref() {
11500 None => quote!(::core::option::Option::None),
11501 Some(action) => {
11502 let variant = match action {
11503 "cascade" => quote!(Cascade),
11504 "restrict" => quote!(Restrict),
11505 "set_null" => quote!(SetNull),
11506 "set_default" => quote!(SetDefault),
11507 "no_action" => quote!(NoAction),
11508 other => unreachable!("on_delete `{other}` should have been rejected at parse"),
11510 };
11511 quote!(::core::option::Option::Some(
11512 #root::core::OnDeleteAction::#variant
11513 ))
11514 }
11515 };
11516 let auto_now_flag = attrs.auto_now;
11517 let cast = match cast_type(&field.ty) {
11518 Some(ty) => quote!(::core::option::Option::Some(<#ty>::FIELD_CAST)),
11519 None => quote!(::core::option::Option::None),
11520 };
11521 let sealed_guard = match (attrs.secret, cast_type(&field.ty)) {
11523 (Some(false), Some(ty)) => quote! {
11524 const _: () = ::core::assert!(
11525 !::core::matches!(<#ty>::FIELD_CAST, #root::core::FieldCast::Sealed),
11526 "`#[rustango(secret = false)]` on a sealed cast: it is always a credential",
11527 );
11528 },
11529 _ => quote!(),
11530 };
11531 let schema = quote! {{
11532 #sealed_guard
11533 #root::core::FieldSchema::from_parts(#root::core::FieldSchemaParts {
11534 name: #name,
11535 column: #column_lit,
11536 ty: #field_type_tokens,
11537 nullable: #nullable,
11538 primary_key: #primary_key,
11539 relation: #relation,
11540 max_length: #max_length,
11541 min: #min,
11542 max: #max,
11543 default: #default,
11544 auto: #auto,
11545 auto_now: #auto_now_flag,
11546 unique: #unique,
11547 generated_as: #generated_as,
11548 help_text: #help_text,
11549 choices: #choices,
11550 db_comment: #db_comment,
11551 verbose_name: #verbose_name,
11552 editable: #editable,
11553 blank: #blank,
11554 case_insensitive: #case_insensitive,
11555 fk_on_delete: #fk_on_delete,
11556 validators: &[ #( #root::core::__validator_names::#validators ),* ],
11557 cast: #cast,
11558 secret: #secret,
11559 })
11560 }};
11561
11562 let decimal_column = kind == DetectedKind::Decimal && !detected_auto && fk_inner.is_none();
11564 let (from_row_init, from_aliased_row_init) = if decimal_column {
11565 (
11566 quote! { #ident: #root::sql::try_get_flat(row, #column_lit)? },
11567 quote! {
11568 #ident: #root::sql::try_get_flat(
11569 row,
11570 #root::core::joins::joined_label(prefix, #column_lit).as_str(),
11571 )?
11572 },
11573 )
11574 } else {
11575 (
11576 quote! { #ident: #root::sql::sqlx::Row::try_get(row, #column_lit)? },
11577 quote! {
11578 #ident: #root::sql::sqlx::Row::try_get(
11579 row,
11580 #root::core::joins::joined_label(prefix, #column_lit).as_str(),
11581 )?
11582 },
11583 )
11584 };
11585 let uuid_column = kind == DetectedKind::Uuid && !detected_auto && fk_inner.is_none();
11588 let (from_row_init_my, from_aliased_row_init_my) = if uuid_column {
11589 (
11590 quote! { #ident: #root::sql::try_get_flat_my(row, #column_lit)? },
11591 quote! {
11592 #ident: #root::sql::try_get_flat_my(
11593 row,
11594 #root::core::joins::joined_label(prefix, #column_lit).as_str(),
11595 )?
11596 },
11597 )
11598 } else {
11599 (from_row_init.clone(), from_aliased_row_init.clone())
11600 };
11601
11602 Ok(FieldInfo {
11603 ident,
11604 column,
11605 primary_key,
11606 auto,
11607 value_ty: &field.ty,
11608 field_type_tokens,
11609 schema,
11610 from_row_init,
11611 from_aliased_row_init,
11612 from_row_init_my,
11613 from_aliased_row_init_my,
11614 uuid_column,
11615 fk_inner: fk_inner.cloned(),
11616 fk_pk_kind: kind,
11617 nullable,
11618 auto_now: attrs.auto_now,
11619 auto_now_add: attrs.auto_now_add,
11620 soft_delete: attrs.soft_delete,
11621 generated_as: attrs.generated_as.clone(),
11622 default_uuid_v7: attrs.default_uuid_v7,
11623 related_name: attrs.related_name.clone(),
11624 })
11625}
11626
11627fn check_bound_compatibility(
11628 field: &syn::Field,
11629 attrs: &FieldAttrs,
11630 kind: DetectedKind,
11631) -> syn::Result<()> {
11632 if attrs.max_length.is_some() && kind != DetectedKind::String {
11633 return Err(syn::Error::new_spanned(
11634 field,
11635 "`max_length` is only valid on `String` fields (or `Option<String>`)",
11636 ));
11637 }
11638 if attrs.case_insensitive && kind != DetectedKind::String {
11640 return Err(syn::Error::new_spanned(
11641 field,
11642 "`citext` is only valid on `String` fields (or `Option<String>`)",
11643 ));
11644 }
11645 if attrs.vector_dims.is_some() && kind != DetectedKind::Vector {
11646 return Err(syn::Error::new_spanned(
11647 field,
11648 "`vector(dims = …)` is only valid on a pgvector `Vector` field",
11649 ));
11650 }
11651 if attrs.geometry_srid.is_some() && kind != DetectedKind::Geometry {
11652 return Err(syn::Error::new_spanned(
11653 field,
11654 "`geometry(srid = …)` is only valid on a PostGIS `Point` field",
11655 ));
11656 }
11657 if attrs.choices.is_some() && kind != DetectedKind::String {
11658 return Err(syn::Error::new_spanned(
11659 field,
11660 "`choices` is only valid on `String` fields (or `Option<String>`) — \
11661 integer-valued enumerations should be modeled with a Rust enum and \
11662 custom (de)serializer for now",
11663 ));
11664 }
11665 if !attrs.validators.is_empty() && kind != DetectedKind::String {
11667 return Err(syn::Error::new_spanned(
11668 field,
11669 "`validators` is only valid on `String` fields (or `Option<String>`)",
11670 ));
11671 }
11672 if (attrs.min.is_some() || attrs.max.is_some()) && !kind.is_integer() {
11673 return Err(syn::Error::new_spanned(
11674 field,
11675 "`min` / `max` are only valid on integer fields (`i32`, `i64`, optionally Option-wrapped)",
11676 ));
11677 }
11678 if let (Some(min), Some(max)) = (attrs.min, attrs.max) {
11679 if min > max {
11680 return Err(syn::Error::new_spanned(
11681 field,
11682 format!("`min` ({min}) is greater than `max` ({max})"),
11683 ));
11684 }
11685 }
11686 if let Some(default) = &attrs.default {
11687 check_default_expr(default, kind).map_err(|msg| syn::Error::new_spanned(field, msg))?;
11688 }
11689 Ok(())
11690}
11691
11692const DEFAULT_KEYWORDS: &[&str] = &[
11695 "NULL",
11696 "TRUE",
11697 "FALSE",
11698 "CURRENT_TIMESTAMP",
11699 "CURRENT_DATE",
11700 "CURRENT_TIME",
11701 "LOCALTIMESTAMP",
11702 "LOCALTIME",
11703];
11704
11705fn check_default_expr(default: &str, kind: DetectedKind) -> Result<(), String> {
11709 let expr = default.trim();
11710 let is_word = expr.starts_with(|c: char| c.is_ascii_alphabetic() || c == '_')
11711 && expr
11712 .chars()
11713 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == ' ');
11714 let upper = expr.to_ascii_uppercase();
11715 if is_word && ["CURRENT_USER", "SESSION_USER", "USER"].contains(&upper.as_str()) {
11716 return Err(format!(
11717 "`default = \"{expr}\"` works on Postgres only: MySQL refuses it as a default \
11718 and SQLite stores the text `{expr}`; set the value in code instead"
11719 ));
11720 }
11721 if is_word && !DEFAULT_KEYWORDS.contains(&upper.as_str()) {
11722 return Err(format!(
11723 "`default = \"{expr}\"` is raw SQL, so `{expr}` is read as a column name; \
11724 for the text '{expr}' write `default = \"'{expr}'\"`"
11725 ));
11726 }
11727 let numeric = kind.is_integer()
11728 || matches!(
11729 kind,
11730 DetectedKind::F32 | DetectedKind::F64 | DetectedKind::Decimal
11731 );
11732 if numeric {
11733 let unquoted = expr
11734 .strip_prefix('\'')
11735 .and_then(|e| e.strip_suffix('\''))
11736 .unwrap_or(expr);
11737 let is_number = if kind.is_integer() {
11738 unquoted.parse::<i64>().is_ok()
11739 } else {
11740 unquoted.parse::<f64>().is_ok_and(f64::is_finite)
11741 };
11742 let is_literal = is_word || expr.is_empty() || expr.starts_with('\'');
11745 if is_literal && !is_number && upper != "NULL" {
11746 return Err(format!(
11747 "`default = \"{expr}\"` is not a number, but the field is numeric"
11748 ));
11749 }
11750 }
11751 Ok(())
11752}
11753
11754fn const_slice(ty: &TokenStream2, items: impl Iterator<Item = TokenStream2>) -> TokenStream2 {
11757 quote!({
11758 const ITEMS: &[#ty] = &[ #(#items),* ];
11759 ITEMS
11760 })
11761}
11762
11763fn optional_u32(value: Option<u32>) -> TokenStream2 {
11764 if let Some(v) = value {
11765 quote!(::core::option::Option::Some(#v))
11766 } else {
11767 quote!(::core::option::Option::None)
11768 }
11769}
11770
11771fn optional_i64(value: Option<i64>) -> TokenStream2 {
11772 if let Some(v) = value {
11773 quote!(::core::option::Option::Some(#v))
11774 } else {
11775 quote!(::core::option::Option::None)
11776 }
11777}
11778
11779fn optional_str(value: Option<&str>) -> TokenStream2 {
11780 if let Some(v) = value {
11781 quote!(::core::option::Option::Some(#v))
11782 } else {
11783 quote!(::core::option::Option::None)
11784 }
11785}
11786
11787fn optional_choices(pairs: Option<&[(String, String)]>) -> TokenStream2 {
11788 let Some(pairs) = pairs else {
11789 return quote!(::core::option::Option::None);
11790 };
11791 let entries = pairs.iter().map(|(v, l)| quote!((#v, #l)));
11792 quote!(::core::option::Option::Some(&[#(#entries),*]))
11793}
11794
11795fn relation_tokens(
11796 field: &syn::Field,
11797 attrs: &FieldAttrs,
11798 fk_inner: Option<&syn::Type>,
11799 table: &str,
11800) -> syn::Result<TokenStream2> {
11801 let root = rustango_root();
11802 if let Some(inner) = fk_inner {
11803 if attrs.fk.is_some() || attrs.o2o.is_some() {
11804 return Err(syn::Error::new_spanned(
11805 field,
11806 "`ForeignKey<T>` already declares the FK target via the type parameter — \
11807 remove the `fk = \"…\"` / `o2o = \"…\"` attribute.",
11808 ));
11809 }
11810 let on = match attrs.on.as_deref() {
11812 Some(on) => quote!(#on),
11813 None => quote! {
11814 match <#inner as #root::core::Model>::SCHEMA.pk_column() {
11815 ::core::option::Option::Some(c) => c,
11816 ::core::option::Option::None => "id",
11817 }
11818 },
11819 };
11820 return Ok(quote! {
11821 ::core::option::Option::Some(#root::core::Relation::fk(
11822 <#inner as #root::core::Model>::SCHEMA.table,
11823 #on,
11824 ))
11825 });
11826 }
11827 match (&attrs.fk, &attrs.o2o) {
11828 (Some(_), Some(_)) => Err(syn::Error::new_spanned(
11829 field,
11830 "`fk` and `o2o` are mutually exclusive",
11831 )),
11832 (Some(to), None) => {
11833 let on = attrs.on.as_deref().unwrap_or("id");
11834 let resolved = if to == "self" { table } else { to };
11840 Ok(quote! {
11841 ::core::option::Option::Some(#root::core::Relation::fk(#resolved, #on))
11842 })
11843 }
11844 (None, Some(to)) => {
11845 let on = attrs.on.as_deref().unwrap_or("id");
11846 let resolved = if to == "self" { table } else { to };
11847 Ok(quote! {
11848 ::core::option::Option::Some(#root::core::Relation::o2o(#resolved, #on))
11849 })
11850 }
11851 (None, None) => {
11852 if attrs.on.is_some() {
11853 return Err(syn::Error::new_spanned(
11854 field,
11855 "`on` requires `fk` or `o2o`",
11856 ));
11857 }
11858 Ok(quote!(::core::option::Option::None))
11859 }
11860 }
11861}
11862
11863#[derive(Clone, Copy, PartialEq, Eq)]
11867enum DetectedKind {
11868 I16,
11869 I32,
11870 I64,
11871 F32,
11872 F64,
11873 Bool,
11874 String,
11875 DateTime,
11876 Date,
11877 Time,
11878 Uuid,
11879 Json,
11880 Decimal,
11881 Binary,
11882 ArrayText,
11884 ArrayInt,
11886 ArrayBigInt,
11888 RangeInt,
11890 RangeBigInt,
11892 RangeNumeric,
11894 RangeDate,
11896 RangeDateTime,
11898 HStore,
11900 Vector,
11904 Geometry,
11908}
11909
11910impl DetectedKind {
11911 fn variant_tokens(self) -> TokenStream2 {
11912 let root = rustango_root();
11913 match self {
11914 Self::I16 => quote!(#root::core::FieldType::I16),
11915 Self::I32 => quote!(#root::core::FieldType::I32),
11916 Self::I64 => quote!(#root::core::FieldType::I64),
11917 Self::F32 => quote!(#root::core::FieldType::F32),
11918 Self::F64 => quote!(#root::core::FieldType::F64),
11919 Self::Bool => quote!(#root::core::FieldType::Bool),
11920 Self::String => quote!(#root::core::FieldType::String),
11921 Self::DateTime => quote!(#root::core::FieldType::DateTime),
11922 Self::Date => quote!(#root::core::FieldType::Date),
11923 Self::Time => quote!(#root::core::FieldType::Time),
11924 Self::Uuid => quote!(#root::core::FieldType::Uuid),
11925 Self::Json => quote!(#root::core::FieldType::Json),
11926 Self::Decimal => quote!(#root::core::FieldType::Decimal),
11927 Self::Binary => quote!(#root::core::FieldType::Binary),
11928 Self::ArrayText => {
11929 quote!(#root::core::FieldType::Array(#root::core::ArrayElem::Text))
11930 }
11931 Self::ArrayInt => {
11932 quote!(#root::core::FieldType::Array(#root::core::ArrayElem::Int))
11933 }
11934 Self::ArrayBigInt => {
11935 quote!(#root::core::FieldType::Array(#root::core::ArrayElem::BigInt))
11936 }
11937 Self::RangeInt => {
11938 quote!(#root::core::FieldType::Range(#root::core::RangeElem::Int))
11939 }
11940 Self::RangeBigInt => {
11941 quote!(#root::core::FieldType::Range(#root::core::RangeElem::BigInt))
11942 }
11943 Self::RangeNumeric => {
11944 quote!(#root::core::FieldType::Range(#root::core::RangeElem::Numeric))
11945 }
11946 Self::RangeDate => {
11947 quote!(#root::core::FieldType::Range(#root::core::RangeElem::Date))
11948 }
11949 Self::RangeDateTime => {
11950 quote!(#root::core::FieldType::Range(#root::core::RangeElem::DateTime))
11951 }
11952 Self::HStore => quote!(#root::core::FieldType::HStore),
11953 Self::Vector => quote!(#root::core::FieldType::Vector(0)),
11956 Self::Geometry => quote!(#root::core::FieldType::Geometry(0)),
11959 }
11960 }
11961
11962 fn is_integer(self) -> bool {
11963 matches!(self, Self::I16 | Self::I32 | Self::I64)
11964 }
11965}
11966
11967#[derive(Clone, Copy)]
11973struct DetectedType<'a> {
11974 kind: DetectedKind,
11975 nullable: bool,
11976 auto: bool,
11977 fk_inner: Option<&'a syn::Type>,
11978}
11979
11980fn auto_inner_type(ty: &syn::Type) -> Option<&syn::Type> {
11985 let Type::Path(TypePath { path, qself: None }) = ty else {
11986 return None;
11987 };
11988 let last = path.segments.last()?;
11989 if last.ident != "Auto" {
11990 return None;
11991 }
11992 let syn::PathArguments::AngleBracketed(args) = &last.arguments else {
11993 return None;
11994 };
11995 args.args.iter().find_map(|a| match a {
11996 syn::GenericArgument::Type(t) => Some(t),
11997 _ => None,
11998 })
11999}
12000
12001fn cast_type(ty: &syn::Type) -> Option<&syn::Type> {
12003 let Type::Path(TypePath { path, qself: None }) = ty else {
12004 return None;
12005 };
12006 let last = path.segments.last()?;
12007 if last.ident == "Cast" {
12008 return Some(ty);
12009 }
12010 if last.ident != "Option" {
12011 return None;
12012 }
12013 let syn::PathArguments::AngleBracketed(args) = &last.arguments else {
12014 return None;
12015 };
12016 match args.args.first()? {
12017 syn::GenericArgument::Type(inner) => cast_type(inner).filter(|_| args.args.len() == 1),
12018 _ => None,
12019 }
12020}
12021
12022fn detect_type(ty: &syn::Type) -> syn::Result<DetectedType<'_>> {
12023 let Type::Path(TypePath { path, qself: None }) = ty else {
12024 return Err(syn::Error::new_spanned(ty, "unsupported field type"));
12025 };
12026 let last = path
12027 .segments
12028 .last()
12029 .ok_or_else(|| syn::Error::new_spanned(ty, "empty type path"))?;
12030
12031 if last.ident == "Option" {
12032 let inner = generic_inner(ty, &last.arguments, "Option")?;
12033 let inner_det = detect_type(inner)?;
12034 if inner_det.nullable {
12035 return Err(syn::Error::new_spanned(
12036 ty,
12037 "nested Option is not supported",
12038 ));
12039 }
12040 if inner_det.auto {
12041 return Err(syn::Error::new_spanned(
12042 ty,
12043 "`Option<Auto<T>>` is not supported — Auto fields are server-assigned and cannot be NULL",
12044 ));
12045 }
12046 return Ok(DetectedType {
12047 nullable: true,
12048 ..inner_det
12049 });
12050 }
12051
12052 if last.ident == "Auto" {
12053 let inner = generic_inner(ty, &last.arguments, "Auto")?;
12054 let inner_det = detect_type(inner)?;
12055 if inner_det.auto {
12056 return Err(syn::Error::new_spanned(ty, "nested Auto is not supported"));
12057 }
12058 if inner_det.nullable {
12059 return Err(syn::Error::new_spanned(
12060 ty,
12061 "`Auto<Option<T>>` is not supported — Auto fields are server-assigned and cannot be NULL",
12062 ));
12063 }
12064 if inner_det.fk_inner.is_some() {
12065 return Err(syn::Error::new_spanned(
12066 ty,
12067 "`Auto<ForeignKey<T>>` is not supported — Auto is for server-assigned PKs, ForeignKey is for parent references",
12068 ));
12069 }
12070 if !matches!(
12071 inner_det.kind,
12072 DetectedKind::I32 | DetectedKind::I64 | DetectedKind::Uuid | DetectedKind::DateTime
12073 ) {
12074 return Err(syn::Error::new_spanned(
12075 ty,
12076 "`Auto<T>` only supports integers (`i32` → SERIAL, `i64` → BIGSERIAL), \
12077 `uuid::Uuid` (DEFAULT gen_random_uuid()), or `chrono::DateTime<chrono::Utc>` \
12078 (DEFAULT now())",
12079 ));
12080 }
12081 return Ok(DetectedType {
12082 auto: true,
12083 ..inner_det
12084 });
12085 }
12086
12087 if last.ident == "ForeignKey" {
12088 let (inner, key_ty) = generic_pair(ty, &last.arguments, "ForeignKey")?;
12089 let kind = match key_ty {
12097 Some(k) => detect_type(k)?.kind,
12098 None => DetectedKind::I64,
12099 };
12100 return Ok(DetectedType {
12101 kind,
12102 nullable: false,
12103 auto: false,
12104 fk_inner: Some(inner),
12105 });
12106 }
12107
12108 let kind = match last.ident.to_string().as_str() {
12109 "i16" => DetectedKind::I16,
12110 "i32" => DetectedKind::I32,
12111 "i64" => DetectedKind::I64,
12112 "f32" => DetectedKind::F32,
12113 "f64" => DetectedKind::F64,
12114 "bool" => DetectedKind::Bool,
12115 "String" => DetectedKind::String,
12116 "DateTime" => DetectedKind::DateTime,
12117 "NaiveDate" => DetectedKind::Date,
12118 "NaiveTime" => DetectedKind::Time,
12119 "Uuid" => DetectedKind::Uuid,
12120 "Value" => DetectedKind::Json,
12121 "Decimal" => DetectedKind::Decimal,
12122 "Vec" => {
12126 let (inner, _) = generic_pair(ty, &last.arguments, "Vec")?;
12127 if let Type::Path(TypePath { path, qself: None }) = inner {
12128 if let Some(seg) = path.segments.last() {
12129 if seg.ident == "u8" && seg.arguments.is_empty() {
12130 return Ok(DetectedType {
12131 kind: DetectedKind::Binary,
12132 nullable: false,
12133 auto: false,
12134 fk_inner: None,
12135 });
12136 }
12137 }
12138 }
12139 return Err(syn::Error::new_spanned(
12140 ty,
12141 "unsupported `Vec<T>` field — only `Vec<u8>` (→ Binary) is supported; \
12142 for a PostgreSQL array column use `Array<String>` / `Array<i32>` / `Array<i64>`",
12143 ));
12144 }
12145 "Array" => {
12148 let (inner, _) = generic_pair(ty, &last.arguments, "Array")?;
12149 let elem = match inner {
12150 Type::Path(TypePath { path, qself: None }) => {
12151 path.segments.last().map(|s| s.ident.to_string())
12152 }
12153 _ => None,
12154 };
12155 let kind = match elem.as_deref() {
12156 Some("String") => DetectedKind::ArrayText,
12157 Some("i32") => DetectedKind::ArrayInt,
12158 Some("i64") => DetectedKind::ArrayBigInt,
12159 _ => {
12160 return Err(syn::Error::new_spanned(
12161 ty,
12162 "unsupported `Array<T>` element — only `Array<String>` (→ text[]), \
12163 `Array<i32>` (→ integer[]), and `Array<i64>` (→ bigint[]) are supported (#341)",
12164 ));
12165 }
12166 };
12167 return Ok(DetectedType {
12168 kind,
12169 nullable: false,
12170 auto: false,
12171 fk_inner: None,
12172 });
12173 }
12174 "Range" => {
12178 let (inner, _) = generic_pair(ty, &last.arguments, "Range")?;
12179 let elem = match inner {
12180 Type::Path(TypePath { path, qself: None }) => {
12181 path.segments.last().map(|s| s.ident.to_string())
12182 }
12183 _ => None,
12184 };
12185 let kind = match elem.as_deref() {
12186 Some("i32") => DetectedKind::RangeInt,
12187 Some("i64") => DetectedKind::RangeBigInt,
12188 Some("Decimal") => DetectedKind::RangeNumeric,
12189 Some("NaiveDate") => DetectedKind::RangeDate,
12190 Some("DateTime") => DetectedKind::RangeDateTime,
12191 _ => {
12192 return Err(syn::Error::new_spanned(
12193 ty,
12194 "unsupported `Range<T>` element — only `Range<i32>` (→ int4range), \
12195 `Range<i64>` (→ int8range), `Range<Decimal>` (→ numrange), \
12196 `Range<NaiveDate>` (→ daterange), and `Range<DateTime<Utc>>` \
12197 (→ tstzrange) are supported (#343)",
12198 ));
12199 }
12200 };
12201 return Ok(DetectedType {
12202 kind,
12203 nullable: false,
12204 auto: false,
12205 fk_inner: None,
12206 });
12207 }
12208 "Cast" => {
12213 return Ok(DetectedType {
12214 kind: DetectedKind::String,
12215 nullable: false,
12216 auto: false,
12217 fk_inner: None,
12218 });
12219 }
12220 "HStore" => {
12223 return Ok(DetectedType {
12224 kind: DetectedKind::HStore,
12225 nullable: false,
12226 auto: false,
12227 fk_inner: None,
12228 });
12229 }
12230 "Vector" => {
12233 return Ok(DetectedType {
12234 kind: DetectedKind::Vector,
12235 nullable: false,
12236 auto: false,
12237 fk_inner: None,
12238 });
12239 }
12240 "Point" => {
12243 return Ok(DetectedType {
12244 kind: DetectedKind::Geometry,
12245 nullable: false,
12246 auto: false,
12247 fk_inner: None,
12248 });
12249 }
12250 other => {
12251 return Err(syn::Error::new_spanned(
12252 ty,
12253 format!("unsupported field type `{other}`; supports i16/i32/i64/f32/f64/bool/String/DateTime/NaiveDate/NaiveTime/Uuid/serde_json::Value/Decimal/Vec<u8>, optionally wrapped in Option or Auto (Auto only on integers/Uuid/DateTime)"),
12254 ));
12255 }
12256 };
12257 Ok(DetectedType {
12258 kind,
12259 nullable: false,
12260 auto: false,
12261 fk_inner: None,
12262 })
12263}
12264
12265fn generic_inner<'a>(
12266 ty: &'a Type,
12267 arguments: &'a PathArguments,
12268 wrapper: &str,
12269) -> syn::Result<&'a Type> {
12270 let PathArguments::AngleBracketed(args) = arguments else {
12271 return Err(syn::Error::new_spanned(
12272 ty,
12273 format!("{wrapper} requires a generic argument"),
12274 ));
12275 };
12276 args.args
12277 .iter()
12278 .find_map(|a| match a {
12279 GenericArgument::Type(t) => Some(t),
12280 _ => None,
12281 })
12282 .ok_or_else(|| {
12283 syn::Error::new_spanned(ty, format!("{wrapper}<T> requires a type argument"))
12284 })
12285}
12286
12287fn generic_pair<'a>(
12291 ty: &'a Type,
12292 arguments: &'a PathArguments,
12293 wrapper: &str,
12294) -> syn::Result<(&'a Type, Option<&'a Type>)> {
12295 let PathArguments::AngleBracketed(args) = arguments else {
12296 return Err(syn::Error::new_spanned(
12297 ty,
12298 format!("{wrapper} requires a generic argument"),
12299 ));
12300 };
12301 let mut types = args.args.iter().filter_map(|a| match a {
12302 GenericArgument::Type(t) => Some(t),
12303 _ => None,
12304 });
12305 let first = types.next().ok_or_else(|| {
12306 syn::Error::new_spanned(ty, format!("{wrapper}<T> requires a type argument"))
12307 })?;
12308 let second = types.next();
12309 Ok((first, second))
12310}
12311
12312fn to_snake_case(s: &str) -> String {
12313 let mut out = String::with_capacity(s.len() + 4);
12314 for (i, ch) in s.chars().enumerate() {
12315 if ch.is_ascii_uppercase() {
12316 if i > 0 {
12317 out.push('_');
12318 }
12319 out.push(ch.to_ascii_lowercase());
12320 } else {
12321 out.push(ch);
12322 }
12323 }
12324 out
12325}
12326
12327#[derive(Default)]
12333struct FormFieldAttrs {
12334 min: Option<i64>,
12335 max: Option<i64>,
12336 min_length: Option<u32>,
12337 max_length: Option<u32>,
12338 clean: Option<syn::Ident>,
12345}
12346
12347#[derive(Default)]
12350struct FormContainerAttrs {
12351 validate: Option<syn::Ident>,
12357}
12358
12359#[derive(Clone, Copy)]
12361enum FormFieldKind {
12362 String,
12363 I16,
12364 I32,
12365 I64,
12366 F32,
12367 F64,
12368 Bool,
12369}
12370
12371impl FormFieldKind {
12372 fn parse_method(self) -> &'static str {
12373 match self {
12374 Self::I16 => "i16",
12375 Self::I32 => "i32",
12376 Self::I64 => "i64",
12377 Self::F32 => "f32",
12378 Self::F64 => "f64",
12379 Self::String | Self::Bool => "",
12382 }
12383 }
12384}
12385
12386fn form_local(name: &str) -> syn::Ident {
12388 syn::Ident::new(name, proc_macro2::Span::mixed_site())
12389}
12390
12391fn expand_form(input: &DeriveInput) -> syn::Result<TokenStream2> {
12392 let root = rustango_root();
12393 let (errs, cleaned, data_param) = (
12394 form_local("__errors"),
12395 form_local("__cleaned"),
12396 form_local("data"),
12397 );
12398 let struct_name = &input.ident;
12399
12400 let Data::Struct(data) = &input.data else {
12401 return Err(syn::Error::new_spanned(
12402 struct_name,
12403 "Form can only be derived on structs",
12404 ));
12405 };
12406 let Fields::Named(named) = &data.fields else {
12407 return Err(syn::Error::new_spanned(
12408 struct_name,
12409 "Form requires a struct with named fields",
12410 ));
12411 };
12412
12413 let container = parse_form_container_attrs(input)?;
12415 let post_field_clean: Vec<TokenStream2> = Vec::new();
12416 let _ = post_field_clean;
12417
12418 let mut field_blocks: Vec<TokenStream2> = Vec::with_capacity(named.named.len());
12419 let mut field_idents: Vec<&syn::Ident> = Vec::with_capacity(named.named.len());
12420
12421 for field in &named.named {
12422 let ident = field
12423 .ident
12424 .as_ref()
12425 .ok_or_else(|| syn::Error::new(field.span(), "tuple structs are not supported"))?;
12426 let attrs = parse_form_field_attrs(field)?;
12427 let (kind, nullable) = detect_form_field(&field.ty, field.span())?;
12428 let is_string = matches!(kind, FormFieldKind::String);
12429 let is_number = !is_string && !matches!(kind, FormFieldKind::Bool);
12430 if !is_string && (attrs.min_length.is_some() || attrs.max_length.is_some()) {
12431 return Err(syn::Error::new(
12432 field.span(),
12433 "`min_length` / `max_length` need a String field",
12434 ));
12435 }
12436 if !is_number && (attrs.min.is_some() || attrs.max.is_some()) {
12437 return Err(syn::Error::new(
12438 field.span(),
12439 "`min` / `max` need a numeric field",
12440 ));
12441 }
12442
12443 let name_lit = ident_name(ident);
12444 let parse_block = render_form_field_parse(ident, &name_lit, kind, nullable, &attrs);
12445 let clean_block = if let Some(clean_fn) = &attrs.clean {
12451 quote! {
12452 if #errs.fields().get(#name_lit).is_none() {
12453 match Self::#clean_fn(&#ident) {
12454 ::core::result::Result::Ok(#cleaned) => { #ident = #cleaned; }
12455 ::core::result::Result::Err(__msg) => {
12456 #errs.add(#name_lit, __msg);
12457 }
12458 }
12459 }
12460 }
12461 } else {
12462 quote! {}
12463 };
12464 field_blocks.push(quote! {
12465 #parse_block
12466 #clean_block
12467 });
12468 field_idents.push(ident);
12469 }
12470
12471 let cross_field_call = if let Some(validate_fn) = &container.validate {
12476 quote! {
12477 if #errs.is_empty() {
12478 let __candidate = Self { #( #field_idents ),* };
12479 if let ::core::result::Result::Err(__other) = Self::#validate_fn(&__candidate) {
12480 #errs.merge(__other);
12481 }
12482 if !#errs.is_empty() {
12483 return ::core::result::Result::Err(#errs);
12484 }
12485 return ::core::result::Result::Ok(__candidate);
12486 }
12487 }
12488 } else {
12489 quote! {}
12490 };
12491
12492 Ok(quote! {
12493 impl #root::forms::Form for #struct_name {
12494 fn parse(
12495 #data_param: &::std::collections::HashMap<::std::string::String, ::std::string::String>,
12496 ) -> ::core::result::Result<Self, #root::forms::FormErrors> {
12497 let mut #errs = #root::forms::FormErrors::default();
12498 #( #field_blocks )*
12499 #cross_field_call
12500 if !#errs.is_empty() {
12501 return ::core::result::Result::Err(#errs);
12502 }
12503 ::core::result::Result::Ok(Self {
12504 #( #field_idents ),*
12505 })
12506 }
12507 }
12508 })
12509}
12510
12511fn parse_form_container_attrs(input: &DeriveInput) -> syn::Result<FormContainerAttrs> {
12512 let mut out = FormContainerAttrs::default();
12513 for attr in &input.attrs {
12514 if !attr.path().is_ident("form") {
12515 continue;
12516 }
12517 attr.parse_nested_meta(|meta| {
12518 if meta.path.is_ident("validate") {
12519 let s: LitStr = meta.value()?.parse()?;
12520 out.validate = Some(syn::Ident::new(&s.value(), s.span()));
12521 return Ok(());
12522 }
12523 Err(meta.error("unknown form container attribute (supported: `validate`)"))
12524 })?;
12525 }
12526 Ok(out)
12527}
12528
12529fn parse_form_field_attrs(field: &syn::Field) -> syn::Result<FormFieldAttrs> {
12530 let mut out = FormFieldAttrs::default();
12531 for attr in &field.attrs {
12532 if !attr.path().is_ident("form") {
12533 continue;
12534 }
12535 attr.parse_nested_meta(|meta| {
12536 if meta.path.is_ident("min") {
12537 let lit: syn::LitInt = meta.value()?.parse()?;
12538 out.min = Some(lit.base10_parse::<i64>()?);
12539 return Ok(());
12540 }
12541 if meta.path.is_ident("max") {
12542 let lit: syn::LitInt = meta.value()?.parse()?;
12543 out.max = Some(lit.base10_parse::<i64>()?);
12544 return Ok(());
12545 }
12546 if meta.path.is_ident("min_length") {
12547 let lit: syn::LitInt = meta.value()?.parse()?;
12548 out.min_length = Some(lit.base10_parse::<u32>()?);
12549 return Ok(());
12550 }
12551 if meta.path.is_ident("max_length") {
12552 let lit: syn::LitInt = meta.value()?.parse()?;
12553 out.max_length = Some(lit.base10_parse::<u32>()?);
12554 return Ok(());
12555 }
12556 if meta.path.is_ident("clean") {
12557 let s: LitStr = meta.value()?.parse()?;
12558 out.clean = Some(syn::Ident::new(&s.value(), s.span()));
12559 return Ok(());
12560 }
12561 Err(meta.error(
12562 "unknown form field attribute (supported: `min`, `max`, `min_length`, `max_length`, `clean`)",
12563 ))
12564 })?;
12565 }
12566 Ok(out)
12567}
12568
12569fn detect_form_field(ty: &Type, span: proc_macro2::Span) -> syn::Result<(FormFieldKind, bool)> {
12570 let Type::Path(TypePath { path, qself: None }) = ty else {
12571 return Err(syn::Error::new(
12572 span,
12573 "Form field must be a simple typed path (e.g. `String`, `i32`, `Option<String>`)",
12574 ));
12575 };
12576 let last = path
12577 .segments
12578 .last()
12579 .ok_or_else(|| syn::Error::new(span, "empty type path"))?;
12580
12581 if last.ident == "Option" {
12582 let inner = generic_inner(ty, &last.arguments, "Option")?;
12583 let (kind, nested) = detect_form_field(inner, span)?;
12584 if nested {
12585 return Err(syn::Error::new(
12586 span,
12587 "nested Option in Form fields is not supported",
12588 ));
12589 }
12590 return Ok((kind, true));
12591 }
12592
12593 let kind = match last.ident.to_string().as_str() {
12594 "String" => FormFieldKind::String,
12595 "i16" => FormFieldKind::I16,
12596 "i32" => FormFieldKind::I32,
12597 "i64" => FormFieldKind::I64,
12598 "f32" => FormFieldKind::F32,
12599 "f64" => FormFieldKind::F64,
12600 "bool" => FormFieldKind::Bool,
12601 other => {
12602 return Err(syn::Error::new(
12603 span,
12604 format!(
12605 "Form field type `{other}` is not supported in v0.8 — use String / \
12606 i16 / i32 / i64 / f32 / f64 / bool, optionally wrapped in Option<…>"
12607 ),
12608 ));
12609 }
12610 };
12611 Ok((kind, false))
12612}
12613
12614#[allow(clippy::too_many_lines)]
12615fn render_form_field_parse(
12616 ident: &syn::Ident,
12617 name_lit: &str,
12618 kind: FormFieldKind,
12619 nullable: bool,
12620 attrs: &FormFieldAttrs,
12621) -> TokenStream2 {
12622 let (errs, data_param) = (form_local("__errors"), form_local("data"));
12623 let lookup = quote! {
12625 let __raw: ::core::option::Option<&::std::string::String> = #data_param.get(#name_lit);
12626 };
12627
12628 let root = rustango_root();
12629 let checkbox = quote! {
12631 match #root::forms::checkbox(__s) {
12632 ::core::option::Option::Some(__b) => __b,
12633 ::core::option::Option::None => {
12634 #errs.add(#name_lit, "Enter true or false.");
12635 false
12636 }
12637 }
12638 };
12639 let parsed_value = match kind {
12640 FormFieldKind::Bool if nullable => quote! {
12642 let __v: ::core::option::Option<bool> = match __raw {
12643 ::core::option::Option::None => ::core::option::Option::None,
12644 ::core::option::Option::Some(__s) if __s.is_empty() => {
12645 ::core::option::Option::None
12646 }
12647 ::core::option::Option::Some(__s) => ::core::option::Option::Some(#checkbox),
12648 };
12649 },
12650 FormFieldKind::Bool => quote! {
12651 let __v: bool = match __raw {
12652 ::core::option::Option::None => false,
12653 ::core::option::Option::Some(__s) => #checkbox,
12654 };
12655 },
12656 FormFieldKind::String => {
12657 if nullable {
12658 quote! {
12659 let __v: ::core::option::Option<::std::string::String> = match __raw {
12660 ::core::option::Option::None => ::core::option::Option::None,
12661 ::core::option::Option::Some(__s) if __s.is_empty() => {
12662 ::core::option::Option::None
12663 }
12664 ::core::option::Option::Some(__s) => {
12665 ::core::option::Option::Some(::core::clone::Clone::clone(__s))
12666 }
12667 };
12668 }
12669 } else {
12670 quote! {
12671 let __v: ::std::string::String = match __raw {
12672 ::core::option::Option::Some(__s) if !__s.is_empty() => {
12673 ::core::clone::Clone::clone(__s)
12674 }
12675 _ => {
12676 #errs.add(#name_lit, "This field is required.");
12677 ::std::string::String::new()
12678 }
12679 };
12680 }
12681 }
12682 }
12683 FormFieldKind::I16
12684 | FormFieldKind::I32
12685 | FormFieldKind::I64
12686 | FormFieldKind::F32
12687 | FormFieldKind::F64 => {
12688 let parse_ty = syn::Ident::new(kind.parse_method(), proc_macro2::Span::call_site());
12689 let ty_lit = kind.parse_method();
12690 let default_val = match kind {
12691 FormFieldKind::I16 => quote! { 0i16 },
12692 FormFieldKind::I32 => quote! { 0i32 },
12693 FormFieldKind::I64 => quote! { 0i64 },
12694 FormFieldKind::F32 => quote! { 0f32 },
12695 FormFieldKind::F64 => quote! { 0f64 },
12696 _ => quote! { Default::default() },
12697 };
12698 let finite = if matches!(kind, FormFieldKind::F32 | FormFieldKind::F64) {
12700 quote! {
12701 .and_then(|__n| if __n.is_finite() {
12702 ::core::result::Result::Ok(__n)
12703 } else {
12704 ::core::result::Result::Err(::std::string::String::from("not a finite number"))
12705 })
12706 }
12707 } else {
12708 quote! {}
12709 };
12710 if nullable {
12711 quote! {
12712 let __v: ::core::option::Option<#parse_ty> = match __raw {
12713 ::core::option::Option::None => ::core::option::Option::None,
12714 ::core::option::Option::Some(__s) if __s.is_empty() => {
12715 ::core::option::Option::None
12716 }
12717 ::core::option::Option::Some(__s) => {
12718 match __s.parse::<#parse_ty>().map_err(|__e| ::std::string::ToString::to_string(&__e)) #finite {
12719 ::core::result::Result::Ok(__n) => {
12720 ::core::option::Option::Some(__n)
12721 }
12722 ::core::result::Result::Err(__e) => {
12723 #errs.add(
12724 #name_lit,
12725 ::std::format!("Enter a valid {} value: {}", #ty_lit, __e),
12726 );
12727 ::core::option::Option::None
12728 }
12729 }
12730 }
12731 };
12732 }
12733 } else {
12734 quote! {
12735 let __v: #parse_ty = match __raw {
12736 ::core::option::Option::Some(__s) if !__s.is_empty() => {
12737 match __s.parse::<#parse_ty>().map_err(|__e| ::std::string::ToString::to_string(&__e)) #finite {
12738 ::core::result::Result::Ok(__n) => __n,
12739 ::core::result::Result::Err(__e) => {
12740 #errs.add(
12741 #name_lit,
12742 ::std::format!("Enter a valid {} value: {}", #ty_lit, __e),
12743 );
12744 #default_val
12745 }
12746 }
12747 }
12748 _ => {
12749 #errs.add(#name_lit, "This field is required.");
12750 #default_val
12751 }
12752 };
12753 }
12754 }
12755 }
12756 };
12757
12758 let validators = render_form_validators(name_lit, kind, nullable, attrs);
12759
12760 quote! {
12761 let mut #ident = {
12765 #lookup
12766 #parsed_value
12767 #validators
12768 __v
12769 };
12770 }
12771}
12772
12773fn render_form_validators(
12774 name_lit: &str,
12775 kind: FormFieldKind,
12776 nullable: bool,
12777 attrs: &FormFieldAttrs,
12778) -> TokenStream2 {
12779 let errs = form_local("__errors");
12780 let mut checks: Vec<TokenStream2> = Vec::new();
12781
12782 let val_ref = if nullable {
12783 quote! { __v.as_ref() }
12784 } else {
12785 quote! { ::core::option::Option::Some(&__v) }
12786 };
12787
12788 let is_string = matches!(kind, FormFieldKind::String);
12789 let is_numeric = matches!(
12790 kind,
12791 FormFieldKind::I16
12792 | FormFieldKind::I32
12793 | FormFieldKind::I64
12794 | FormFieldKind::F32
12795 | FormFieldKind::F64
12796 );
12797
12798 if is_string {
12799 if let Some(min_len) = attrs.min_length {
12800 let min_len_usize = min_len as usize;
12801 checks.push(quote! {
12802 if let ::core::option::Option::Some(__s) = #val_ref {
12803 if __s.chars().count() < #min_len_usize {
12804 #errs.add(
12805 #name_lit,
12806 ::std::format!("Ensure this value has at least {} characters.", #min_len_usize),
12807 );
12808 }
12809 }
12810 });
12811 }
12812 if let Some(max_len) = attrs.max_length {
12813 let max_len_usize = max_len as usize;
12814 checks.push(quote! {
12815 if let ::core::option::Option::Some(__s) = #val_ref {
12816 if __s.chars().count() > #max_len_usize {
12817 #errs.add(
12818 #name_lit,
12819 ::std::format!("Ensure this value has at most {} characters.", #max_len_usize),
12820 );
12821 }
12822 }
12823 });
12824 }
12825 }
12826
12827 if is_numeric {
12828 if let Some(min) = attrs.min {
12829 checks.push(quote! {
12830 if let ::core::option::Option::Some(__n) = #val_ref {
12831 if (*__n as f64) < (#min as f64) {
12832 #errs.add(
12833 #name_lit,
12834 ::std::format!("Ensure this value is greater than or equal to {}.", #min),
12835 );
12836 }
12837 }
12838 });
12839 }
12840 if let Some(max) = attrs.max {
12841 checks.push(quote! {
12842 if let ::core::option::Option::Some(__n) = #val_ref {
12843 if (*__n as f64) > (#max as f64) {
12844 #errs.add(
12845 #name_lit,
12846 ::std::format!("Ensure this value is less than or equal to {}.", #max),
12847 );
12848 }
12849 }
12850 });
12851 }
12852 }
12853
12854 quote! { #( #checks )* }
12855}
12856
12857struct ViewSetAttrs {
12862 model: syn::Path,
12863 fields: Vec<String>,
12864 filter_fields: Vec<String>,
12865 search_fields: Vec<String>,
12866 ordering: Vec<(String, bool)>,
12868 page_size: Option<usize>,
12869 read_only: bool,
12870 allow_anonymous: bool,
12871 perms: ViewSetPermsAttrs,
12872 serializer: Option<syn::Path>,
12877}
12878
12879#[derive(Default)]
12880struct ViewSetPermsAttrs {
12881 list: Vec<String>,
12882 retrieve: Vec<String>,
12883 create: Vec<String>,
12884 update: Vec<String>,
12885 destroy: Vec<String>,
12886}
12887
12888fn expand_viewset(input: &DeriveInput) -> syn::Result<TokenStream2> {
12889 let root = rustango_root();
12890 let struct_name = &input.ident;
12891
12892 match &input.data {
12894 Data::Struct(s) => match &s.fields {
12895 Fields::Unit | Fields::Named(_) => {}
12896 Fields::Unnamed(_) => {
12897 return Err(syn::Error::new_spanned(
12898 struct_name,
12899 "ViewSet can only be derived on a unit struct or an empty named struct",
12900 ));
12901 }
12902 },
12903 _ => {
12904 return Err(syn::Error::new_spanned(
12905 struct_name,
12906 "ViewSet can only be derived on a struct",
12907 ));
12908 }
12909 }
12910
12911 let attrs = parse_viewset_attrs(input)?;
12912 let model_path = &attrs.model;
12913
12914 let field_lits = attrs.fields.iter().map(|f| f.as_str());
12915
12916 let filter_fields_call = if attrs.filter_fields.is_empty() {
12917 quote!()
12918 } else {
12919 let lits = attrs.filter_fields.iter().map(|f| f.as_str());
12920 quote!(.filter_fields(&[ #(#lits),* ]))
12921 };
12922
12923 let search_fields_call = if attrs.search_fields.is_empty() {
12924 quote!()
12925 } else {
12926 let lits = attrs.search_fields.iter().map(|f| f.as_str());
12927 quote!(.search_fields(&[ #(#lits),* ]))
12928 };
12929
12930 let ordering_call = if attrs.ordering.is_empty() {
12931 quote!()
12932 } else {
12933 let pairs = attrs.ordering.iter().map(|(f, desc)| {
12934 let f = f.as_str();
12935 quote!((#f, #desc))
12936 });
12937 quote!(.ordering(&[ #(#pairs),* ]))
12938 };
12939
12940 let page_size_call = if let Some(n) = attrs.page_size {
12941 quote!(.page_size(#n))
12942 } else {
12943 quote!()
12944 };
12945
12946 let read_only_call = if attrs.read_only {
12947 quote!(.read_only())
12948 } else {
12949 quote!()
12950 };
12951 let allow_anonymous_call = if attrs.allow_anonymous {
12952 quote!(.allow_anonymous())
12953 } else {
12954 quote!()
12955 };
12956
12957 let serializer_call = if let Some(ref ser) = attrs.serializer {
12961 quote!(.serializer::<#ser>())
12962 } else {
12963 quote!()
12964 };
12965
12966 let perms = &attrs.perms;
12967 let perms_call = if perms.list.is_empty()
12968 && perms.retrieve.is_empty()
12969 && perms.create.is_empty()
12970 && perms.update.is_empty()
12971 && perms.destroy.is_empty()
12972 {
12973 quote!()
12974 } else {
12975 let list_lits = perms.list.iter().map(|s| s.as_str());
12976 let retrieve_lits = perms.retrieve.iter().map(|s| s.as_str());
12977 let create_lits = perms.create.iter().map(|s| s.as_str());
12978 let update_lits = perms.update.iter().map(|s| s.as_str());
12979 let destroy_lits = perms.destroy.iter().map(|s| s.as_str());
12980 quote! {
12981 .permissions(#root::viewset::ViewSetPerms {
12982 list: ::std::vec![ #(#list_lits.to_owned()),* ],
12983 retrieve: ::std::vec![ #(#retrieve_lits.to_owned()),* ],
12984 create: ::std::vec![ #(#create_lits.to_owned()),* ],
12985 update: ::std::vec![ #(#update_lits.to_owned()),* ],
12986 destroy: ::std::vec![ #(#destroy_lits.to_owned()),* ],
12987 })
12988 }
12989 };
12990
12991 Ok(quote! {
12992 impl #struct_name {
12993 pub fn router(
13002 prefix: &str,
13003 pool: impl ::core::convert::Into<#root::sql::Pool>,
13004 ) -> #root::__axum::Router {
13005 #root::viewset::ViewSet::for_model(
13006 <#model_path as #root::core::Model>::SCHEMA,
13007 &[ #(#field_lits),* ],
13008 )
13009 #filter_fields_call
13010 #search_fields_call
13011 #ordering_call
13012 #page_size_call
13013 #perms_call
13014 #read_only_call
13015 #allow_anonymous_call
13016 #serializer_call
13017 .router(prefix, pool.into())
13018 }
13019 }
13020 })
13021}
13022
13023fn parse_viewset_attrs(input: &DeriveInput) -> syn::Result<ViewSetAttrs> {
13024 let mut model: Option<syn::Path> = None;
13025 let mut fields: Option<Vec<String>> = None;
13026 let mut filter_fields: Vec<String> = Vec::new();
13027 let mut search_fields: Vec<String> = Vec::new();
13028 let mut ordering: Vec<(String, bool)> = Vec::new();
13029 let mut page_size: Option<usize> = None;
13030 let mut read_only = false;
13031 let mut allow_anonymous = false;
13032 let mut perms = ViewSetPermsAttrs::default();
13033 let mut serializer: Option<syn::Path> = None;
13034
13035 for attr in &input.attrs {
13036 if !attr.path().is_ident("viewset") {
13037 continue;
13038 }
13039 attr.parse_nested_meta(|meta| {
13040 if meta.path.is_ident("model") {
13041 let path: syn::Path = meta.value()?.parse()?;
13042 model = Some(path);
13043 return Ok(());
13044 }
13045 if meta.path.is_ident("serializer") {
13046 let path: syn::Path = meta.value()?.parse()?;
13047 serializer = Some(path);
13048 return Ok(());
13049 }
13050 if meta.path.is_ident("fields") {
13051 let s: LitStr = meta.value()?.parse()?;
13052 fields = Some(split_field_list(&s.value()));
13053 return Ok(());
13054 }
13055 if meta.path.is_ident("filter_fields") {
13056 let s: LitStr = meta.value()?.parse()?;
13057 filter_fields = split_field_list(&s.value());
13058 return Ok(());
13059 }
13060 if meta.path.is_ident("search_fields") {
13061 let s: LitStr = meta.value()?.parse()?;
13062 search_fields = split_field_list(&s.value());
13063 return Ok(());
13064 }
13065 if meta.path.is_ident("ordering") {
13066 let s: LitStr = meta.value()?.parse()?;
13067 ordering = parse_ordering_list(&s.value());
13068 return Ok(());
13069 }
13070 if meta.path.is_ident("page_size") {
13071 let lit: syn::LitInt = meta.value()?.parse()?;
13072 page_size = Some(lit.base10_parse::<usize>()?);
13073 return Ok(());
13074 }
13075 if meta.path.is_ident("read_only") {
13076 read_only = true;
13077 return Ok(());
13078 }
13079 if meta.path.is_ident("allow_anonymous") {
13080 allow_anonymous = true;
13081 return Ok(());
13082 }
13083 if meta.path.is_ident("permissions") {
13084 meta.parse_nested_meta(|inner| {
13085 let parse_codenames = |inner: &syn::meta::ParseNestedMeta| -> syn::Result<Vec<String>> {
13086 let s: LitStr = inner.value()?.parse()?;
13087 Ok(split_field_list(&s.value()))
13088 };
13089 if inner.path.is_ident("list") {
13090 perms.list = parse_codenames(&inner)?;
13091 } else if inner.path.is_ident("retrieve") {
13092 perms.retrieve = parse_codenames(&inner)?;
13093 } else if inner.path.is_ident("create") {
13094 perms.create = parse_codenames(&inner)?;
13095 } else if inner.path.is_ident("update") {
13096 perms.update = parse_codenames(&inner)?;
13097 } else if inner.path.is_ident("destroy") {
13098 perms.destroy = parse_codenames(&inner)?;
13099 } else {
13100 return Err(inner.error(
13101 "unknown permissions key (supported: list, retrieve, create, update, destroy)",
13102 ));
13103 }
13104 Ok(())
13105 })?;
13106 return Ok(());
13107 }
13108 Err(meta.error(
13109 "unknown viewset attribute (supported: model, fields, filter_fields, \
13110 search_fields, ordering, page_size, read_only, allow_anonymous, serializer, \
13111 permissions(...))",
13112 ))
13113 })?;
13114 }
13115
13116 let model = model.ok_or_else(|| {
13117 syn::Error::new_spanned(&input.ident, "`#[viewset(model = SomeModel)]` is required")
13118 })?;
13119 let fields = fields.ok_or_else(|| {
13121 syn::Error::new_spanned(
13122 &input.ident,
13123 "`#[viewset(fields = \"id, title\")]` is required: the fields the API renders and writes",
13124 )
13125 })?;
13126
13127 Ok(ViewSetAttrs {
13128 model,
13129 fields,
13130 filter_fields,
13131 search_fields,
13132 ordering,
13133 page_size,
13134 read_only,
13135 allow_anonymous,
13136 perms,
13137 serializer,
13138 })
13139}
13140
13141struct SerializerContainerAttrs {
13144 model: syn::Path,
13145 cross_validate: Option<syn::Ident>,
13153}
13154
13155#[derive(Default)]
13156struct SerializerFieldAttrs {
13157 read_only: bool,
13158 write_only: bool,
13159 source: Option<String>,
13160 skip: bool,
13161 method: Option<String>,
13165 validate: Option<String>,
13170 nested: bool,
13180 nested_strict: bool,
13185 many: Option<syn::Type>,
13194 slug: Option<String>,
13205 max_length: Option<u64>,
13209 min_length: Option<u64>,
13212 min: Option<i64>,
13215 max: Option<i64>,
13218}
13219
13220fn parse_serializer_container_attrs(input: &DeriveInput) -> syn::Result<SerializerContainerAttrs> {
13221 let mut model: Option<syn::Path> = None;
13222 let mut cross_validate: Option<syn::Ident> = None;
13223 for attr in &input.attrs {
13224 if !attr.path().is_ident("serializer") {
13225 continue;
13226 }
13227 attr.parse_nested_meta(|meta| {
13228 if meta.path.is_ident("model") {
13229 let _eq: syn::Token![=] = meta.input.parse()?;
13230 model = Some(meta.input.parse()?);
13231 return Ok(());
13232 }
13233 if meta.path.is_ident("validate") {
13234 let s: LitStr = meta.value()?.parse()?;
13239 cross_validate = Some(syn::Ident::new(&s.value(), s.span()));
13240 return Ok(());
13241 }
13242 Err(meta.error(
13243 "unknown serializer container attribute \
13244 (supported: `model`, `validate`)",
13245 ))
13246 })?;
13247 }
13248 let model = model.ok_or_else(|| {
13249 syn::Error::new_spanned(
13250 &input.ident,
13251 "`#[serializer(model = SomeModel)]` is required",
13252 )
13253 })?;
13254 Ok(SerializerContainerAttrs {
13255 model,
13256 cross_validate,
13257 })
13258}
13259
13260#[derive(Default)]
13263struct SerdeWireAttrs {
13264 rename_ser: Option<String>,
13265 rename_de: Option<String>,
13266 skip_ser: bool,
13267 skip_de: bool,
13268 skip_ser_if: Option<syn::ExprPath>,
13269}
13270
13271#[derive(Clone, Copy)]
13273enum SerdeRenameRule {
13274 Lower,
13275 Upper,
13276 Pascal,
13277 Camel,
13278 ScreamingSnake,
13279 Kebab,
13280 ScreamingKebab,
13281}
13282
13283impl SerdeRenameRule {
13284 fn parse(s: &LitStr) -> syn::Result<Self> {
13285 Ok(match s.value().as_str() {
13286 "lowercase" | "snake_case" => Self::Lower,
13287 "UPPERCASE" => Self::Upper,
13288 "PascalCase" => Self::Pascal,
13289 "camelCase" => Self::Camel,
13290 "SCREAMING_SNAKE_CASE" => Self::ScreamingSnake,
13291 "kebab-case" => Self::Kebab,
13292 "SCREAMING-KEBAB-CASE" => Self::ScreamingKebab,
13293 _ => {
13294 return Err(syn::Error::new_spanned(
13295 s,
13296 "unknown serde `rename_all` rule",
13297 ))
13298 }
13299 })
13300 }
13301
13302 fn apply(self, field: &str) -> String {
13304 match self {
13305 Self::Lower => field.to_owned(),
13306 Self::Upper | Self::ScreamingSnake => field.to_ascii_uppercase(),
13307 Self::Pascal | Self::Camel => {
13308 let mut out = String::new();
13309 let mut cap = matches!(self, Self::Pascal);
13310 for ch in field.chars() {
13311 if ch == '_' {
13312 cap = !out.is_empty() || matches!(self, Self::Pascal);
13313 } else if cap {
13314 out.push(ch.to_ascii_uppercase());
13315 cap = false;
13316 } else {
13317 out.push(ch);
13318 }
13319 }
13320 out
13321 }
13322 Self::Kebab => field.replace('_', "-"),
13323 Self::ScreamingKebab => field.to_ascii_uppercase().replace('_', "-"),
13324 }
13325 }
13326}
13327
13328fn skip_serde_meta_value(meta: &syn::meta::ParseNestedMeta) -> syn::Result<()> {
13330 if meta.input.peek(syn::Token![=]) {
13331 let _: syn::Expr = meta.value()?.parse()?;
13332 } else if meta.input.peek(syn::token::Paren) {
13333 let _: proc_macro2::Group = meta.input.parse()?;
13334 }
13335 Ok(())
13336}
13337
13338fn parse_serde_rename(
13340 meta: &syn::meta::ParseNestedMeta,
13341 mut set: impl FnMut(bool, LitStr) -> syn::Result<()>,
13342) -> syn::Result<()> {
13343 if meta.input.peek(syn::Token![=]) {
13344 let s: LitStr = meta.value()?.parse()?;
13345 set(true, s.clone())?;
13346 return set(false, s);
13347 }
13348 meta.parse_nested_meta(|inner| {
13349 let is_ser = inner.path.is_ident("serialize");
13350 if !is_ser && !inner.path.is_ident("deserialize") {
13351 return Err(inner.error("expected `serialize` or `deserialize`"));
13352 }
13353 set(is_ser, inner.value()?.parse()?)
13354 })
13355}
13356
13357fn parse_serde_container_rules(
13359 input: &DeriveInput,
13360) -> syn::Result<(Option<SerdeRenameRule>, Option<SerdeRenameRule>)> {
13361 let (mut ser, mut de) = (None, None);
13362 for attr in input.attrs.iter().filter(|a| a.path().is_ident("serde")) {
13363 attr.parse_nested_meta(|meta| {
13364 if meta.path.is_ident("rename_all") {
13365 return parse_serde_rename(&meta, |is_ser, s| {
13366 let rule = Some(SerdeRenameRule::parse(&s)?);
13367 if is_ser {
13368 ser = rule;
13369 } else {
13370 de = rule;
13371 }
13372 Ok(())
13373 });
13374 }
13375 skip_serde_meta_value(&meta)
13376 })?;
13377 }
13378 Ok((ser, de))
13379}
13380
13381fn parse_serde_field_attrs(field: &syn::Field) -> syn::Result<SerdeWireAttrs> {
13382 let mut out = SerdeWireAttrs::default();
13383 for attr in field.attrs.iter().filter(|a| a.path().is_ident("serde")) {
13384 attr.parse_nested_meta(|meta| {
13385 if meta.path.is_ident("rename") {
13386 return parse_serde_rename(&meta, |is_ser, s| {
13387 if is_ser {
13388 out.rename_ser = Some(s.value());
13389 } else {
13390 out.rename_de = Some(s.value());
13391 }
13392 Ok(())
13393 });
13394 }
13395 if meta.path.is_ident("skip") {
13396 out.skip_ser = true;
13397 out.skip_de = true;
13398 return Ok(());
13399 }
13400 if meta.path.is_ident("skip_serializing") {
13401 out.skip_ser = true;
13402 return Ok(());
13403 }
13404 if meta.path.is_ident("skip_deserializing") {
13405 out.skip_de = true;
13406 return Ok(());
13407 }
13408 if meta.path.is_ident("skip_serializing_if") {
13409 let s: LitStr = meta.value()?.parse()?;
13410 out.skip_ser_if = Some(s.parse()?);
13411 return Ok(());
13412 }
13413 skip_serde_meta_value(&meta)
13414 })?;
13415 }
13416 Ok(out)
13417}
13418
13419fn parse_serializer_field_attrs(field: &syn::Field) -> syn::Result<SerializerFieldAttrs> {
13420 let mut out = SerializerFieldAttrs::default();
13421 for attr in &field.attrs {
13422 if !attr.path().is_ident("serializer") {
13423 continue;
13424 }
13425 attr.parse_nested_meta(|meta| {
13426 if meta.path.is_ident("read_only") {
13427 out.read_only = true;
13428 return Ok(());
13429 }
13430 if meta.path.is_ident("write_only") {
13431 out.write_only = true;
13432 return Ok(());
13433 }
13434 if meta.path.is_ident("skip") {
13435 out.skip = true;
13436 return Ok(());
13437 }
13438 if meta.path.is_ident("source") {
13439 let s: LitStr = meta.value()?.parse()?;
13440 out.source = Some(s.value());
13441 return Ok(());
13442 }
13443 if meta.path.is_ident("method") {
13444 let s: LitStr = meta.value()?.parse()?;
13445 out.method = Some(s.value());
13446 return Ok(());
13447 }
13448 if meta.path.is_ident("validate") {
13449 let s: LitStr = meta.value()?.parse()?;
13450 out.validate = Some(s.value());
13451 return Ok(());
13452 }
13453 if meta.path.is_ident("many") {
13454 let _eq: syn::Token![=] = meta.input.parse()?;
13455 out.many = Some(meta.input.parse()?);
13456 return Ok(());
13457 }
13458 if meta.path.is_ident("nested") {
13459 out.nested = true;
13460 if meta.input.peek(syn::token::Paren) {
13463 meta.parse_nested_meta(|inner| {
13464 if inner.path.is_ident("strict") {
13465 out.nested_strict = true;
13466 return Ok(());
13467 }
13468 Err(inner.error("unknown nested sub-attribute (supported: `strict`)"))
13469 })?;
13470 }
13471 return Ok(());
13472 }
13473 if meta.path.is_ident("slug") {
13474 let s: LitStr = meta.value()?.parse()?;
13475 out.slug = Some(s.value());
13476 return Ok(());
13477 }
13478 if meta.path.is_ident("max_length") {
13479 let lit: syn::LitInt = meta.value()?.parse()?;
13480 out.max_length = Some(lit.base10_parse::<u64>()?);
13481 return Ok(());
13482 }
13483 if meta.path.is_ident("min_length") {
13484 let lit: syn::LitInt = meta.value()?.parse()?;
13485 out.min_length = Some(lit.base10_parse::<u64>()?);
13486 return Ok(());
13487 }
13488 if meta.path.is_ident("min") {
13489 let lit: syn::LitInt = meta.value()?.parse()?;
13490 out.min = Some(lit.base10_parse::<i64>()?);
13491 return Ok(());
13492 }
13493 if meta.path.is_ident("max") {
13494 let lit: syn::LitInt = meta.value()?.parse()?;
13495 out.max = Some(lit.base10_parse::<i64>()?);
13496 return Ok(());
13497 }
13498 Err(meta.error(
13499 "unknown serializer field attribute (supported: \
13500 `read_only`, `write_only`, `source`, `skip`, `method`, \
13501 `validate`, `nested`, `many`, `slug`, `max_length`, \
13502 `min_length`, `min`, `max`)",
13503 ))
13504 })?;
13505 }
13506 if out.read_only && out.write_only {
13508 return Err(syn::Error::new_spanned(
13509 field,
13510 "a field cannot be both `read_only` and `write_only`",
13511 ));
13512 }
13513 if out.method.is_some() && out.source.is_some() {
13514 return Err(syn::Error::new_spanned(
13515 field,
13516 "`method` and `source` are mutually exclusive — `method` computes \
13517 the value from a method, `source` reads it from a different model field",
13518 ));
13519 }
13520 if out.slug.is_some() && (out.method.is_some() || out.nested || out.many.is_some()) {
13521 return Err(syn::Error::new_spanned(
13522 field,
13523 "`slug` is mutually exclusive with `method`, `nested`, and `many` \
13524 — pick one strategy for populating the field",
13525 ));
13526 }
13527 Ok(out)
13528}
13529
13530fn expand_serializer(input: &DeriveInput) -> syn::Result<TokenStream2> {
13531 let root = rustango_root();
13532 let struct_name = &input.ident;
13533 let struct_name_lit = struct_name.to_string();
13534
13535 let Data::Struct(data) = &input.data else {
13536 return Err(syn::Error::new_spanned(
13537 struct_name,
13538 "Serializer can only be derived on structs",
13539 ));
13540 };
13541 let Fields::Named(named) = &data.fields else {
13542 return Err(syn::Error::new_spanned(
13543 struct_name,
13544 "Serializer requires a struct with named fields",
13545 ));
13546 };
13547
13548 let container = parse_serializer_container_attrs(input)?;
13549 let model_path = &container.model;
13550
13551 #[allow(dead_code)]
13555 struct FieldInfo {
13556 ident: syn::Ident,
13557 ty: syn::Type,
13558 attrs: SerializerFieldAttrs,
13559 serde: SerdeWireAttrs,
13560 ser_name: String,
13562 de_name: String,
13563 }
13564 let (ser_rule, de_rule) = parse_serde_container_rules(input)?;
13565 let mut fields_info: Vec<FieldInfo> = Vec::new();
13566 for field in &named.named {
13567 let ident = field.ident.clone().expect("named field has ident");
13568 let attrs = parse_serializer_field_attrs(field)?;
13569 let serde = parse_serde_field_attrs(field)?;
13570 let name = ident_name(&ident);
13571 let wire = |rename: &Option<String>, rule: Option<SerdeRenameRule>| {
13572 rename
13573 .clone()
13574 .unwrap_or_else(|| rule.map_or_else(|| name.clone(), |r| r.apply(&name)))
13575 };
13576 fields_info.push(FieldInfo {
13577 ser_name: wire(&serde.rename_ser, ser_rule),
13578 de_name: wire(&serde.rename_de, de_rule),
13579 ident,
13580 ty: field.ty.clone(),
13581 attrs,
13582 serde,
13583 });
13584 }
13585
13586 let from_model_fields = fields_info.iter().map(|fi| {
13588 let ident = &fi.ident;
13589 if let Some(_inner) = &fi.attrs.many {
13590 quote! { #ident: ::std::vec::Vec::new() }
13594 } else if let Some(method) = &fi.attrs.method {
13595 let method_ident = syn::Ident::new(method, ident.span());
13599 quote! { #ident: Self::#method_ident(model) }
13600 } else if let Some(slug_field) = &fi.attrs.slug {
13601 let src_name = fi
13610 .attrs
13611 .source
13612 .as_deref()
13613 .unwrap_or(&ident_name(&fi.ident))
13614 .to_owned();
13615 let src_ident = ident_from_name(&src_name, ident.span());
13616 let slug_ident = syn::Ident::new(slug_field, ident.span());
13617 quote! {
13618 #ident: #root::serializer::LoadedRelation::render(
13619 &model.#src_ident,
13620 |__loaded| ::core::clone::Clone::clone(&__loaded.#slug_ident),
13621 ::core::default::Default::default,
13622 )
13623 }
13624 } else if fi.attrs.nested {
13625 let src_name = fi.attrs.source.as_deref().unwrap_or(&ident_name(&fi.ident)).to_owned();
13643 let src_ident = ident_from_name(&src_name, ident.span());
13644 let unloaded = if fi.attrs.nested_strict {
13645 let panic_msg = format!(
13646 "nested(strict) serializer for `{ident}` requires `model.{src_name}` to be loaded — \
13647 call .get(&pool).await? or .select_related(\"{src_name}\") on the model first",
13648 );
13649 quote! { || ::core::panic!("{}", #panic_msg) }
13650 } else {
13651 quote! { ::core::default::Default::default }
13652 };
13653 quote! {
13654 #ident: #root::serializer::LoadedRelation::render(
13655 &model.#src_ident,
13656 |__loaded| #root::serializer::ModelSerializer::from_model(__loaded),
13657 #unloaded,
13658 )
13659 }
13660 } else if fi.attrs.write_only || fi.attrs.skip {
13661 quote! { #ident: ::core::default::Default::default() }
13663 } else if let Some(src) = &fi.attrs.source {
13664 let src_ident = ident_from_name(src, ident.span());
13665 quote! { #ident: ::core::clone::Clone::clone(&model.#src_ident) }
13666 } else {
13667 quote! { #ident: ::core::clone::Clone::clone(&model.#ident) }
13668 }
13669 });
13670
13671 let is_writable = |fi: &&FieldInfo| {
13674 !fi.attrs.read_only
13675 && !fi.attrs.skip
13676 && !fi.serde.skip_de
13677 && fi.attrs.method.is_none()
13678 && !fi.attrs.nested
13679 && fi.attrs.many.is_none()
13680 && fi.attrs.slug.is_none()
13681 };
13682
13683 let opt_usize = |v: Option<u64>| match v {
13691 Some(n) => {
13692 let n = n as usize;
13693 quote!(::core::option::Option::Some(#n))
13694 }
13695 None => quote!(::core::option::Option::None),
13696 };
13697 #[cfg(feature = "openapi")]
13698 let opt_u64 = |v: Option<u64>| match v {
13699 Some(n) => quote!(::core::option::Option::Some(#n)),
13700 None => quote!(::core::option::Option::None),
13701 };
13702 let opt_i64 = |v: Option<i64>| match v {
13703 Some(n) => quote!(::core::option::Option::Some(#n)),
13704 None => quote!(::core::option::Option::None),
13705 };
13706 let constraint_blocks: Vec<_> = fields_info
13707 .iter()
13708 .filter(is_writable)
13709 .map(|fi| {
13710 let ident = &fi.ident;
13711 let fname = &fi.de_name;
13712 let mname = fi
13713 .attrs
13714 .source
13715 .clone()
13716 .unwrap_or_else(|| ident_name(&fi.ident));
13717 let attr_max_len = opt_usize(fi.attrs.max_length);
13718 let attr_min_len = opt_usize(fi.attrs.min_length);
13719 let attr_min = opt_i64(fi.attrs.min);
13720 let attr_max = opt_i64(fi.attrs.max);
13721 let ty = &fi.ty;
13723 let len_check = (fi.attrs.max_length.is_some() || fi.attrs.min_length.is_some())
13724 .then(|| quote! { #root::forms::validators::assert_length_bounded::<#ty>(); });
13725 let num_check = (fi.attrs.min.is_some() || fi.attrs.max.is_some())
13726 .then(|| quote! { #root::forms::validators::assert_number_bounded::<#ty>(); });
13727 quote! {
13728 #len_check
13729 #num_check
13730 if __sent(#fname) {
13731 let __sf = <#model_path as #root::core::Model>::SCHEMA.field(#mname);
13732 let __max_length: ::core::option::Option<usize> = #attr_max_len
13733 .or_else(|| __sf.and_then(|__f| __f.max_length).map(|__n| __n as usize));
13734 let __min_length: ::core::option::Option<usize> = #attr_min_len;
13735 let __min: ::core::option::Option<i64> =
13736 #attr_min.or_else(|| __sf.and_then(|__f| __f.min));
13737 let __max: ::core::option::Option<i64> =
13738 #attr_max.or_else(|| __sf.and_then(|__f| __f.max));
13739 let __choices = __sf.and_then(|__f| __f.choices);
13740 let __v = #root::__serde_json::to_value(&self.#ident)
13741 .unwrap_or(#root::__serde_json::Value::Null);
13742 #root::forms::validators::check_value(
13743 #fname, &__v, __max_length, __min_length, __min, __max, __choices,
13744 &mut __errors,
13745 );
13746 }
13747 }
13748 })
13749 .collect();
13750 let has_constraints = !constraint_blocks.is_empty();
13751
13752 let validator_calls: Vec<_> = fields_info
13756 .iter()
13757 .filter_map(|fi| {
13758 let ident = &fi.ident;
13759 let name_lit = &fi.de_name;
13760 let method = fi.attrs.validate.as_ref()?;
13761 let method_ident = syn::Ident::new(method, ident.span());
13762 Some(quote! {
13763 if __sent(#name_lit) {
13764 if let ::core::result::Result::Err(__e) = Self::#method_ident(&self.#ident) {
13765 __errors.add(#name_lit.to_owned(), __e);
13766 }
13767 }
13768 })
13769 })
13770 .collect();
13771 let cross_validate_call = container.cross_validate.as_ref().map(|method_ident| {
13778 quote! {
13779 if let ::core::result::Result::Err(__cross) = self.#method_ident() {
13782 __errors.merge(__cross);
13783 }
13784 }
13785 });
13786 let has_validators = !validator_calls.is_empty() || container.cross_validate.is_some();
13787 let has_run_validations = has_validators || has_constraints;
13790 let checks = quote! {
13794 let mut __errors = #root::forms::FormErrors::default();
13795 #( #constraint_blocks )*
13796 #( #validator_calls )*
13797 #cross_validate_call
13798 if __errors.is_empty() {
13799 ::core::result::Result::Ok(())
13800 } else {
13801 ::core::result::Result::Err(__errors)
13802 }
13803 };
13804 let validate_body = quote! {
13805 let __sent = |_: &str| true;
13806 #checks
13807 };
13808 let validate_patch_body = quote! {
13810 let __sent = |__n: &str| __obj.is_some_and(|__o| __o.contains_key(__n));
13811 #checks
13812 };
13813 let validate_method = if has_validators {
13819 quote! {
13820 impl #struct_name {
13821 pub fn validate(&self) -> ::core::result::Result<(), #root::forms::FormErrors> {
13827 #validate_body
13828 }
13829 }
13830 }
13831 } else {
13832 quote! {}
13833 };
13834 let trait_validate_override = if has_run_validations {
13839 quote! {
13840 fn validate(&self) -> ::core::result::Result<(), #root::forms::FormErrors> {
13841 #validate_body
13842 }
13843 }
13844 } else {
13845 quote! {}
13846 };
13847
13848 let many_setters: Vec<_> = fields_info
13852 .iter()
13853 .filter_map(|fi| {
13854 let many_ty = fi.attrs.many.as_ref()?;
13855 let ident = &fi.ident;
13856 let setter = syn::Ident::new(&format!("set_{}", ident_name(ident)), ident.span());
13857 Some(quote! {
13858 pub fn #setter(
13863 &mut self,
13864 models: &[<#many_ty as #root::serializer::ModelSerializer>::Model],
13865 ) -> &mut Self {
13866 self.#ident = models.iter()
13867 .map(<#many_ty as #root::serializer::ModelSerializer>::from_model)
13868 .collect();
13869 self
13870 }
13871 })
13872 })
13873 .collect();
13874 let many_setters_impl = if many_setters.is_empty() {
13875 quote! {}
13876 } else {
13877 quote! {
13878 impl #struct_name {
13879 #( #many_setters )*
13880 }
13881 }
13882 };
13883
13884 let output_fields: Vec<_> = fields_info
13886 .iter()
13887 .filter(|fi| !fi.attrs.write_only && !fi.serde.skip_ser)
13888 .collect();
13889 let output_field_count = output_fields.iter().map(|fi| {
13891 let ident = &fi.ident;
13892 if let Some(pred) = &fi.serde.skip_ser_if {
13893 quote! { + if #pred(&self.#ident) { 0 } else { 1 } }
13894 } else {
13895 quote! { + 1 }
13896 }
13897 });
13898 let output_field_count = quote! { 0usize #( #output_field_count )* };
13899 let serialize_fields = output_fields.iter().map(|fi| {
13900 let ident = &fi.ident;
13901 let name_lit = &fi.ser_name;
13902 if let Some(pred) = &fi.serde.skip_ser_if {
13903 quote! {
13904 if #pred(&self.#ident) {
13905 __state.skip_field(#name_lit)?;
13906 } else {
13907 __state.serialize_field(#name_lit, &self.#ident)?;
13908 }
13909 }
13910 } else {
13911 quote! { __state.serialize_field(#name_lit, &self.#ident)?; }
13912 }
13913 });
13914
13915 let writable_lits: Vec<_> = fields_info
13930 .iter()
13931 .filter(is_writable)
13932 .map(|fi| fi.de_name.clone())
13933 .collect();
13934
13935 let writable_source_lits: Vec<String> = fields_info
13940 .iter()
13941 .filter(is_writable)
13942 .map(|fi| {
13943 fi.attrs
13944 .source
13945 .clone()
13946 .unwrap_or_else(|| ident_name(&fi.ident))
13947 })
13948 .collect();
13949
13950 let readable: Vec<&FieldInfo> = fields_info
13954 .iter()
13955 .filter(|fi| {
13956 !fi.attrs.write_only
13957 && !fi.serde.skip_ser
13958 && !fi.attrs.skip
13959 && fi.attrs.method.is_none()
13960 && !fi.attrs.nested
13961 && fi.attrs.many.is_none()
13962 && fi.attrs.slug.is_none()
13963 })
13964 .collect();
13965 let readable_source_lits: Vec<String> = readable
13966 .iter()
13967 .map(|fi| {
13968 fi.attrs
13969 .source
13970 .clone()
13971 .unwrap_or_else(|| ident_name(&fi.ident))
13972 })
13973 .collect();
13974 let readable_lits: Vec<String> = readable.iter().map(|fi| fi.ser_name.clone()).collect();
13976
13977 let from_writable_json_inits: Vec<_> = fields_info
13984 .iter()
13985 .map(|fi| {
13986 let ident = &fi.ident;
13987 let fname = &fi.de_name;
13988 let ty = &fi.ty;
13989 if is_writable(&fi) {
13990 quote! {
13991 #ident: match __obj.and_then(|__o| __o.get(#fname)) {
13992 ::core::option::Option::Some(__v) => {
13993 match #root::__serde_json::from_value::<#ty>(
13994 ::core::clone::Clone::clone(__v),
13995 ) {
13996 ::core::result::Result::Ok(__x) => __x,
13997 ::core::result::Result::Err(__e) => {
13998 __errors.add(#fname.to_owned(), __e.to_string());
13999 ::core::default::Default::default()
14000 }
14001 }
14002 }
14003 ::core::option::Option::None => ::core::default::Default::default(),
14004 }
14005 }
14006 } else {
14007 quote! { #ident: ::core::default::Default::default() }
14008 }
14009 })
14010 .collect();
14011 let patch_overlays: Vec<_> = fields_info
14013 .iter()
14014 .filter(is_writable)
14015 .map(|fi| {
14016 let ident = &fi.ident;
14017 let fname = &fi.de_name;
14018 let ty = &fi.ty;
14019 quote! {
14020 if let ::core::option::Option::Some(__v) = __obj.and_then(|__o| __o.get(#fname)) {
14021 match #root::__serde_json::from_value::<#ty>(::core::clone::Clone::clone(__v)) {
14022 ::core::result::Result::Ok(__x) => __merged.#ident = __x,
14023 ::core::result::Result::Err(__e) => {
14024 __errors.add(#fname.to_owned(), __e.to_string());
14025 }
14026 }
14027 }
14028 }
14029 })
14030 .collect();
14031 let patch_validate_call = if has_run_validations {
14032 quote! { __merged.__rustango_validate_patch(__obj) }
14033 } else {
14034 quote! { let _ = &__merged; ::core::result::Result::Ok(()) }
14035 };
14036 let patch_validate_helper = if has_run_validations {
14037 quote! {
14038 impl #struct_name {
14039 #[doc(hidden)]
14040 fn __rustango_validate_patch(
14041 &self,
14042 __obj: ::core::option::Option<&#root::__serde_json::Map<::std::string::String, #root::__serde_json::Value>>,
14043 ) -> ::core::result::Result<(), #root::forms::FormErrors> {
14044 #validate_patch_body
14045 }
14046 }
14047 }
14048 } else {
14049 quote! {}
14050 };
14051
14052 let openapi_impl = {
14056 #[cfg(feature = "openapi")]
14057 {
14058 let property_calls = output_fields.iter().map(|fi| {
14059 let name_lit = &fi.ser_name;
14060 let ty = &fi.ty;
14061 let nullable_call = if is_option(ty) {
14062 quote! { .nullable() }
14063 } else {
14064 quote! {}
14065 };
14066 let limits_call = is_writable(fi).then(|| {
14068 let mname = fi
14069 .attrs
14070 .source
14071 .clone()
14072 .unwrap_or_else(|| ident_name(&fi.ident));
14073 let (a, b, c, d) = (
14074 opt_u64(fi.attrs.max_length),
14075 opt_u64(fi.attrs.min_length),
14076 opt_i64(fi.attrs.min),
14077 opt_i64(fi.attrs.max),
14078 );
14079 quote! {
14080 .field_limits(
14081 <#model_path as #root::core::Model>::SCHEMA.field(#mname),
14082 #a, #b, #c, #d,
14083 )
14084 }
14085 });
14086 quote! {
14087 .property(
14088 #name_lit,
14089 <#ty as #root::openapi::OpenApiSchema>::openapi_schema()
14090 #limits_call
14091 #nullable_call,
14092 )
14093 }
14094 });
14095 let required_lits: Vec<_> = output_fields
14096 .iter()
14097 .filter(|fi| !is_option(&fi.ty) && fi.serde.skip_ser_if.is_none())
14098 .map(|fi| fi.ser_name.clone())
14099 .collect();
14100 quote! {
14101 impl #root::openapi::OpenApiSchema for #struct_name {
14102 fn openapi_schema() -> #root::openapi::Schema {
14103 #root::openapi::Schema::object()
14104 #( #property_calls )*
14105 .required([ #( #required_lits ),* ])
14106 }
14107 }
14108 }
14109 }
14110 #[cfg(not(feature = "openapi"))]
14111 {
14112 quote! {}
14113 }
14114 };
14115
14116 let related_tables = fields_info.iter().filter_map(|fi| {
14118 let name = &fi.ser_name;
14119 if let Some(inner) = &fi.attrs.many {
14120 return Some(quote! {
14121 (#name, <<#inner as #root::serializer::ModelSerializer>::Model
14122 as #root::core::Model>::SCHEMA.table)
14123 });
14124 }
14125 if !(fi.attrs.nested || fi.attrs.slug.is_some()) {
14126 return None;
14127 }
14128 let src_name = fi
14129 .attrs
14130 .source
14131 .as_deref()
14132 .unwrap_or(&ident_name(&fi.ident))
14133 .to_owned();
14134 let src_ident = ident_from_name(&src_name, fi.ident.span());
14135 Some(quote! {
14136 (#name, #root::serializer::relation_table(|__m: &#model_path| &__m.#src_ident))
14137 })
14138 });
14139
14140 Ok(quote! {
14141 impl #root::serializer::ModelSerializer for #struct_name {
14142 type Model = #model_path;
14143
14144 fn related_tables() -> ::std::vec::Vec<(&'static str, &'static str)> {
14145 ::std::vec![ #( #related_tables ),* ]
14146 }
14147
14148 fn from_model(model: &Self::Model) -> Self {
14149 Self {
14150 #( #from_model_fields ),*
14151 }
14152 }
14153
14154 fn writable_fields() -> &'static [&'static str] {
14155 &[ #( #writable_lits ),* ]
14156 }
14157
14158 fn writable_source_fields() -> &'static [&'static str] {
14159 &[ #( #writable_source_lits ),* ]
14160 }
14161
14162 fn readable_source_fields() -> &'static [&'static str] {
14163 &[ #( #readable_source_lits ),* ]
14164 }
14165
14166 fn readable_fields() -> &'static [&'static str] {
14167 &[ #( #readable_lits ),* ]
14168 }
14169
14170 fn from_writable_json(
14171 __body: &#root::__serde_json::Value,
14172 ) -> ::core::result::Result<Self, #root::forms::FormErrors> {
14173 let mut __errors = #root::forms::FormErrors::default();
14174 let __obj = __body.as_object();
14175 let __out = Self {
14176 #( #from_writable_json_inits ),*
14177 };
14178 if __errors.is_empty() {
14179 ::core::result::Result::Ok(__out)
14180 } else {
14181 ::core::result::Result::Err(__errors)
14182 }
14183 }
14184
14185 fn validate_patch(
14186 __stored: &Self::Model,
14187 __body: &#root::__serde_json::Value,
14188 ) -> ::core::result::Result<(), #root::forms::FormErrors> {
14189 let mut __errors = #root::forms::FormErrors::default();
14190 let __obj = __body.as_object();
14191 #[allow(unused_mut)]
14192 let mut __merged = <Self as #root::serializer::ModelSerializer>::from_model(__stored);
14193 #( #patch_overlays )*
14194 if !__errors.is_empty() {
14195 return ::core::result::Result::Err(__errors);
14196 }
14197 #patch_validate_call
14198 }
14199
14200 #trait_validate_override
14201 }
14202
14203 #patch_validate_helper
14204
14205 impl #root::__serde::Serialize for #struct_name {
14206 fn serialize<S>(&self, serializer: S)
14207 -> ::core::result::Result<S::Ok, S::Error>
14208 where
14209 S: #root::__serde::Serializer,
14210 {
14211 use #root::__serde::ser::SerializeStruct;
14212 let mut __state = serializer.serialize_struct(
14213 #struct_name_lit,
14214 #output_field_count,
14215 )?;
14216 #( #serialize_fields )*
14217 __state.end()
14218 }
14219 }
14220
14221 #openapi_impl
14222
14223 #validate_method
14224
14225 #many_setters_impl
14226 })
14227}
14228
14229#[cfg_attr(not(feature = "openapi"), allow(dead_code))]
14233fn is_option(ty: &syn::Type) -> bool {
14234 if let syn::Type::Path(p) = ty {
14235 if let Some(last) = p.path.segments.last() {
14236 return last.ident == "Option";
14237 }
14238 }
14239 false
14240}
14241
14242#[cfg(test)]
14243mod main_attr_tests {
14244 use super::*;
14245
14246 fn expand(args: &str) -> String {
14247 let body = quote! { async fn main() { run().await } };
14248 expand_main(args.parse().unwrap(), body)
14249 .expect("expansion failed")
14250 .to_string()
14251 }
14252
14253 #[test]
14257 fn logging_false_emits_no_subscriber() {
14258 let out = expand("logging = false");
14259 assert!(
14260 !out.contains("logging :: Setup"),
14261 "`logging = false` must not install a subscriber:\n{out}",
14262 );
14263 assert!(
14264 out.contains("block_on"),
14265 "the runtime must still be built:\n{out}",
14266 );
14267 }
14268
14269 #[test]
14270 fn default_still_installs_a_subscriber() {
14271 let out = expand("");
14275 assert!(
14276 out.contains("logging :: Setup"),
14277 "the default entrypoint must still install one:\n{out}",
14278 );
14279 assert!(
14282 !out.contains("args ()"),
14283 "the entrypoint must not route logs by argv:\n{out}",
14284 );
14285 }
14286
14287 #[test]
14288 fn logging_false_composes_with_flavor() {
14289 let out = expand("flavor = \"current_thread\", logging = false");
14290 assert!(!out.contains("logging :: Setup"), "{out}");
14291 assert!(out.contains("new_current_thread"), "{out}");
14292 }
14293
14294 #[test]
14295 fn logging_true_is_the_default_shape() {
14296 assert!(
14297 parse_main_args(&"logging = true".parse().unwrap())
14298 .unwrap()
14299 .logging
14300 );
14301 assert!(
14302 !parse_main_args(&"logging=false".parse().unwrap())
14303 .unwrap()
14304 .logging
14305 );
14306 }
14307}
14308
14309#[cfg(test)]
14310mod main_arg_tests {
14311 use super::*;
14312
14313 fn expand(args: &str) -> syn::Result<TokenStream2> {
14314 let body = quote! { async fn main() { run().await } };
14315 expand_main(args.parse().unwrap(), body)
14316 }
14317
14318 #[test]
14320 fn unknown_or_misspelt_args_are_errors() {
14321 for args in [
14322 "logging = flase",
14323 "multi_thread",
14324 "workers = 4",
14325 r#"flavor = "curent_thread""#,
14326 "worker_threads = 4",
14327 ] {
14328 let err = expand(args).expect_err(args).to_string();
14329 assert!(err.contains("#[rustango::main]"), "{args}: {err}");
14330 }
14331 }
14332
14333 #[test]
14334 fn supported_args_expand() {
14335 for args in [
14336 "",
14337 r#"flavor = "current_thread""#,
14338 r#"flavor = "multi_thread", logging = false"#,
14339 "logging = true",
14340 ] {
14341 assert!(expand(args).is_ok(), "{args}");
14342 }
14343 }
14344}
14345
14346#[cfg(test)]
14347mod embed_migrations_tests {
14348 use super::*;
14349
14350 fn expand(path: &str) -> String {
14351 expand_embed_migrations(quote! { #path })
14352 .expect("expansion failed")
14353 .to_string()
14354 }
14355
14356 #[test]
14357 fn missing_dir_warns() {
14358 let out = expand("./no_such_migrations_dir");
14359 assert!(out.contains("deprecated"), "missing dir must warn:\n{out}");
14360 assert!(out.contains("no_such_migrations_dir"), "{out}");
14361 }
14362
14363 #[test]
14364 fn existing_dir_does_not_warn() {
14365 let out = expand("./src");
14366 assert!(!out.contains("deprecated"), "{out}");
14367 }
14368}
14369
14370#[cfg(test)]
14371mod field_attr_tests {
14372 use super::*;
14373
14374 fn error_for(field: syn::Field) -> Option<String> {
14375 let attrs = parse_field_attrs(&field).unwrap();
14376 let kind = detect_type(&field.ty).unwrap().kind;
14377 check_bound_compatibility(&field, &attrs, kind)
14378 .err()
14379 .map(|e| e.to_string())
14380 }
14381
14382 #[test]
14384 fn type_specific_attrs_reject_other_types() {
14385 let f: syn::FieldsNamed = syn::parse_quote!({
14386 #[rustango(citext)] a: i64,
14387 #[rustango(vector(dims = 3))] b: String,
14388 #[rustango(geometry(srid = 4326))] c: String,
14389 #[rustango(citext)] d: String,
14390 });
14391 let errs: Vec<_> = f.named.into_iter().map(error_for).collect();
14392 assert!(errs[0].as_deref().unwrap().contains("citext"), "{errs:?}");
14393 assert!(errs[1].as_deref().unwrap().contains("vector"), "{errs:?}");
14394 assert!(errs[2].as_deref().unwrap().contains("geometry"), "{errs:?}");
14395 assert_eq!(errs[3], None);
14396 }
14397
14398 #[test]
14400 fn manager_ext_is_refused() {
14401 let input: DeriveInput = syn::parse_quote! {
14402 #[rustango(table = "p", manager(ext = "PostManagerExt"))]
14403 struct Post { id: i64 }
14404 };
14405 let err = parse_container_attrs(&input).err().expect("refused");
14406 assert!(err.to_string().contains("#2132"), "{err}");
14407 }
14408
14409 #[test]
14411 fn set_null_needs_an_optional_foreign_key() {
14412 let input: DeriveInput = syn::parse_quote! {
14413 #[rustango(table = "c")]
14414 struct Child {
14415 #[rustango(primary_key)] id: i64,
14416 #[rustango(on_delete = "set_null")] parent: ForeignKey<Parent>,
14417 }
14418 };
14419 let err = expand(&input).err().expect("refused");
14420 assert!(err.to_string().contains("set_null"), "{err}");
14421 let input: DeriveInput = syn::parse_quote! {
14422 #[rustango(table = "c")]
14423 struct Child {
14424 #[rustango(primary_key)] id: i64,
14425 #[rustango(on_delete = "set_null")] parent: Option<ForeignKey<Parent>>,
14426 }
14427 };
14428 assert!(expand(&input).is_ok());
14429 }
14430
14431 #[test]
14433 fn set_null_needs_a_nullable_fk_column() {
14434 let input: DeriveInput = syn::parse_quote! {
14435 #[rustango(table = "c")]
14436 struct Child {
14437 #[rustango(primary_key)] id: i64,
14438 #[rustango(fk = "p", on_delete = "set_null")] parent_id: i64,
14439 #[rustango(o2o = "q", on_delete = "set_null")] other_id: Option<i64>,
14440 }
14441 };
14442 let err = expand(&input).err().expect("refused");
14443 assert!(err.to_string().contains("set_null"), "{err}");
14444 let input: DeriveInput = syn::parse_quote! {
14445 #[rustango(table = "c")]
14446 struct Child {
14447 #[rustango(primary_key)] id: i64,
14448 #[rustango(fk = "p", on_delete = "set_null")] parent_id: std::option::Option<i64>,
14449 #[rustango(o2o = "q", on_delete = "set_null")] other_id: ::core::option::Option<i64>,
14450 #[rustango(on_delete = "set_null")] parent: core::option::Option<ForeignKey<Parent>>,
14451 }
14452 };
14453 assert!(expand(&input).is_ok());
14454 }
14455
14456 #[test]
14459 fn unknown_index_columns_are_errors() {
14460 let input: DeriveInput = syn::parse_quote! {
14461 #[rustango(
14462 table = "w",
14463 unique_together = "a, nope",
14464 index_together = "a, b_typo",
14465 index("missing"),
14466 get_latest_by = "-when_typo"
14467 )]
14468 struct W { #[rustango(primary_key)] id: i64, a: i64, b: i64 }
14469 };
14470 let err = expand(&input).expect_err("must not compile");
14471 let all: Vec<String> = err.into_iter().map(|e| e.to_string()).collect();
14472 for name in ["nope", "b_typo", "missing", "when_typo"] {
14473 assert!(
14474 all.iter().any(|m| m.contains(&format!("`{name}`"))),
14475 "{name}: {all:?}"
14476 );
14477 }
14478 }
14479
14480 #[test]
14483 fn known_index_columns_compile() {
14484 let input: DeriveInput = syn::parse_quote! {
14485 #[rustango(
14486 table = "w",
14487 unique_together = "a, b_col",
14488 index("lower(a)"),
14489 get_latest_by = "-b"
14490 )]
14491 struct W {
14492 #[rustango(primary_key)] id: i64,
14493 a: i64,
14494 #[rustango(column = "b_col")] b: i64,
14495 }
14496 };
14497 expand(&input).expect("compiles");
14498 }
14499
14500 #[test]
14502 fn reverse_accessor_clash_names_related_name() {
14503 let input: DeriveInput = syn::parse_quote! {
14504 #[rustango(table = "m")]
14505 struct Message {
14506 #[rustango(primary_key)] id: i64,
14507 sender: ForeignKey<User>,
14508 recipient: ForeignKey<User>,
14509 }
14510 };
14511 let err = expand(&input).err().expect("refused");
14512 assert!(err.to_string().contains("related_name"), "{err}");
14513 }
14514
14515 #[test]
14518 fn related_name_plus_skips_the_reverse_accessor() {
14519 let input: DeriveInput = syn::parse_quote! {
14520 #[rustango(table = "m")]
14521 struct Message {
14522 #[rustango(primary_key)] id: i64,
14523 sender: ForeignKey<User>,
14524 #[rustango(related_name = "+")] recipient: ForeignKey<User>,
14525 #[rustango(related_name = "+")] owner: ForeignKey<other::Account>,
14526 }
14527 };
14528 let out = expand(&input).expect("derives").to_string();
14529 assert_eq!(out.matches("message_set_pool").count(), 1, "{out}");
14530 assert!(!out.contains("impl other :: Account {"), "{out}");
14531 }
14532
14533 fn field_error(src: &str) -> Option<String> {
14534 let f: syn::FieldsNamed = syn::parse_str(&format!("{{ {src} }}")).unwrap();
14535 let field = f.named.into_iter().next().unwrap();
14536 match parse_field_attrs(&field) {
14537 Err(e) => Some(e.to_string()),
14538 Ok(_) => error_for(field),
14539 }
14540 }
14541
14542 #[test]
14544 fn flag_attrs_accept_eq_true() {
14545 for key in [
14546 "primary_key",
14547 "unique",
14548 "soft_delete",
14549 "index",
14550 "auto_now",
14551 "auto_now_add",
14552 ] {
14553 let f: syn::FieldsNamed =
14554 syn::parse_str(&format!("{{ #[rustango({key} = true)] a: i64 }}")).unwrap();
14555 let field = f.named.into_iter().next().unwrap();
14556 assert!(parse_field_attrs(&field).is_ok(), "{key} = true");
14557 let err = field_error(&format!("#[rustango({key} = false)] a: i64")).unwrap();
14558 assert!(err.contains(&format!("`{key} = false`")), "{err}");
14559 }
14560 let f: syn::Field =
14561 syn::parse_quote!(#[rustango(primary_key = true, unique = true)] a: i64);
14562 let attrs = parse_field_attrs(&f).unwrap();
14563 assert!(attrs.primary_key && attrs.unique);
14564 }
14565
14566 #[test]
14568 fn unknown_key_is_named_with_a_suggestion() {
14569 let err = field_error("#[rustango(max_lenght = 5)] a: String").unwrap();
14570 assert!(err.contains("`max_lenght`"), "{err}");
14571 assert!(err.contains("did you mean `max_length`"), "{err}");
14572 assert!(err.contains("Supported: column"), "{err}");
14573 let input: DeriveInput = syn::parse_quote! {
14574 #[rustango(tabel = "p")]
14575 struct P { id: i64 }
14576 };
14577 let err = parse_container_attrs(&input).err().unwrap().to_string();
14578 assert!(
14579 err.contains("`tabel`") && err.contains("did you mean `table`"),
14580 "{err}"
14581 );
14582 let err = field_error("#[rustango(zzzzzz)] a: i64").unwrap();
14583 assert!(!err.contains("did you mean"), "{err}");
14584 }
14585
14586 #[test]
14588 fn listed_keys_are_all_known() {
14589 for key in FIELD_ATTR_KEYS {
14590 let err = field_error(&format!("#[rustango({key})] a: i64")).unwrap_or_default();
14591 assert!(!err.contains("unknown rustango field"), "{key}: {err}");
14592 }
14593 for key in CONTAINER_ATTR_KEYS {
14594 let input: DeriveInput =
14595 syn::parse_str(&format!("#[rustango({key})] struct P {{ id: i64 }}")).unwrap();
14596 let err = parse_container_attrs(&input)
14597 .err()
14598 .map(|e| e.to_string())
14599 .unwrap_or_default();
14600 assert!(!err.contains("unknown rustango container"), "{key}: {err}");
14601 }
14602 }
14603
14604 #[test]
14606 fn default_shapes_only_sqlite_takes_are_refused() {
14607 for bad in [
14608 r#"#[rustango(default = "draft")] s: String"#,
14609 r#"#[rustango(default = "draft")] s: Option<String>"#,
14610 r#"#[rustango(default = "abc")] n: i32"#,
14611 r#"#[rustango(default = "'abc'")] n: i64"#,
14612 r#"#[rustango(default = "true")] n: i32"#,
14613 r#"#[rustango(default = "")] n: i32"#,
14614 r#"#[rustango(default = "'x'")] n: f64"#,
14615 ] {
14616 assert!(field_error(bad).is_some(), "{bad} must be refused");
14617 }
14618 let err = field_error(r#"#[rustango(default = "draft")] s: String"#).unwrap();
14619 assert!(err.contains(r#"default = "'draft'""#), "{err}");
14620 let err = field_error(r#"#[rustango(default = "current_user")] s: String"#).unwrap();
14622 assert!(
14623 err.contains("Postgres only") && !err.contains("column"),
14624 "{err}"
14625 );
14626 for good in [
14627 r#"#[rustango(default = "'draft'")] s: String"#,
14628 r#"#[rustango(default = "")] s: String"#,
14629 r#"#[rustango(default = "now()")] s: String"#,
14630 r#"#[rustango(default = "CURRENT_TIMESTAMP")] s: String"#,
14631 r#"#[rustango(default = "NULL")] s: Option<String>"#,
14632 r#"#[rustango(default = "0")] n: i32"#,
14633 r#"#[rustango(default = "-1")] n: i64"#,
14634 r#"#[rustango(default = "'5'")] n: i64"#,
14635 r#"#[rustango(default = "(1 + 2)")] n: i64"#,
14636 r#"#[rustango(default = "1.5")] n: f64"#,
14637 r#"#[rustango(default = "true")] b: bool"#,
14638 ] {
14639 assert_eq!(field_error(good), None, "{good}");
14640 }
14641 }
14642
14643 #[test]
14645 fn raw_idents_derive_plain_names() {
14646 let ident: syn::Ident = syn::parse_quote!(r#type);
14647 assert_eq!(column_type_ident(&ident).to_string(), "type_col");
14648 assert_eq!(ident_from_name("type", ident.span()).to_string(), "r#type");
14649 }
14650}
14651
14652#[cfg(test)]
14653mod model_attr_name_tests {
14654 use super::*;
14655
14656 fn container_error(item: proc_macro2::TokenStream) -> Option<String> {
14657 let input: DeriveInput = syn::parse2(item).unwrap();
14658 parse_container_attrs(&input).err().map(|e| e.to_string())
14659 }
14660
14661 fn field_error(field: proc_macro2::TokenStream) -> Option<String> {
14662 let f: syn::FieldsNamed = syn::parse2(quote!({ #field })).unwrap();
14663 let field = f.named.into_iter().next().unwrap();
14664 parse_field_attrs(&field).err().map(|e| e.to_string())
14665 }
14666
14667 #[test]
14669 fn rgo_is_read_like_rustango() {
14670 let long: DeriveInput = syn::parse_quote! {
14671 #[rustango(table = "t", display = "name")] struct S;
14672 };
14673 let short: DeriveInput = syn::parse_quote! {
14674 #[rgo(table = "t", display = "name")] struct S;
14675 };
14676 let (long, short) = (
14677 parse_container_attrs(&long).unwrap(),
14678 parse_container_attrs(&short).unwrap(),
14679 );
14680 assert_eq!(long.table, short.table);
14681 assert_eq!(short.table.as_deref(), Some("t"));
14682 assert_eq!(long.display.map(|d| d.0), short.display.map(|d| d.0));
14683 }
14684
14685 #[test]
14686 fn mixing_names_on_one_item_or_field_is_refused() {
14687 let err = container_error(quote! {
14688 #[rustango(table = "t")] #[rgo(display = "name")] struct S;
14689 })
14690 .expect("a mix on an item must fail");
14691 assert!(
14692 err.contains("`#[rustango(...)]` and `#[rgo(...)]`"),
14693 "{err}"
14694 );
14695 let err = field_error(quote! { #[rgo(primary_key)] #[rustango(column = "c")] a: i64 })
14696 .expect("a mix on a field must fail");
14697 assert!(
14698 err.contains("`#[rgo(...)]` and `#[rustango(...)]`"),
14699 "{err}"
14700 );
14701 assert_eq!(
14703 container_error(quote! { #[rgo(table = "t")] #[rgo(display = "name")] struct S; }),
14704 None
14705 );
14706 assert_eq!(
14707 field_error(quote! { #[rustango(primary_key)] #[rustango(column = "c")] a: i64 }),
14708 None
14709 );
14710 }
14711
14712 #[test]
14714 fn hints_use_the_written_name() {
14715 let err = container_error(quote! { #[rgo(table = "t", unique_together = "a")] struct S; })
14716 .expect("one column must fail");
14717 assert!(err.contains("#[rgo(unique)]"), "{err}");
14718 let err =
14719 container_error(quote! { #[rustango(table = "t", index_together = "a")] struct S; })
14720 .expect("one column must fail");
14721 assert!(err.contains("#[rustango(index)]"), "{err}");
14722 }
14723}