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))]
102pub fn derive_viewset(input: TokenStream) -> TokenStream {
103 let input = parse_macro_input!(input as DeriveInput);
104 expand_viewset(&input)
105 .unwrap_or_else(syn::Error::into_compile_error)
106 .into()
107}
108
109#[proc_macro_derive(Form, attributes(form))]
137pub fn derive_form(input: TokenStream) -> TokenStream {
138 let input = parse_macro_input!(input as DeriveInput);
139 expand_form(&input)
140 .unwrap_or_else(syn::Error::into_compile_error)
141 .into()
142}
143
144#[proc_macro_derive(Serializer, attributes(serializer, serde))]
169pub fn derive_serializer(input: TokenStream) -> TokenStream {
170 let input = parse_macro_input!(input as DeriveInput);
171 expand_serializer(&input)
172 .unwrap_or_else(syn::Error::into_compile_error)
173 .into()
174}
175
176#[proc_macro]
215pub fn embed_migrations(input: TokenStream) -> TokenStream {
216 expand_embed_migrations(input.into())
217 .unwrap_or_else(syn::Error::into_compile_error)
218 .into()
219}
220
221#[proc_macro]
232pub fn embed_static(input: TokenStream) -> TokenStream {
233 expand_embed_static(input.into())
234 .unwrap_or_else(syn::Error::into_compile_error)
235 .into()
236}
237
238fn expand_embed_static(input: TokenStream2) -> syn::Result<TokenStream2> {
239 let lit: LitStr = syn::parse2(input)?;
240 let manifest = std::env::var("CARGO_MANIFEST_DIR").map_err(|_| {
241 syn::Error::new(
242 lit.span(),
243 "embed_static! needs a Cargo build (CARGO_MANIFEST_DIR not set)",
244 )
245 })?;
246 let root = std::path::Path::new(&manifest).join(lit.value());
247 if !root.is_dir() {
248 return Err(syn::Error::new(
249 lit.span(),
250 format!("embed_static!: {} is not a directory", root.display()),
251 ));
252 }
253 let mut files = Vec::new();
254 walk_static(&root, "", &mut files)
255 .map_err(|e| syn::Error::new(lit.span(), format!("embed_static!: {e}")))?;
256 files.sort();
257 let entries = files.iter().map(|(rel, abs)| {
258 quote! { (#rel, ::core::include_bytes!(#abs) as &'static [u8]) }
259 });
260 let root_path = rustango_root();
261 Ok(quote! {
262 #root_path::static_files::EmbeddedDir::__new(&[ #( #entries ),* ])
263 })
264}
265
266fn walk_static(
268 dir: &std::path::Path,
269 rel: &str,
270 out: &mut Vec<(String, String)>,
271) -> std::io::Result<()> {
272 for entry in std::fs::read_dir(dir)? {
273 let entry = entry?;
274 let name = entry.file_name();
275 let Some(name) = name.to_str() else { continue };
276 if name.starts_with('.') {
277 continue;
278 }
279 let path = if rel.is_empty() {
280 name.to_owned()
281 } else {
282 format!("{rel}/{name}")
283 };
284 let kind = entry.file_type()?;
285 if kind.is_dir() {
286 walk_static(&entry.path(), &path, out)?;
287 } else if kind.is_file() {
288 let abs = entry.path().to_str().map(str::to_owned).ok_or_else(|| {
289 std::io::Error::other(format!("{} is not UTF-8", entry.path().display()))
290 })?;
291 out.push((path, abs));
292 }
293 }
294 Ok(())
295}
296
297#[allow(non_snake_case)]
347#[proc_macro]
348pub fn Q(input: TokenStream) -> TokenStream {
349 expand_q(input.into())
350 .unwrap_or_else(syn::Error::into_compile_error)
351 .into()
352}
353
354#[proc_macro_attribute]
400pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
401 expand_main(args.into(), item.into())
402 .unwrap_or_else(syn::Error::into_compile_error)
403 .into()
404}
405
406fn expand_main(args: TokenStream2, item: TokenStream2) -> syn::Result<TokenStream2> {
407 let mut input: syn::ItemFn = syn::parse2(item)?;
408 if input.sig.asyncness.is_none() {
409 return Err(syn::Error::new(
410 input.sig.ident.span(),
411 "`#[rustango::main]` must wrap an `async fn`",
412 ));
413 }
414
415 let root = rustango_root();
425 let MainArgs {
426 flavor,
427 logging,
428 stack_size,
429 } = parse_main_args(&args)?;
430 let stack_size = match stack_size {
431 Some(n) => quote!(#n),
432 None => quote!(#root::__private_runtime::MAIN_STACK_SIZE),
433 };
434 let builder_call = match flavor {
435 Flavor::CurrentThread => quote! {
436 #root::__private_runtime::tokio::runtime::Builder::new_current_thread()
437 },
438 Flavor::MultiThread => quote! {
440 #root::__private_runtime::tokio::runtime::Builder::new_multi_thread()
441 .thread_stack_size(#stack_size)
442 },
443 };
444
445 let mut inner = input.clone();
448 inner.sig.ident = syn::Ident::new("__rustango_main_body", input.sig.ident.span());
449 inner.attrs.retain(|a| {
451 ["allow", "expect", "deny", "warn", "forbid"]
452 .iter()
453 .any(|l| a.path().is_ident(l))
454 });
455 inner.vis = syn::Visibility::Inherited;
456 input.sig.asyncness = None;
457 input.sig.output = syn::parse2(quote!(-> ::std::process::ExitCode))?;
458
459 let logging_prologue = if logging {
463 quote! {
464 let _ = #root::logging::Setup::new()
469 .with_default_env_filter(#root::__private_runtime::main_default_filter())
470 .with_format(#root::__private_runtime::main_format())
471 .install();
472 }
473 } else {
474 quote! {}
475 };
476
477 input.block = syn::parse2(quote! {{
478 { #logging_prologue }
479 #inner
480 #root::__private_runtime::run_on_main_thread(#stack_size, || {
481 let __rt = #builder_call
482 .enable_all()
483 .build()
484 .expect("failed to build tokio runtime");
485 ::std::process::Termination::report(__rt.block_on(__rustango_main_body()))
486 })
487 }})?;
488
489 Ok(quote! {
490 #input
491 })
492}
493
494enum Flavor {
495 MultiThread,
496 CurrentThread,
497}
498
499struct MainArgs {
501 flavor: Flavor,
502 logging: bool,
503 stack_size: Option<usize>,
505}
506
507const MIN_STACK_SIZE: usize = 64 * 1024;
509
510const MAIN_ARGS_HELP: &str =
511 "supported: `flavor = \"current_thread\" | \"multi_thread\"`, `logging = true | false`, `stack_size = <bytes>`";
512
513fn parse_main_args(args: &TokenStream2) -> syn::Result<MainArgs> {
515 use syn::parse::Parser as _;
516 let metas = syn::punctuated::Punctuated::<syn::MetaNameValue, syn::Token![,]>::parse_terminated
517 .parse2(args.clone())
518 .map_err(|e| {
519 syn::Error::new(
520 e.span(),
521 format!("#[rustango::main]: {e}; {MAIN_ARGS_HELP}"),
522 )
523 })?;
524 let mut out = MainArgs {
525 flavor: Flavor::MultiThread,
526 logging: true,
527 stack_size: None,
528 };
529 for meta in &metas {
530 let lit = match &meta.value {
531 syn::Expr::Lit(l) => Some(&l.lit),
532 _ => None,
533 };
534 let name = meta.path.get_ident().map(ToString::to_string);
535 match (name.as_deref(), lit) {
536 (Some("flavor"), Some(syn::Lit::Str(s))) if s.value() == "current_thread" => {
537 out.flavor = Flavor::CurrentThread;
538 }
539 (Some("flavor"), Some(syn::Lit::Str(s))) if s.value() == "multi_thread" => {
540 out.flavor = Flavor::MultiThread;
541 }
542 (Some("logging"), Some(syn::Lit::Bool(b))) => out.logging = b.value,
543 (Some("stack_size"), Some(syn::Lit::Int(n))) => {
544 let bytes = n.base10_parse::<usize>()?;
545 if bytes < MIN_STACK_SIZE {
546 return Err(syn::Error::new(
547 n.span(),
548 format!("#[rustango::main]: `stack_size` must be at least {MIN_STACK_SIZE} bytes"),
549 ));
550 }
551 out.stack_size = Some(bytes);
552 }
553 _ => {
554 return Err(syn::Error::new(
555 meta.span(),
556 format!("#[rustango::main]: unsupported argument; {MAIN_ARGS_HELP}"),
557 ))
558 }
559 }
560 }
561 Ok(out)
562}
563
564struct QInput {
566 base_path: syn::Path,
567 field: syn::Ident,
568 value: syn::Expr,
569}
570
571impl syn::parse::Parse for QInput {
572 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
573 let base_path: syn::Path = input.parse()?;
574 input.parse::<syn::Token![.]>()?;
575 let field: syn::Ident = input.parse()?;
576 input.parse::<syn::Token![=]>()?;
577 let value: syn::Expr = input.parse()?;
578 Ok(QInput {
579 base_path,
580 field,
581 value,
582 })
583 }
584}
585
586fn expand_q(input: TokenStream2) -> syn::Result<TokenStream2> {
587 let q: QInput = syn::parse2(input)?;
588 let root = rustango_root();
589 let field_str = ident_name(&q.field);
590 let field_span = q.field.span();
591 let (base, suffix) = match field_str.find("__") {
592 Some(idx) => (&field_str[..idx], &field_str[idx + 2..]),
593 None => (field_str.as_str(), ""),
594 };
595 if base.is_empty() {
596 return Err(syn::Error::new(
597 field_span,
598 "Q!(): field name is empty before `__` suffix",
599 ));
600 }
601 let base_ident = ident_from_name(base, field_span);
602 let value = &q.value;
603 let path = &q.base_path;
604
605 let expanded = match suffix {
610 "" | "exact" => quote! {
611 #root::core::Column::eq(#path::#base_ident, #value)
612 },
613 "ne" => quote! {
614 #root::core::Column::ne(#path::#base_ident, #value)
615 },
616 "gt" => quote! {
617 #root::core::Column::gt(#path::#base_ident, #value)
618 },
619 "gte" => quote! {
620 #root::core::Column::gte(#path::#base_ident, #value)
621 },
622 "lt" => quote! {
623 #root::core::Column::lt(#path::#base_ident, #value)
624 },
625 "lte" => quote! {
626 #root::core::Column::lte(#path::#base_ident, #value)
627 },
628 "iexact" => quote! {
633 #root::core::Column::iexact(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
634 },
635 "contains" => quote! {
636 #root::core::Column::contains(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
637 },
638 "icontains" => quote! {
639 #root::core::Column::icontains(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
640 },
641 "startswith" => quote! {
642 #root::core::Column::startswith(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
643 },
644 "istartswith" => quote! {
645 #root::core::Column::istartswith(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
646 },
647 "endswith" => quote! {
648 #root::core::Column::endswith(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
649 },
650 "iendswith" => quote! {
651 #root::core::Column::iendswith(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
652 },
653 "in" => quote! {
654 #root::core::Column::is_in(#path::#base_ident, #value)
655 },
656 "not_in" => quote! {
657 #root::core::Column::not_in(#path::#base_ident, #value)
658 },
659 "isnull" => {
660 let b = match value {
663 syn::Expr::Lit(syn::ExprLit {
664 lit: syn::Lit::Bool(b),
665 ..
666 }) => b.value(),
667 _ => {
668 return Err(syn::Error::new_spanned(
669 value,
670 "Q!(): `__isnull` requires a `true` or `false` literal",
671 ));
672 }
673 };
674 if b {
675 quote! { #root::core::Column::is_null(#path::#base_ident) }
676 } else {
677 quote! { #root::core::Column::is_not_null(#path::#base_ident) }
678 }
679 }
680 "between" => {
681 let tuple = match value {
683 syn::Expr::Tuple(t) if t.elems.len() == 2 => t,
684 _ => {
685 return Err(syn::Error::new_spanned(
686 value,
687 "Q!(): `__between` requires a tuple literal `(lo, hi)`",
688 ));
689 }
690 };
691 let lo = &tuple.elems[0];
692 let hi = &tuple.elems[1];
693 quote! { #root::core::Column::between(#path::#base_ident, #lo, #hi) }
694 }
695 "regex" => quote! {
696 #root::core::Column::regex(#path::#base_ident, #value)
697 },
698 "iregex" => quote! {
699 #root::core::Column::iregex(#path::#base_ident, #value)
700 },
701 _ => {
702 return Err(syn::Error::new(
703 field_span,
704 format!(
705 "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",
706 suffix
707 ),
708 ));
709 }
710 };
711 Ok(expanded)
712}
713
714fn expand_embed_migrations(input: TokenStream2) -> syn::Result<TokenStream2> {
715 let path_str = if input.is_empty() {
717 "./migrations".to_string()
718 } else {
719 let lit: LitStr = syn::parse2(input)?;
720 lit.value()
721 };
722
723 let manifest = std::env::var("CARGO_MANIFEST_DIR").map_err(|_| {
724 syn::Error::new(
725 proc_macro2::Span::call_site(),
726 "embed_migrations! must be invoked during a Cargo build (CARGO_MANIFEST_DIR not set)",
727 )
728 })?;
729 let abs = std::path::Path::new(&manifest).join(&path_str);
730
731 let mut entries: Vec<(String, std::path::PathBuf)> = Vec::new();
732 if abs.is_dir() {
733 let read = std::fs::read_dir(&abs).map_err(|e| {
734 syn::Error::new(
735 proc_macro2::Span::call_site(),
736 format!("embed_migrations!: cannot read {}: {e}", abs.display()),
737 )
738 })?;
739 for entry in read.flatten() {
740 let path = entry.path();
741 if !path.is_file() {
742 continue;
743 }
744 if path.extension().and_then(|s| s.to_str()) != Some("json") {
745 continue;
746 }
747 let Some(stem) = path.file_stem().and_then(|s| s.to_str()) else {
748 continue;
749 };
750 entries.push((stem.to_owned(), path));
751 }
752 }
753 entries.sort_by(|a, b| a.0.cmp(&b.0));
754
755 let mut chain_names: Vec<String> = Vec::with_capacity(entries.len());
768 let mut prev_refs: Vec<(String, Option<String>)> = Vec::with_capacity(entries.len());
769 let mut texts: Vec<String> = Vec::with_capacity(entries.len());
770 for (stem, path) in &entries {
771 let raw = std::fs::read(path)
772 .map_err(|e| e.to_string())
773 .and_then(decode_text)
774 .map_err(|e| {
775 syn::Error::new(
776 proc_macro2::Span::call_site(),
777 format!(
778 "embed_migrations!: cannot read {} for chain validation: {e}",
779 path.display()
780 ),
781 )
782 })?;
783 let json: serde_json::Value = serde_json::from_str(&raw).map_err(|e| {
784 syn::Error::new(
785 proc_macro2::Span::call_site(),
786 format!(
787 "embed_migrations!: {} is not valid JSON: {e}",
788 path.display()
789 ),
790 )
791 })?;
792 let name = json
793 .get("name")
794 .and_then(|v| v.as_str())
795 .ok_or_else(|| {
796 syn::Error::new(
797 proc_macro2::Span::call_site(),
798 format!(
799 "embed_migrations!: {} is missing the `name` field",
800 path.display()
801 ),
802 )
803 })?
804 .to_owned();
805 if name != *stem {
806 return Err(syn::Error::new(
807 proc_macro2::Span::call_site(),
808 format!(
809 "embed_migrations!: file stem `{stem}` does not match the migration's \
810 `name` field `{name}` — rename the file or fix the JSON",
811 ),
812 ));
813 }
814 let prev = json.get("prev").and_then(|v| v.as_str()).map(str::to_owned);
815 chain_names.push(name.clone());
816 prev_refs.push((name, prev));
817 texts.push(raw);
818 }
819
820 let name_set: std::collections::HashSet<&str> =
821 chain_names.iter().map(String::as_str).collect();
822 for (name, prev) in &prev_refs {
823 if let Some(p) = prev {
824 if !name_set.contains(p.as_str()) {
825 return Err(syn::Error::new(
826 proc_macro2::Span::call_site(),
827 format!(
828 "embed_migrations!: broken migration chain — `{name}` declares \
829 prev=`{p}` but no migration with that name exists in {}",
830 abs.display()
831 ),
832 ));
833 }
834 }
835 }
836
837 let pairs: Vec<TokenStream2> = entries
840 .iter()
841 .zip(&texts)
842 .map(|((name, path), text)| {
843 let path_lit = path.display().to_string();
844 quote! { (#name, {
845 const _: &[u8] = ::core::include_bytes!(#path_lit);
846 #text
847 }) }
848 })
849 .collect();
850
851 let missing_warning = if abs.is_dir() {
854 quote! {}
855 } else {
856 let note = format!(
857 "embed_migrations!: directory {} does not exist; no migrations are embedded",
858 abs.display()
859 );
860 quote! {
861 #[deprecated(note = #note)]
862 const __RUSTANGO_MIGRATIONS_DIR_MISSING: () = ();
863 #[allow(clippy::let_unit_value)]
864 let _ = __RUSTANGO_MIGRATIONS_DIR_MISSING;
865 }
866 };
867
868 Ok(quote! {
869 {
870 #missing_warning
871 const __RUSTANGO_EMBEDDED: &[(&'static str, &'static str)] = &[#(#pairs),*];
872 __RUSTANGO_EMBEDDED
873 }
874 })
875}
876
877fn expand(input: &DeriveInput) -> syn::Result<TokenStream2> {
878 let root = rustango_root();
879 let struct_name = &input.ident;
880
881 let Data::Struct(data) = &input.data else {
882 return Err(syn::Error::new_spanned(
883 struct_name,
884 "Model can only be derived on structs",
885 ));
886 };
887 let Fields::Named(named) = &data.fields else {
888 return Err(syn::Error::new_spanned(
889 struct_name,
890 "Model requires a struct with named fields",
891 ));
892 };
893
894 let mut container = parse_container_attrs(input)?;
895 let table = container
896 .table
897 .unwrap_or_else(|| to_snake_case(&struct_name.to_string()));
898 let model_name = struct_name.to_string();
899
900 let collected = collect_fields(named, &table)?;
901 let explicit_pks = container
903 .reverse_has_relations
904 .iter()
905 .map(|r| (&r.self_pk_column, r.self_pk_span))
906 .chain(
907 container
908 .generic_has_relations
909 .iter()
910 .map(|r| (&r.self_pk_column, r.self_pk_span)),
911 );
912 for (column, span) in explicit_pks {
913 if let (Some(column), Some(span)) = (column, span) {
914 if !collected.column_entries.iter().any(|c| &c.column == column) {
915 return Err(syn::Error::new(
916 span,
917 format!("`self_pk_column = \"{column}\"` names no column of `{model_name}`"),
918 ));
919 }
920 }
921 }
922 if let Some((_, pk_column)) = &collected.primary_key {
924 for rel in &mut container.reverse_has_relations {
925 rel.self_pk_column.get_or_insert_with(|| pk_column.clone());
926 }
927 for rel in &mut container.generic_has_relations {
928 rel.self_pk_column.get_or_insert_with(|| pk_column.clone());
929 }
930 }
931
932 if let Some((ref display, span)) = container.display {
934 if !collected.field_names.iter().any(|n| n == display) {
935 return Err(syn::Error::new(
936 span,
937 format!("`display = \"{display}\"` does not match any field on this struct"),
938 ));
939 }
940 }
941 let display = container.display.map(|(name, _)| name);
942 let app_label = container.app.clone();
943
944 if let Some(admin) = &container.admin {
954 for (label, list) in [
955 ("search_fields", &admin.search_fields),
956 ("readonly_fields", &admin.readonly_fields),
957 ("list_filter", &admin.list_filter),
958 ] {
959 if let Some((names, span)) = list {
960 for name in names {
961 if !collected.field_names.iter().any(|n| n == name) {
962 return Err(syn::Error::new(
963 *span,
964 format!(
965 "`{label} = \"{name}\"`: \"{name}\" is not a declared field on this struct"
966 ),
967 ));
968 }
969 }
970 }
971 }
972 if let Some((pairs, span)) = &admin.ordering {
973 for (name, _) in pairs {
974 if !collected.field_names.iter().any(|n| n == name) {
975 return Err(syn::Error::new(
976 *span,
977 format!(
978 "`ordering = \"{name}\"`: \"{name}\" is not a declared field on this struct"
979 ),
980 ));
981 }
982 }
983 }
984 if let Some((groups, span)) = &admin.fieldsets {
985 for (_, fields) in groups {
986 for name in fields {
987 if !collected.field_names.iter().any(|n| n == name) {
988 return Err(syn::Error::new(
989 *span,
990 format!(
991 "`fieldsets`: \"{name}\" is not a declared field on this struct"
992 ),
993 ));
994 }
995 }
996 }
997 }
998 }
999 if let Some(audit) = &container.audit {
1000 if let Some((names, span)) = &audit.track {
1001 for name in names {
1002 if !collected.field_names.iter().any(|n| n == name) {
1003 return Err(syn::Error::new(
1004 *span,
1005 format!(
1006 "`audit(track = \"{name}\")`: \"{name}\" is not a declared field on this struct"
1007 ),
1008 ));
1009 }
1010 }
1011 }
1012 }
1013
1014 for (col, _desc, span) in &container.default_order {
1018 if !collected.field_names.iter().any(|n| n == col) {
1019 return Err(syn::Error::new(
1020 *span,
1021 format!(
1022 "`default_order = \"...\"`: \"{col}\" is not a declared field on this struct"
1023 ),
1024 ));
1025 }
1026 }
1027
1028 check_known_columns(&container.indexes, &container.get_latest_by, &collected)?;
1029
1030 let audit_track_names: Option<Vec<String>> = container.audit.as_ref().map(|audit| {
1033 audit
1034 .track
1035 .as_ref()
1036 .map(|(names, _)| names.clone())
1037 .unwrap_or_default()
1038 });
1039
1040 let upsert_unique: Option<Vec<String>> = container
1043 .indexes
1044 .iter()
1045 .find(|i| i.unique && i.where_clause.is_none() && !i.columns.is_empty())
1046 .map(|i| i.columns.clone());
1047
1048 let mut all_indexes: Vec<IndexAttr> = container.indexes;
1050 for field in &named.named {
1051 let ident = field.ident.as_ref().expect("named");
1052 let col = ident_name(ident);
1054 if let Ok(fa) = parse_field_attrs(field) {
1056 if fa.index {
1057 let col_name = fa.column.clone().unwrap_or_else(|| col.clone());
1058 let auto_name = capped_index_name(if fa.index_unique {
1059 format!("{table}_{col_name}_uq_idx")
1060 } else {
1061 format!("{table}_{col_name}_idx")
1062 });
1063 all_indexes.push(IndexAttr {
1064 span: field.span(),
1065 name: fa.index_name.or(Some(auto_name)),
1066 columns: vec![col_name],
1067 unique: fa.index_unique,
1068 method: fa.index_method,
1069 where_clause: None,
1070 include: Vec::new(),
1071 });
1072 }
1073 }
1074 }
1075 for field in &named.named {
1077 let (Ok(fa), Ok(det)) = (parse_field_attrs(field), detect_type(&field.ty)) else {
1078 continue;
1079 };
1080 if det.kind != DetectedKind::String || fa.max_length.is_some() {
1081 continue;
1082 }
1083 let col = fa
1084 .column
1085 .clone()
1086 .unwrap_or_else(|| ident_name(field.ident.as_ref().expect("named")));
1087 let relation = det.fk_inner.is_some() || fa.fk.is_some() || fa.o2o.is_some();
1089 if fa.primary_key
1090 || fa.unique
1091 || relation
1092 || all_indexes.iter().any(|i| i.columns.contains(&col))
1093 {
1094 return Err(syn::Error::new_spanned(
1095 field,
1096 "a primary-key, unique, indexed or foreign-key `String` needs \
1097 `max_length`: MySQL cannot index an unbounded (LONGTEXT) column",
1098 ));
1099 }
1100 }
1101
1102 let model_impl = model_impl_tokens(
1103 struct_name,
1104 &model_name,
1105 &table,
1106 display.as_deref(),
1107 app_label.as_deref(),
1108 container.admin.as_ref(),
1109 &container.default_order,
1110 &collected.field_schemas,
1111 collected.soft_delete_column.as_deref(),
1112 container.permissions,
1113 audit_track_names.as_deref(),
1114 &container.m2m,
1115 &all_indexes,
1116 &container.checks,
1117 &container.excludes,
1118 &container.composite_fks,
1119 &container.generic_fks,
1120 container.scope.as_deref(),
1121 container.access.as_deref(),
1122 container.is_view,
1123 container.verbose_name.as_deref(),
1124 container.verbose_name_plural.as_deref(),
1125 container.managed,
1126 container.base_manager_name.as_deref(),
1127 container.order_with_respect_to.as_deref(),
1128 container.proxy,
1129 &container.required_db_features,
1130 container.required_db_vendor.as_deref(),
1131 container.default_related_name.as_deref(),
1132 container.db_table_comment.as_deref(),
1133 container
1134 .get_latest_by
1135 .as_ref()
1136 .map(|(c, d, _)| (c.as_str(), *d)),
1137 &container.extra_permissions,
1138 &container.default_permissions,
1139 &container.global_scopes,
1140 &container.reverse_has_relations,
1141 &container.generic_has_relations,
1142 );
1143 let module_ident = column_module_ident(struct_name);
1144 let column_consts = column_const_tokens(&module_ident, &collected.column_entries);
1145 let audited_fields: Option<Vec<&ColumnEntry>> = container.audit.as_ref().map(|audit| {
1146 let track_set: Option<std::collections::HashSet<&str>> = audit
1147 .track
1148 .as_ref()
1149 .map(|(names, _)| names.iter().map(String::as_str).collect());
1150 collected
1151 .column_entries
1152 .iter()
1153 .filter(|c| {
1154 track_set
1155 .as_ref()
1156 .map_or(true, |s| s.contains(c.name.as_str()))
1157 })
1158 .collect()
1159 });
1160 let inherent_impl = inherent_impl_tokens(
1161 struct_name,
1162 &collected,
1163 collected.primary_key.as_ref(),
1164 &column_consts,
1165 audited_fields.as_deref(),
1166 upsert_unique.as_deref(),
1167 &container.manager_fns,
1168 );
1169 let column_module = column_module_tokens(&module_ident, struct_name, &collected.column_entries);
1170 let from_row_impl = from_row_impl_tokens(
1171 struct_name,
1172 &collected.from_row_inits,
1173 &collected.from_row_inits_my,
1174 );
1175 let reverse_helpers = reverse_helper_tokens(
1176 struct_name,
1177 &collected.fk_relations,
1178 container.default_related_name.as_deref(),
1179 )?;
1180 let m2m_accessors = m2m_accessor_tokens(struct_name, &container.m2m);
1181 let generic_m2m_accessors = generic_m2m_accessor_tokens(struct_name, &container.generic_m2m);
1182 let through_accessors = through_accessor_tokens(struct_name, &container.through_relations);
1184 let reverse_has_accessors = reverse_has_accessor_tokens(
1186 struct_name,
1187 &container.reverse_has_relations,
1188 &collected.column_entries,
1189 );
1190 let generic_fk_accessors = generic_fk_accessor_tokens(
1191 struct_name,
1192 &container.generic_fks,
1193 &collected.column_entries,
1194 );
1195
1196 Ok(quote! {
1197 #model_impl
1198 #inherent_impl
1199 #from_row_impl
1200 #column_module
1201 #reverse_helpers
1202 #m2m_accessors
1203 #generic_m2m_accessors
1204 #through_accessors
1205 #reverse_has_accessors
1206 #generic_fk_accessors
1207
1208 #root::core::inventory::submit! {
1209 #root::core::ModelEntry::new(
1214 <#struct_name as #root::core::Model>::SCHEMA,
1215 ::core::module_path!(),
1216 )
1217 .with_audited(
1218 <#struct_name as #root::core::Model>::__rustango_audited_update,
1219 <#struct_name as #root::core::Model>::__rustango_audited_delete,
1220 )
1221 .with_audited_create(<#struct_name as #root::core::Model>::__rustango_audited_create)
1222 .with_audited_update_record(
1223 <#struct_name as #root::core::Model>::__rustango_audited_update_record,
1224 )
1225 .with_audited_cascade(<#struct_name as #root::core::Model>::__rustango_audited_cascade)
1226 }
1227 })
1228}
1229
1230fn check_known_columns(
1233 indexes: &[IndexAttr],
1234 get_latest_by: &Option<(String, bool, proc_macro2::Span)>,
1235 collected: &CollectedFields,
1236) -> syn::Result<()> {
1237 let is_column = |c: &str| collected.column_entries.iter().any(|e| e.column == c);
1238 let plain = |c: &str| c.chars().all(|ch| ch.is_ascii_alphanumeric() || ch == '_');
1240 let mut found: Vec<(proc_macro2::Span, String)> = Vec::new();
1241 for idx in indexes {
1242 for col in idx.columns.iter().filter(|c| plain(c) && !is_column(c)) {
1243 found.push((
1244 idx.span,
1245 format!("index column `{col}` is not a column of this model; its DDL will fail"),
1246 ));
1247 }
1248 }
1249 if let Some((col, _, span)) = get_latest_by {
1250 if !is_column(col) && !collected.field_names.iter().any(|n| n == col) {
1251 found.push((
1252 *span,
1253 format!("`get_latest_by`: `{col}` is not a field of this model"),
1254 ));
1255 }
1256 }
1257 let mut errors = found
1258 .into_iter()
1259 .map(|(span, note)| syn::Error::new(span, note));
1260 let Some(mut first) = errors.next() else {
1261 return Ok(());
1262 };
1263 first.extend(errors);
1264 Err(first)
1265}
1266
1267fn join_guard_tokens(rel: &FkRelation, receiver: &TokenStream2) -> TokenStream2 {
1270 let root = rustango_root();
1271 let parent_ty = &rel.parent_type;
1272 let field_ident = &rel.field_ident;
1273 let null_fk = if rel.nullable {
1274 quote! {
1275 if #receiver.#field_ident.is_none() {
1276 return ::core::result::Result::Ok(false);
1277 }
1278 }
1279 } else {
1280 quote! {}
1281 };
1282 quote! {
1283 #null_fk
1284 #root::sql::__rustango_require_join(
1285 row, <#parent_ty as #root::core::Model>::SCHEMA, alias,
1286 )?;
1287 }
1288}
1289
1290fn loaded_reuse_tokens(
1294 rel: &FkRelation,
1295 receiver: &TokenStream2,
1296 load: &TokenStream2,
1297) -> TokenStream2 {
1298 let root = rustango_root();
1299 let field_ident = &rel.field_ident;
1300 let pattern = if rel.nullable {
1301 quote!(::core::option::Option::Some(#root::sql::ForeignKey::Loaded { value: __loaded, .. }))
1302 } else {
1303 quote!(#root::sql::ForeignKey::Loaded { value: __loaded, .. })
1304 };
1305 quote! {
1306 if let #pattern = &mut #receiver.#field_ident {
1307 if let ::core::option::Option::Some(__r) = __rest {
1308 let _ = #load(&mut **__loaded, row, __r, &__next_alias)?;
1309 }
1310 return ::core::result::Result::Ok(true);
1311 }
1312 }
1313}
1314
1315fn loaded_assign_tokens(rel: &FkRelation, receiver: &TokenStream2) -> TokenStream2 {
1318 let root = rustango_root();
1319 let field_ident = &rel.field_ident;
1320 if rel.nullable {
1321 quote! {
1322 if let ::core::option::Option::Some(__fk) = &mut #receiver.#field_ident {
1323 let _pk = ::core::clone::Clone::clone(__fk.pk_ref());
1324 *__fk = #root::sql::ForeignKey::loaded(_pk, _parent);
1325 }
1326 }
1327 } else {
1328 quote! {
1329 let _pk = ::core::clone::Clone::clone(#receiver.#field_ident.pk_ref());
1330 #receiver.#field_ident = #root::sql::ForeignKey::loaded(_pk, _parent);
1331 }
1332 }
1333}
1334
1335fn load_related_impl_tokens(struct_name: &syn::Ident, fk_relations: &[FkRelation]) -> TokenStream2 {
1346 let root = rustango_root();
1347 let arms = fk_relations.iter().map(|rel| {
1348 let parent_ty = &rel.parent_type;
1349 let field_ident = &rel.field_ident;
1350 let fk_name = ident_name(field_ident);
1351 let fk_col = fk_name.as_str();
1352 let guard = join_guard_tokens(rel, "e!(self));
1353 let reuse = loaded_reuse_tokens(
1354 rel,
1355 "e!(self),
1356 "e!(#root::sql::LoadRelated::__rustango_load_related),
1357 );
1358 let assign = loaded_assign_tokens(rel, "e!(self));
1359 quote! {
1360 #fk_col => {
1361 #guard
1362 #reuse
1363 let mut _parent: #parent_ty = <#parent_ty>::__rustango_from_aliased_row(row, alias)?;
1364 if let ::core::option::Option::Some(__r) = __rest {
1370 let _ = #root::sql::LoadRelated::__rustango_load_related(
1371 &mut _parent, row, __r, &__next_alias,
1372 )?;
1373 }
1374 #assign
1375 ::core::result::Result::Ok(true)
1376 }
1377 }
1378 });
1379 quote! {
1380 #[cfg(feature = "postgres")]
1381 impl #root::sql::LoadRelated for #struct_name {
1382 #[allow(unused_variables)]
1383 fn __rustango_load_related(
1384 &mut self,
1385 row: &#root::sql::sqlx::postgres::PgRow,
1386 field_name: &str,
1387 alias: &str,
1388 ) -> ::core::result::Result<bool, #root::sql::sqlx::Error> {
1389 let (__base, __rest): (&str, ::core::option::Option<&str>) =
1395 match field_name.split_once("__") {
1396 ::core::option::Option::Some((b, r)) => (b, ::core::option::Option::Some(r)),
1397 ::core::option::Option::None => (field_name, ::core::option::Option::None),
1398 };
1399 let __next_alias: ::std::string::String = match __rest {
1400 ::core::option::Option::Some(__r) => {
1401 let __rb = __r.split_once("__").map(|(b, _)| b).unwrap_or(__r);
1402 ::std::format!("{}__{}", alias, __rb)
1403 }
1404 ::core::option::Option::None => ::std::string::String::new(),
1405 };
1406 match __base {
1407 #( #arms )*
1408 _ => ::core::result::Result::Ok(false),
1409 }
1410 }
1411 }
1412 }
1413}
1414
1415fn load_related_impl_my_tokens(
1423 struct_name: &syn::Ident,
1424 fk_relations: &[FkRelation],
1425) -> TokenStream2 {
1426 let root = rustango_root();
1427 let arms = fk_relations.iter().map(|rel| {
1428 let parent_ty = &rel.parent_type;
1429 let field_ident = &rel.field_ident;
1430 let fk_name = ident_name(field_ident);
1431 let fk_col = fk_name.as_str();
1432 let guard = join_guard_tokens(rel, "e!(__self));
1433 let reuse = loaded_reuse_tokens(
1434 rel,
1435 "e!(__self),
1436 "e!(#root::sql::LoadRelatedMy::__rustango_load_related_my),
1437 );
1438 let assign = loaded_assign_tokens(rel, "e!(__self));
1439 quote! {
1444 #fk_col => {
1445 #guard
1446 #reuse
1447 let mut _parent: #parent_ty =
1448 <#parent_ty>::__rustango_from_aliased_my_row(row, alias)?;
1449 if let ::core::option::Option::Some(__r) = __rest {
1452 let _ = #root::sql::LoadRelatedMy::__rustango_load_related_my(
1453 &mut _parent, row, __r, &__next_alias,
1454 )?;
1455 }
1456 #assign
1457 ::core::result::Result::Ok(true)
1458 }
1459 }
1460 });
1461 quote! {
1462 #root::__impl_my_load_related!(#struct_name, |__self, row, field_name, alias, __rest, __next_alias| {
1463 #( #arms )*
1464 });
1465 }
1466}
1467
1468fn load_related_impl_sqlite_tokens(
1472 struct_name: &syn::Ident,
1473 fk_relations: &[FkRelation],
1474) -> TokenStream2 {
1475 let root = rustango_root();
1476 let arms = fk_relations.iter().map(|rel| {
1477 let parent_ty = &rel.parent_type;
1478 let field_ident = &rel.field_ident;
1479 let fk_name = ident_name(field_ident);
1480 let fk_col = fk_name.as_str();
1481 let guard = join_guard_tokens(rel, "e!(__self));
1482 let reuse = loaded_reuse_tokens(
1483 rel,
1484 "e!(__self),
1485 "e!(#root::sql::LoadRelatedSqlite::__rustango_load_related_sqlite),
1486 );
1487 let assign = loaded_assign_tokens(rel, "e!(__self));
1488 quote! {
1489 #fk_col => {
1490 #guard
1491 #reuse
1492 let mut _parent: #parent_ty =
1493 <#parent_ty>::__rustango_from_aliased_sqlite_row(row, alias)?;
1494 if let ::core::option::Option::Some(__r) = __rest {
1497 let _ = #root::sql::LoadRelatedSqlite::__rustango_load_related_sqlite(
1498 &mut _parent, row, __r, &__next_alias,
1499 )?;
1500 }
1501 #assign
1502 ::core::result::Result::Ok(true)
1503 }
1504 }
1505 });
1506 quote! {
1507 #root::__impl_sqlite_load_related!(#struct_name, |__self, row, field_name, alias, __rest, __next_alias| {
1508 #( #arms )*
1509 });
1510 }
1511}
1512
1513fn fk_pk_access_impl_tokens(struct_name: &syn::Ident, fk_relations: &[FkRelation]) -> TokenStream2 {
1521 let root = rustango_root();
1522 let arms = fk_relations.iter().map(|rel| {
1523 let field_ident = &rel.field_ident;
1524 let fk_name = ident_name(field_ident);
1525 let fk_col = fk_name.as_str();
1526 if rel.pk_kind == DetectedKind::I64 {
1527 if rel.nullable {
1533 quote! {
1534 #fk_col => self.#field_ident
1535 .as_ref()
1536 .map(|fk| #root::sql::ForeignKey::pk(fk)),
1537 }
1538 } else {
1539 quote! {
1540 #fk_col => ::core::option::Option::Some(self.#field_ident.pk()),
1541 }
1542 }
1543 } else {
1544 quote! {
1552 #fk_col => ::core::option::Option::None,
1553 }
1554 }
1555 });
1556 let value_arms = fk_relations.iter().map(|rel| {
1562 let field_ident = &rel.field_ident;
1563 let fk_name = ident_name(field_ident);
1564 let fk_col = fk_name.as_str();
1565 if rel.nullable {
1566 quote! {
1567 #fk_col => self.#field_ident
1568 .as_ref()
1569 .map(|fk| ::core::convert::Into::<#root::core::SqlValue>::into(
1570 #root::sql::ForeignKey::pk(fk)
1571 )),
1572 }
1573 } else {
1574 quote! {
1575 #fk_col => ::core::option::Option::Some(
1576 ::core::convert::Into::<#root::core::SqlValue>::into(
1577 self.#field_ident.pk()
1578 )
1579 ),
1580 }
1581 }
1582 });
1583 quote! {
1584 impl #root::sql::FkPkAccess for #struct_name {
1585 #[allow(unused_variables)]
1586 fn __rustango_fk_pk(&self, field_name: &str) -> ::core::option::Option<i64> {
1587 match field_name {
1588 #( #arms )*
1589 _ => ::core::option::Option::None,
1590 }
1591 }
1592 #[allow(unused_variables)]
1593 fn __rustango_fk_pk_value(
1594 &self,
1595 field_name: &str,
1596 ) -> ::core::option::Option<#root::core::SqlValue> {
1597 match field_name {
1598 #( #value_arms )*
1599 _ => ::core::option::Option::None,
1600 }
1601 }
1602 }
1603 }
1604}
1605
1606const NO_REVERSE_ACCESSOR: &str = "+";
1608
1609fn reverse_helper_tokens(
1617 child_ident: &syn::Ident,
1618 fk_relations: &[FkRelation],
1619 default_related_name: Option<&str>,
1620) -> syn::Result<TokenStream2> {
1621 let root = rustango_root();
1622 let fk_relations: Vec<&FkRelation> = fk_relations
1624 .iter()
1625 .filter(|rel| rel.related_name.as_deref() != Some(NO_REVERSE_ACCESSOR))
1626 .collect();
1627 if fk_relations.is_empty() {
1628 return Ok(TokenStream2::new());
1629 }
1630 let default_pg_suffix = default_related_name
1644 .map(str::to_owned)
1645 .unwrap_or_else(|| format!("{}_set", to_snake_case(&ident_name(child_ident))));
1646 let name_of = |rel: &FkRelation| {
1647 rel.related_name
1648 .clone()
1649 .unwrap_or_else(|| default_pg_suffix.clone())
1650 };
1651 for (i, rel) in fk_relations.iter().enumerate() {
1653 let parent_ty = &rel.parent_type;
1654 let parent = quote!(#parent_ty).to_string();
1655 let name = name_of(rel);
1656 if let Some(first) = fk_relations[..i].iter().find(|r| {
1657 let other = &r.parent_type;
1658 quote!(#other).to_string() == parent && name_of(r) == name
1659 }) {
1660 return Err(syn::Error::new_spanned(
1661 &rel.field_ident,
1662 format!(
1663 "`{}` and `{}` both point at `{parent}` and would both add `{parent}::{name}`; \
1664 give one `#[rustango(related_name = \"...\")]`, or `related_name = \"+\"` for none",
1665 first.field_ident, rel.field_ident
1666 ),
1667 ));
1668 }
1669 }
1670 let impls = fk_relations.iter().map(|rel| {
1671 let pg_suffix = name_of(rel);
1672 let pool_suffix = format!("{}_pool", pg_suffix);
1673 let pg_method_ident = syn::Ident::new(&pg_suffix, child_ident.span());
1674 let pool_method_ident = syn::Ident::new(&pool_suffix, child_ident.span());
1675 let parent_ty = &rel.parent_type;
1676 let fk_name = ident_name(&rel.field_ident);
1677 let fk_col = fk_name.as_str();
1678 let doc = format!(
1679 "Fetch every `{child_ident}` whose `{fk_col}` foreign key points at this row. \
1680 Single SQL query — `SELECT … FROM <{child_ident} table> WHERE {fk_col} = $1` — \
1681 generated from the FK declaration on `{child_ident}::{fk_col}`. Composes with \
1682 further `{child_ident}::objects()` filters via direct queryset use."
1683 );
1684 quote! {
1685 impl #parent_ty {
1686 #[doc = #doc]
1687 pub async fn #pg_method_ident<'_c, _E: #root::sql::SqlExecutor<'_c>>(
1695 &self,
1696 _executor: _E,
1697 ) -> ::core::result::Result<
1698 ::std::vec::Vec<#child_ident>,
1699 #root::sql::ExecError,
1700 > {
1701 let _key = #root::sql::__reverse_fk_key::<Self, #child_ident>(self, #fk_col)?;
1702 #root::query::QuerySet::<#child_ident>::new()
1703 .filter_op(#fk_col, #root::core::Op::Eq, _key)
1704 .fetch_on(_executor)
1705 .await
1706 }
1707
1708 #[deprecated(since = "0.62.0", note = "drop the `_pool` suffix")]
1713 pub async fn #pool_method_ident(
1714 &self,
1715 pool: &#root::sql::Pool,
1716 ) -> ::core::result::Result<
1717 ::std::vec::Vec<#child_ident>,
1718 #root::sql::ExecError,
1719 > {
1720 self.#pg_method_ident(pool).await
1721 }
1722 }
1723 }
1724 });
1725 Ok(quote! { #( #impls )* })
1726}
1727
1728fn generic_fk_accessor_tokens(
1742 struct_name: &syn::Ident,
1743 generic_fks: &[GenericFkAttr],
1744 column_entries: &[ColumnEntry],
1745) -> TokenStream2 {
1746 let root = rustango_root();
1747 if generic_fks.is_empty() {
1748 return TokenStream2::new();
1749 }
1750 let methods = generic_fks.iter().filter_map(|gfk| {
1751 let ct_ident = column_entries
1757 .iter()
1758 .find(|c| c.column == gfk.ct_column)
1759 .map(|c| c.ident.clone())?;
1760 let pk_ident = column_entries
1761 .iter()
1762 .find(|c| c.column == gfk.pk_column)
1763 .map(|c| c.ident.clone())?;
1764
1765 let accessor_ident =
1766 syn::Ident::new(&format!("{}_pool", gfk.name), struct_name.span());
1767 let setter_ident =
1768 syn::Ident::new(&format!("set_{}_for", gfk.name), struct_name.span());
1769 let name_literal = gfk.name.as_str();
1770
1771 Some(quote! {
1772 #[doc = concat!(
1773 "Resolve the polymorphic `",
1774 #name_literal,
1775 "` relation. Reads `self.",
1776 stringify!(#ct_ident),
1777 "` + `self.",
1778 stringify!(#pk_ident),
1779 "`, looks up the matching `ContentType`, and fetches the target row as a JSON map.\n\n",
1780 "Returns `Ok(None)` when the ContentType is stale / unseeded or the target row was deleted. Emitted by `#[rustango(generic_fk(name = \"",
1781 #name_literal,
1782 "\", ...))]`."
1783 )]
1784 pub async fn #accessor_ident(
1785 &self,
1786 pool: &#root::sql::Pool,
1787 ) -> ::core::result::Result<
1788 ::core::option::Option<#root::__serde_json::Value>,
1789 #root::sql::ExecError,
1790 > {
1791 let gfk = #root::contenttypes::GenericForeignKey::new(
1792 self.#ct_ident as i64,
1793 self.#pk_ident as i64,
1794 );
1795 gfk.get_object(pool).await
1796 }
1797
1798 #[doc = concat!(
1799 "Set the polymorphic `",
1800 #name_literal,
1801 "` target. Looks up the `ContentType` for `T` via the cached registry, then assigns both `self.",
1802 stringify!(#ct_ident),
1803 "` and `self.",
1804 stringify!(#pk_ident),
1805 "`.\n\nFollow with `self.insert(pool)` or `self.update(pool)` to persist. Emitted by `#[rustango(generic_fk(name = \"",
1806 #name_literal,
1807 "\", ...))]`."
1808 )]
1809 pub async fn #setter_ident<T: #root::core::Model>(
1810 &mut self,
1811 pool: &#root::sql::Pool,
1812 target_pk: i64,
1813 ) -> ::core::result::Result<(), #root::sql::ExecError> {
1814 let gfk = #root::contenttypes::GenericForeignKey::for_target::<T>(
1815 pool,
1816 target_pk,
1817 ).await?;
1818 self.#ct_ident = gfk.content_type_id as _;
1819 self.#pk_ident = gfk.object_pk as _;
1820 ::core::result::Result::Ok(())
1821 }
1822 })
1823 });
1824 quote! {
1825 impl #struct_name {
1826 #( #methods )*
1827 }
1828 }
1829}
1830
1831fn m2m_accessor_tokens(struct_name: &syn::Ident, m2m_relations: &[M2MAttr]) -> TokenStream2 {
1832 let root = rustango_root();
1833 if m2m_relations.is_empty() {
1834 return TokenStream2::new();
1835 }
1836 let methods = m2m_relations.iter().map(|rel| {
1837 let method_name = format!("{}_m2m", rel.name);
1838 let method_ident = syn::Ident::new(&method_name, struct_name.span());
1839 let through = rel.through.as_str();
1840 let src_col = rel.src.as_str();
1841 let dst_col = rel.dst.as_str();
1842 quote! {
1843 pub fn #method_ident(&self) -> #root::sql::M2MManager {
1844 #root::sql::M2MManager {
1845 src_pk: self.__rustango_pk_value(),
1846 through: #through,
1847 src_col: #src_col,
1848 dst_col: #dst_col,
1849 }
1850 }
1851 }
1852 });
1853 quote! {
1854 impl #struct_name {
1855 #( #methods )*
1856 }
1857 }
1858}
1859
1860fn generic_m2m_accessor_tokens(
1863 struct_name: &syn::Ident,
1864 relations: &[GenericM2MAttr],
1865) -> TokenStream2 {
1866 let root = rustango_root();
1867 if relations.is_empty() {
1868 return TokenStream2::new();
1869 }
1870 let methods = relations.iter().map(|rel| {
1871 let method_ident = syn::Ident::new(&format!("{}_m2m", rel.name), struct_name.span());
1872 let through = rel.through.as_str();
1873 let pk_col = rel.pk_column.as_str();
1874 let ct_col = rel.ct_column.as_str();
1875 let related_col = rel.related_column.as_str();
1876 quote! {
1877 pub fn #method_ident(&self) -> #root::sql::GenericM2MManager {
1878 #root::sql::GenericM2MManager {
1879 src_pk: self.__rustango_pk_value(),
1880 src_schema: <Self as #root::core::Model>::SCHEMA,
1881 through: #through,
1882 pk_col: #pk_col,
1883 ct_col: #ct_col,
1884 dst_col: #related_col,
1885 }
1886 }
1887 }
1888 });
1889 let entries = relations.iter().map(|rel| {
1891 let through = rel.through.as_str();
1892 let pk_col = rel.pk_column.as_str();
1893 let ct_col = rel.ct_column.as_str();
1894 quote! {
1895 #root::core::inventory::submit! {
1896 #root::core::GenericM2MEntry::new(
1897 <#struct_name as #root::core::Model>::SCHEMA,
1898 #through,
1899 #pk_col,
1900 #ct_col,
1901 )
1902 }
1903 }
1904 });
1905 quote! {
1906 impl #struct_name {
1907 #( #methods )*
1908 }
1909 #( #entries )*
1910 }
1911}
1912
1913fn reverse_has_accessor_tokens(
1935 struct_name: &syn::Ident,
1936 reverse_has_relations: &[ReverseHasAttr],
1937 columns: &[ColumnEntry],
1938) -> TokenStream2 {
1939 let root = rustango_root();
1940 if reverse_has_relations.is_empty() {
1941 return TokenStream2::new();
1942 }
1943 let methods = reverse_has_relations.iter().map(|rel| {
1944 let exists_name = format!("{}_exists_expr", rel.name);
1945 let not_exists_name = format!("{}_not_exists_expr", rel.name);
1946 let count_name = format!("{}_count", rel.name);
1947 let fetch_name = format!("{}_fetch", rel.name);
1948 let first_name = format!("{}_first", rel.name);
1949 let pluck_name = format!("{}_pluck", rel.name);
1950 let accessor_name = rel.name.as_str();
1951 let exists_ident = syn::Ident::new(&exists_name, struct_name.span());
1952 let not_exists_ident = syn::Ident::new(¬_exists_name, struct_name.span());
1953 let count_ident = syn::Ident::new(&count_name, struct_name.span());
1954 let fetch_ident = syn::Ident::new(&fetch_name, struct_name.span());
1955 let first_ident = syn::Ident::new(&first_name, struct_name.span());
1956 let pluck_ident = syn::Ident::new(&pluck_name, struct_name.span());
1957 let accessor_ident = syn::Ident::new(accessor_name, struct_name.span());
1958 let child = &rel.child;
1959 let child_fk_column = rel.child_fk_column.as_str();
1960 let self_pk_column = rel.self_pk_column();
1961 let self_value = columns
1963 .iter()
1964 .find(|c| c.column == self_pk_column)
1965 .map_or_else(
1966 || quote! { self.__rustango_pk_value() },
1967 |c| {
1968 let ident = &c.ident;
1969 quote! {
1970 ::core::convert::Into::<#root::core::SqlValue>::into(
1971 ::core::clone::Clone::clone(&self.#ident)
1972 )
1973 }
1974 },
1975 );
1976 let exists_doc = format!(
1977 "Eloquent `whereHas` analog — yields `EXISTS (SELECT 1 \
1978 FROM <{child}> WHERE {child_fk_column} = <outer>.{self_pk_column})`. \
1979 Drop into `QuerySet::<{struct_name}>::where_raw(...)` to \
1980 filter to {struct_name}s with at least one matching child.",
1981 );
1982 let not_exists_doc = format!(
1983 "Eloquent `whereDoesntHave` analog — yields `NOT EXISTS \
1984 (SELECT 1 FROM <{child}> WHERE {child_fk_column} = \
1985 <outer>.{self_pk_column})`. Drop into \
1986 `QuerySet::<{struct_name}>::where_raw(...)` to filter to \
1987 {struct_name}s with **no** matching child.",
1988 );
1989 let count_doc = format!(
1990 "Eloquent `$model->{name}->count()` analog — returns \
1991 the number of `{child}` rows whose `{child_fk_column}` \
1992 matches this `{struct_name}` instance's `{self_pk_column}`. \
1993 Issued as `SELECT COUNT(*) FROM <{child}> WHERE \
1994 {child_fk_column} = <self.{self_pk_column}>`.",
1995 name = rel.name,
1996 );
1997 let accessor_doc = format!(
1998 "Eloquent `$model->{name}` accessor — returns a \
1999 `QuerySet<{child}>` filtered to rows whose \
2000 `{child_fk_column}` matches this `{struct_name}` \
2001 instance's `{self_pk_column}`. **Chainable**: compose `.filter()` \
2002 / `.order_by()` / `.limit()` etc. on top, then call \
2003 `.fetch(&pool)` (the QuerySet trait method) when \
2004 done. For the simple \"fetch all\" hot path with no \
2005 further composition, prefer the bare-name \
2006 `{name}_fetch(&pool)` companion.",
2007 name = rel.name,
2008 );
2009 let fetch_doc = format!(
2010 "Eloquent `$model->{name}->get()` — bare-name hot-path \
2011 over `{name}(&self).fetch(&pool)`. Use this when \
2012 you don't need further `.filter()` / `.order_by()` \
2013 composition; falls back to the chainable accessor when \
2014 you do. Avoids the `_pool` suffix on the most common \
2015 call-site shape.",
2016 name = rel.name,
2017 );
2018 quote! {
2019 #[doc = #accessor_doc]
2020 pub fn #accessor_ident(&self) -> #root::query::QuerySet<#child> {
2021 #root::query::QuerySet::<#child>::new()
2022 .filter(#child_fk_column, #self_value)
2023 }
2024
2025 #[doc = #fetch_doc]
2026 pub async fn #fetch_ident(
2027 &self,
2028 pool: &#root::sql::Pool,
2029 ) -> ::core::result::Result<
2030 ::std::vec::Vec<#child>,
2031 #root::sql::ExecError,
2032 > {
2033 use #root::sql::FetcherPool as _;
2034 self.#accessor_ident().fetch(pool).await
2035 }
2036
2037 pub async fn #first_ident(
2044 &self,
2045 pool: &#root::sql::Pool,
2046 ) -> ::core::result::Result<
2047 ::core::option::Option<#child>,
2048 #root::sql::ExecError,
2049 > {
2050 self.#accessor_ident().first(pool).await
2051 }
2052
2053 pub async fn #pluck_ident<U>(
2058 &self,
2059 col: &'static str,
2060 pool: &#root::sql::Pool,
2061 ) -> ::core::result::Result<
2062 ::std::vec::Vec<U>,
2063 #root::sql::ExecError,
2064 >
2065 where
2066 U: #root::sql::FlatScalar,
2067 {
2068 self.#accessor_ident()
2069 .values_list_flat(col)
2070 .fetch::<U>(pool)
2071 .await
2072 }
2073
2074 #[doc = #exists_doc]
2075 pub fn #exists_ident() -> #root::core::WhereExpr {
2076 use #root::core::{Expr, Model as _, Op, SelectQuery, WhereExpr};
2077 let child_schema =
2078 <#child as #root::core::Model>::SCHEMA;
2079 let inner = SelectQuery::new(child_schema).where_clause(WhereExpr::ExprCompare {
2080 lhs: Expr::Column(#child_fk_column),
2081 op: Op::Eq,
2082 rhs: Expr::OuterRef(#self_pk_column),
2083 });
2084 WhereExpr::Exists(::std::boxed::Box::new(inner))
2085 }
2086
2087 #[doc = #not_exists_doc]
2088 pub fn #not_exists_ident() -> #root::core::WhereExpr {
2089 use #root::core::{Expr, Model as _, Op, SelectQuery, WhereExpr};
2090 let child_schema =
2091 <#child as #root::core::Model>::SCHEMA;
2092 let inner = SelectQuery::new(child_schema).where_clause(WhereExpr::ExprCompare {
2093 lhs: Expr::Column(#child_fk_column),
2094 op: Op::Eq,
2095 rhs: Expr::OuterRef(#self_pk_column),
2096 });
2097 WhereExpr::NotExists(::std::boxed::Box::new(inner))
2098 }
2099
2100 #[doc = #count_doc]
2101 pub async fn #count_ident(
2102 &self,
2103 pool: &#root::sql::Pool,
2104 ) -> ::core::result::Result<
2105 i64,
2106 #root::sql::ExecError,
2107 > {
2108 use #root::sql::CounterPool as _;
2109 self.#accessor_ident().count(pool).await
2110 }
2111 }
2112 });
2113 quote! {
2114 impl #struct_name {
2115 #( #methods )*
2116 }
2117 }
2118}
2119
2120fn through_accessor_tokens(
2138 struct_name: &syn::Ident,
2139 through_relations: &[ThroughAttr],
2140) -> TokenStream2 {
2141 let root = rustango_root();
2142 if through_relations.is_empty() {
2143 return TokenStream2::new();
2144 }
2145 let methods = through_relations.iter().map(|rel| {
2146 let method_name = format!("{}_through", rel.name);
2147 let count_name = format!("{}_through_count", rel.name);
2148 let fetch_name = format!("{}_through_fetch", rel.name);
2149 let first_name = format!("{}_through_first", rel.name);
2150 let pluck_name = format!("{}_through_pluck", rel.name);
2151 let method_ident = syn::Ident::new(&method_name, struct_name.span());
2152 let count_ident = syn::Ident::new(&count_name, struct_name.span());
2153 let fetch_ident = syn::Ident::new(&fetch_name, struct_name.span());
2154 let first_ident = syn::Ident::new(&first_name, struct_name.span());
2155 let pluck_ident = syn::Ident::new(&pluck_name, struct_name.span());
2156 let far = &rel.far;
2157 let intermediate = &rel.intermediate;
2158 let far_fk_column = rel.far_fk_column.as_str();
2159 let intermediate_fk_column = rel.intermediate_fk_column.as_str();
2160 let intermediate_pk_column = rel.intermediate_pk_column.as_str();
2161 let doc = format!(
2162 "Eloquent `hasManyThrough` accessor — returns a \
2163 `QuerySet<{far}>` whose rows reach this `{struct_name}` \
2164 instance through the intermediate `{intermediate}` table. \
2165 Generated SQL shape: \
2166 `… WHERE {far_fk_column} IN (SELECT \
2167 {intermediate_pk_column} FROM <{intermediate}> WHERE \
2168 {intermediate_fk_column} = self.pk)`. Chainable like any \
2169 other QuerySet — compose `.filter()` / `.order_by()` / \
2170 `.limit()` etc. on top.",
2171 );
2172 let count_doc = format!(
2173 "Eloquent `$model->{name}->count()` analog for the \
2174 through-relation — returns the number of `{far}` rows \
2175 reachable through `{intermediate}`. Equivalent to \
2176 `self.{name}_through().count(pool)` but spelled \
2177 as a bare instance method for parity with the \
2178 `reverse_has` `<name>_count` shape.",
2179 name = rel.name,
2180 );
2181 quote! {
2182 #[doc = #doc]
2183 pub fn #method_ident(&self) -> #root::query::QuerySet<#far> {
2184 use #root::core::{Filter, Model as _, Op, SelectQuery, WhereExpr};
2185 let intermediate_schema =
2186 <#intermediate as #root::core::Model>::SCHEMA;
2187 let sub = SelectQuery::new(intermediate_schema)
2188 .where_clause(WhereExpr::Predicate(Filter::new(
2189 #intermediate_fk_column,
2190 Op::Eq,
2191 self.__rustango_pk_value(),
2192 )))
2193 .projection(::std::vec![#intermediate_pk_column]);
2194 #root::query::QuerySet::<#far>::new().where_raw(
2195 WhereExpr::InSubquery {
2196 column: #far_fk_column,
2197 negated: false,
2198 subquery: ::std::boxed::Box::new(sub),
2199 },
2200 )
2201 }
2202
2203 #[doc = #count_doc]
2204 pub async fn #count_ident(
2205 &self,
2206 pool: &#root::sql::Pool,
2207 ) -> ::core::result::Result<
2208 i64,
2209 #root::sql::ExecError,
2210 > {
2211 use #root::sql::CounterPool as _;
2212 self.#method_ident().count(pool).await
2213 }
2214
2215 pub async fn #fetch_ident(
2223 &self,
2224 pool: &#root::sql::Pool,
2225 ) -> ::core::result::Result<
2226 ::std::vec::Vec<#far>,
2227 #root::sql::ExecError,
2228 > {
2229 use #root::sql::FetcherPool as _;
2230 self.#method_ident().fetch(pool).await
2231 }
2232
2233 pub async fn #first_ident(
2240 &self,
2241 pool: &#root::sql::Pool,
2242 ) -> ::core::result::Result<
2243 ::core::option::Option<#far>,
2244 #root::sql::ExecError,
2245 > {
2246 self.#method_ident().first(pool).await
2247 }
2248
2249 pub async fn #pluck_ident<U>(
2253 &self,
2254 col: &'static str,
2255 pool: &#root::sql::Pool,
2256 ) -> ::core::result::Result<
2257 ::std::vec::Vec<U>,
2258 #root::sql::ExecError,
2259 >
2260 where
2261 U: #root::sql::FlatScalar,
2262 {
2263 self.#method_ident()
2264 .values_list_flat(col)
2265 .fetch::<U>(pool)
2266 .await
2267 }
2268 }
2269 });
2270 quote! {
2271 impl #struct_name {
2272 #( #methods )*
2273 }
2274 }
2275}
2276
2277struct ColumnEntry {
2278 ident: syn::Ident,
2281 value_ty: Type,
2283 name: String,
2285 column: String,
2287 field_type_tokens: TokenStream2,
2289 uuid_column: bool,
2291}
2292
2293struct CollectedFields {
2294 field_schemas: Vec<TokenStream2>,
2295 from_row_inits: Vec<TokenStream2>,
2296 from_aliased_row_inits: Vec<TokenStream2>,
2300 from_row_inits_my: Vec<TokenStream2>,
2302 from_aliased_row_inits_my: Vec<TokenStream2>,
2303 insert_columns: Vec<TokenStream2>,
2306 insert_values: Vec<TokenStream2>,
2309 insert_pushes: Vec<TokenStream2>,
2314 returning_cols: Vec<TokenStream2>,
2317 auto_assigns: Vec<TokenStream2>,
2320 auto_field_idents: Vec<(syn::Ident, String, bool)>,
2324 generated_field_idents: Vec<(syn::Ident, String)>,
2329 first_db_auto: Option<(syn::Ident, Type)>,
2332 bulk_pushes_no_auto: Vec<TokenStream2>,
2336 bulk_pushes_all: Vec<TokenStream2>,
2340 bulk_columns_no_auto: Vec<TokenStream2>,
2343 bulk_columns_all: Vec<TokenStream2>,
2346 bulk_fill_in_place: Vec<TokenStream2>,
2349 rust_filled_idents: Vec<syn::Ident>,
2351 auto_now_idents: Vec<syn::Ident>,
2353 uuid_v7_fields: Vec<(syn::Ident, String)>,
2356 bulk_auto_uniformity: Vec<TokenStream2>,
2360 first_auto_ident: Option<syn::Ident>,
2363 has_auto: bool,
2365 pk_is_auto: bool,
2369 update_assignments: Vec<TokenStream2>,
2372 upsert_update_columns: Vec<TokenStream2>,
2375 primary_key: Option<(syn::Ident, String)>,
2376 column_entries: Vec<ColumnEntry>,
2377 field_names: Vec<String>,
2380 fk_relations: Vec<FkRelation>,
2385 soft_delete_column: Option<String>,
2390 soft_delete_field_ident: Option<syn::Ident>,
2394}
2395
2396#[derive(Clone)]
2397struct FkRelation {
2398 parent_type: Type,
2401 field_ident: syn::Ident,
2404 pk_kind: DetectedKind,
2409 nullable: bool,
2414 related_name: Option<String>,
2419}
2420
2421fn collect_fields(named: &syn::FieldsNamed, table: &str) -> syn::Result<CollectedFields> {
2422 let root = rustango_root();
2423 let cap = named.named.len();
2424 let mut out = CollectedFields {
2425 field_schemas: Vec::with_capacity(cap),
2426 from_row_inits: Vec::with_capacity(cap),
2427 from_aliased_row_inits: Vec::with_capacity(cap),
2428 from_row_inits_my: Vec::with_capacity(cap),
2429 from_aliased_row_inits_my: Vec::with_capacity(cap),
2430 insert_columns: Vec::with_capacity(cap),
2431 insert_values: Vec::with_capacity(cap),
2432 insert_pushes: Vec::with_capacity(cap),
2433 returning_cols: Vec::new(),
2434 auto_assigns: Vec::new(),
2435 auto_field_idents: Vec::new(),
2436 generated_field_idents: Vec::new(),
2437 first_db_auto: None,
2438 bulk_pushes_no_auto: Vec::with_capacity(cap),
2439 bulk_pushes_all: Vec::with_capacity(cap),
2440 bulk_columns_no_auto: Vec::with_capacity(cap),
2441 bulk_columns_all: Vec::with_capacity(cap),
2442 bulk_fill_in_place: Vec::new(),
2443 rust_filled_idents: Vec::new(),
2444 auto_now_idents: Vec::new(),
2445 uuid_v7_fields: Vec::new(),
2446 bulk_auto_uniformity: Vec::new(),
2447 first_auto_ident: None,
2448 has_auto: false,
2449 pk_is_auto: false,
2450 update_assignments: Vec::with_capacity(cap),
2451 upsert_update_columns: Vec::with_capacity(cap),
2452 primary_key: None,
2453 column_entries: Vec::with_capacity(cap),
2454 field_names: Vec::with_capacity(cap),
2455 fk_relations: Vec::new(),
2456 soft_delete_column: None,
2457 soft_delete_field_ident: None,
2458 };
2459
2460 for field in &named.named {
2461 let info = process_field(field, table)?;
2462 out.field_names.push(ident_name(info.ident));
2463 out.field_schemas.push(info.schema);
2464 out.from_row_inits.push(info.from_row_init);
2465 out.from_aliased_row_inits.push(info.from_aliased_row_init);
2466 out.from_row_inits_my.push(info.from_row_init_my);
2467 out.from_aliased_row_inits_my
2468 .push(info.from_aliased_row_init_my);
2469 if let Some(parent_ty) = info.fk_inner.clone() {
2470 out.fk_relations.push(FkRelation {
2471 parent_type: parent_ty,
2472 field_ident: info.ident.clone(),
2473 pk_kind: info.fk_pk_kind,
2474 nullable: info.nullable,
2475 related_name: info.related_name.clone(),
2476 });
2477 }
2478 if info.soft_delete {
2479 if out.soft_delete_column.is_some() {
2480 return Err(syn::Error::new_spanned(
2481 field,
2482 "only one field may be marked `#[rustango(soft_delete)]`",
2483 ));
2484 }
2485 out.soft_delete_column = Some(info.column.clone());
2486 out.soft_delete_field_ident = Some(info.ident.clone());
2487 }
2488 let column = info.column.as_str();
2489 let ident = info.ident;
2490 if info.generated_as.is_some() {
2499 out.column_entries.push(ColumnEntry {
2500 ident: ident.clone(),
2501 value_ty: info.value_ty.clone(),
2502 name: ident_name(ident),
2503 column: info.column.clone(),
2504 field_type_tokens: info.field_type_tokens,
2505 uuid_column: info.uuid_column,
2506 });
2507 out.returning_cols.push(quote!(#column));
2514 out.generated_field_idents
2515 .push((ident.clone(), info.column.clone()));
2516 continue;
2517 }
2518 out.insert_columns.push(quote!(#column));
2519 out.insert_values.push(quote! {
2520 ::core::convert::Into::<#root::core::SqlValue>::into(
2521 ::core::clone::Clone::clone(&self.#ident)
2522 )
2523 });
2524 if info.auto {
2525 out.has_auto = true;
2526 if out.first_auto_ident.is_none() {
2527 out.first_auto_ident = Some(ident.clone());
2528 }
2529 if !info.default_uuid_v7 && !info.auto_now_add && !info.auto_now {
2542 if out.first_db_auto.is_none() {
2543 out.first_db_auto = auto_inner_type(info.value_ty)
2544 .cloned()
2545 .map(|ty| (ident.clone(), ty));
2546 }
2547 out.returning_cols.push(quote!(#column));
2548 out.auto_field_idents.push((
2549 ident.clone(),
2550 info.column.clone(),
2551 info.fk_pk_kind == DetectedKind::Uuid,
2552 ));
2553 out.auto_assigns.push(quote! {
2554 self.#ident = #root::sql::try_get_returning(_returning_row, #column)?;
2555 });
2556 }
2557 if info.default_uuid_v7 {
2558 out.insert_pushes.push(quote! {
2564 if matches!(&self.#ident, #root::sql::Auto::Unset) {
2565 self.#ident = #root::sql::Auto::Set(
2566 #root::__uuid::Uuid::now_v7(),
2567 );
2568 }
2569 if let #root::sql::Auto::Set(_v) = &self.#ident {
2570 _columns.push(#column);
2571 _values.push(::core::convert::Into::<#root::core::SqlValue>::into(
2572 ::core::clone::Clone::clone(_v)
2573 ));
2574 }
2575 });
2576 } else if info.auto_now_add || info.auto_now {
2577 out.insert_pushes.push(quote! {
2599 if matches!(&self.#ident, #root::sql::Auto::Unset) {
2600 self.#ident = #root::sql::Auto::Set(
2601 #root::__chrono::Utc::now(),
2602 );
2603 }
2604 if let #root::sql::Auto::Set(_v) = &self.#ident {
2605 _columns.push(#column);
2606 _values.push(::core::convert::Into::<#root::core::SqlValue>::into(
2607 ::core::clone::Clone::clone(_v)
2608 ));
2609 }
2610 });
2611 } else {
2612 out.insert_pushes.push(quote! {
2613 if let #root::sql::Auto::Set(_v) = &self.#ident {
2614 _columns.push(#column);
2615 _values.push(::core::convert::Into::<#root::core::SqlValue>::into(
2616 ::core::clone::Clone::clone(_v)
2617 ));
2618 }
2619 });
2620 }
2621 if info.default_uuid_v7 || info.auto_now_add || info.auto_now {
2634 out.rust_filled_idents.push(ident.clone());
2635 }
2636 if info.auto_now {
2637 out.auto_now_idents.push(ident.clone());
2638 }
2639 if info.default_uuid_v7 {
2640 out.uuid_v7_fields
2641 .push((ident.clone(), info.column.clone()));
2642 }
2643 let rust_fill = if info.default_uuid_v7 {
2644 Some(quote!(#root::__uuid::Uuid::now_v7()))
2645 } else if info.auto_now_add || info.auto_now {
2646 Some(quote!(#root::__chrono::Utc::now()))
2647 } else {
2648 None
2649 };
2650 out.bulk_columns_all.push(quote!(#column));
2653 if let Some(fill) = &rust_fill {
2654 let push = quote! {
2655 _row_vals.push(::core::convert::Into::<#root::core::SqlValue>::into(
2656 match &_row.#ident {
2657 #root::sql::Auto::Set(_v) => ::core::clone::Clone::clone(_v),
2658 #root::sql::Auto::Unset => #fill,
2659 }
2660 ));
2661 };
2662 out.bulk_columns_no_auto.push(quote!(#column));
2663 out.bulk_pushes_no_auto.push(push.clone());
2664 out.bulk_pushes_all.push(push);
2665 } else {
2666 out.bulk_pushes_all.push(quote! {
2667 _row_vals.push(::core::convert::Into::<#root::core::SqlValue>::into(
2668 ::core::clone::Clone::clone(&_row.#ident)
2669 ));
2670 });
2671 }
2672 if info.default_uuid_v7 {
2673 out.bulk_fill_in_place.push(quote! {
2674 if matches!(&_row.#ident, #root::sql::Auto::Unset) {
2675 _row.#ident = #root::sql::Auto::Set(#root::__uuid::Uuid::now_v7());
2676 }
2677 });
2678 }
2679 let ident_clone = ident.clone();
2684 if rust_fill.is_none() {
2685 out.bulk_auto_uniformity.push(quote! {
2686 for _r in rows.iter().skip(1) {
2687 if matches!(_r.#ident_clone, #root::sql::Auto::Unset) != _first_unset {
2688 return ::core::result::Result::Err(
2689 #root::sql::ExecError::Sql(
2690 #root::sql::SqlError::BulkAutoMixed
2691 )
2692 );
2693 }
2694 }
2695 });
2696 }
2697 } else {
2698 out.insert_pushes.push(quote! {
2699 _columns.push(#column);
2700 _values.push(::core::convert::Into::<#root::core::SqlValue>::into(
2701 ::core::clone::Clone::clone(&self.#ident)
2702 ));
2703 });
2704 out.bulk_columns_no_auto.push(quote!(#column));
2706 out.bulk_columns_all.push(quote!(#column));
2707 let push_expr = quote! {
2708 _row_vals.push(::core::convert::Into::<#root::core::SqlValue>::into(
2709 ::core::clone::Clone::clone(&_row.#ident)
2710 ));
2711 };
2712 out.bulk_pushes_no_auto.push(push_expr.clone());
2713 out.bulk_pushes_all.push(push_expr);
2714 }
2715 if info.primary_key {
2716 if out.primary_key.is_some() {
2717 return Err(syn::Error::new_spanned(
2718 field,
2719 "only one field may be marked `#[rustango(primary_key)]`",
2720 ));
2721 }
2722 out.primary_key = Some((ident.clone(), info.column.clone()));
2723 if info.auto {
2724 out.pk_is_auto = true;
2725 }
2726 } else if info.auto_now_add {
2727 } else if info.auto_now {
2729 out.update_assignments.push(quote! {
2732 #root::core::Assignment::new(
2733 #column,
2734 {
2735 let _now = #root::__chrono::Utc::now();
2736 self.#ident = #root::sql::Auto::Set(_now);
2737 ::core::convert::Into::<#root::core::SqlValue>::into(_now)
2738 },
2739 )
2740 });
2741 out.upsert_update_columns.push(quote!(#column));
2742 } else {
2743 out.update_assignments.push(quote! {
2744 #root::core::Assignment::new(
2745 #column,
2746 ::core::convert::Into::<#root::core::SqlValue>::into(
2747 ::core::clone::Clone::clone(&self.#ident)
2748 ),
2749 )
2750 });
2751 out.upsert_update_columns.push(quote!(#column));
2752 }
2753 out.column_entries.push(ColumnEntry {
2754 ident: ident.clone(),
2755 value_ty: info.value_ty.clone(),
2756 name: ident_name(ident),
2757 column: info.column.clone(),
2758 field_type_tokens: info.field_type_tokens,
2759 uuid_column: info.uuid_column,
2760 });
2761 }
2762 Ok(out)
2763}
2764
2765fn capped_index_name(name: String) -> String {
2768 const MAX: usize = 63;
2769 if name.len() <= MAX {
2770 return name;
2771 }
2772 let hash = name.bytes().fold(0x811c_9dc5_u32, |h, b| {
2774 (h ^ u32::from(b)).wrapping_mul(0x0100_0193)
2775 });
2776 let mut cut = MAX - 9;
2777 while !name.is_char_boundary(cut) {
2778 cut -= 1;
2779 }
2780 format!("{}_{hash:08x}", &name[..cut])
2781}
2782
2783fn model_impl_tokens(
2784 struct_name: &syn::Ident,
2785 model_name: &str,
2786 table: &str,
2787 display: Option<&str>,
2788 app_label: Option<&str>,
2789 admin: Option<&AdminAttrs>,
2790 default_order: &[(String, bool, proc_macro2::Span)],
2791 field_schemas: &[TokenStream2],
2792 soft_delete_column: Option<&str>,
2793 permissions: bool,
2794 audit_track: Option<&[String]>,
2795 m2m_relations: &[M2MAttr],
2796 indexes: &[IndexAttr],
2797 checks: &[CheckAttr],
2798 excludes: &[ExcludeAttr],
2799 composite_fks: &[CompositeFkAttr],
2800 generic_fks: &[GenericFkAttr],
2801 scope: Option<&str>,
2802 access: Option<&str>,
2803 is_view: bool,
2804 verbose_name: Option<&str>,
2805 verbose_name_plural: Option<&str>,
2806 managed: bool,
2807 base_manager_name: Option<&str>,
2808 order_with_respect_to: Option<&str>,
2809 proxy: bool,
2810 required_db_features: &[String],
2811 required_db_vendor: Option<&str>,
2812 default_related_name: Option<&str>,
2813 db_table_comment: Option<&str>,
2814 get_latest_by: Option<(&str, bool)>,
2815 extra_permissions: &[(String, String)],
2816 default_permissions: &[String],
2817 global_scopes: &[GlobalScopeAttr],
2818 reverse_has_relations: &[ReverseHasAttr],
2819 generic_has_relations: &[GenericHasAttr],
2820) -> TokenStream2 {
2821 let root = rustango_root();
2822 let display_tokens = if let Some(name) = display {
2823 quote!(::core::option::Option::Some(#name))
2824 } else {
2825 quote!(::core::option::Option::None)
2826 };
2827 let app_label_tokens = if let Some(name) = app_label {
2828 quote!(::core::option::Option::Some(#name))
2829 } else {
2830 quote!(::core::option::Option::None)
2831 };
2832 let soft_delete_tokens = if let Some(col) = soft_delete_column {
2833 quote!(::core::option::Option::Some(#col))
2834 } else {
2835 quote!(::core::option::Option::None)
2836 };
2837 let audit_track_tokens = match audit_track {
2838 None => quote!(::core::option::Option::None),
2839 Some(names) => {
2840 let lits = names.iter().map(|n| n.as_str());
2841 quote!(::core::option::Option::Some(&[ #(#lits),* ]))
2842 }
2843 };
2844 let admin_tokens = admin_config_tokens(admin);
2845 let scope_tokens = match scope.map(|s| s.to_ascii_lowercase()).as_deref() {
2849 Some("registry") => quote!(#root::core::ModelScope::Registry),
2850 _ => quote!(#root::core::ModelScope::Tenant),
2851 };
2852 let access_tokens = match access {
2854 Some("admin_only") => {
2855 quote!(::core::option::Option::Some(#root::core::TableAccess::AdminOnly))
2856 }
2857 Some("never") => quote!(::core::option::Option::Some(#root::core::TableAccess::Never)),
2858 Some(_) => quote!(::core::option::Option::Some(#root::core::TableAccess::Open)),
2859 None => quote!(::core::option::Option::None),
2860 };
2861 let verbose_name_tokens = optional_str(verbose_name);
2862 let verbose_name_plural_tokens = optional_str(verbose_name_plural);
2863 let base_manager_name_tokens = optional_str(base_manager_name);
2864 let order_with_respect_to_tokens = optional_str(order_with_respect_to);
2865 let required_db_features_lits: Vec<&str> =
2866 required_db_features.iter().map(String::as_str).collect();
2867 let required_db_vendor_tokens = optional_str(required_db_vendor);
2868 let default_related_name_tokens = optional_str(default_related_name);
2869 let db_table_comment_tokens = optional_str(db_table_comment);
2870 let get_latest_by_tokens = match get_latest_by {
2871 Some((col, desc)) => {
2872 quote!(::core::option::Option::Some((#col, #desc)))
2873 }
2874 None => quote!(::core::option::Option::None),
2875 };
2876 let extra_permission_tokens: Vec<_> = extra_permissions
2877 .iter()
2878 .map(|(c, l)| quote!((#c, #l)))
2879 .collect();
2880 let default_permission_tokens: Vec<_> = default_permissions
2881 .iter()
2882 .map(|action| quote!(#action))
2883 .collect();
2884 let indexes_tokens = indexes.iter().map(|idx| {
2885 let derived_name = idx
2890 .name
2891 .clone()
2892 .unwrap_or_else(|| capped_index_name(format!("{table}_{}_idx", idx.columns.join("_"))));
2893 let name = derived_name.as_str();
2894 let cols: Vec<&str> = idx.columns.iter().map(String::as_str).collect();
2895 let unique = idx.unique;
2896 let method_variant = match idx.method.as_str() {
2900 "gin" => quote!(#root::core::IndexMethod::Gin),
2901 "gist" => quote!(#root::core::IndexMethod::Gist),
2902 "brin" => quote!(#root::core::IndexMethod::Brin),
2903 "spgist" => quote!(#root::core::IndexMethod::SpGist),
2904 "hash" => quote!(#root::core::IndexMethod::Hash),
2905 "bloom" => quote!(#root::core::IndexMethod::Bloom),
2906 _ => quote!(#root::core::IndexMethod::BTree),
2907 };
2908 let where_clause = match &idx.where_clause {
2909 Some(s) => quote!(::core::option::Option::Some(#s)),
2910 None => quote!(::core::option::Option::None),
2911 };
2912 let include_lits: Vec<&str> = idx.include.iter().map(String::as_str).collect();
2913 quote! {{
2914 let mut i = #root::core::IndexSchema::new(#name, &[ #(#cols),* ]);
2915 i.unique = #unique;
2916 i.method = #method_variant;
2917 i.where_clause = #where_clause;
2918 i.include = &[ #(#include_lits),* ];
2919 i
2920 }}
2921 });
2922 let checks_tokens = checks.iter().map(|c| {
2923 let name = c.name.as_str();
2924 let expr = c.expr.as_str();
2925 quote!(#root::core::CheckConstraint::new(#name, #expr))
2926 });
2927 let excludes_tokens = excludes.iter().map(|e| {
2928 let name = e.name.as_str();
2929 let using = e.using.as_str();
2930 let element_tokens = e.elements.iter().map(|(col, op)| {
2931 let col_s = col.as_str();
2932 let op_s = op.as_str();
2933 quote!((#col_s, #op_s))
2934 });
2935 let where_tokens = match e.where_clause.as_deref() {
2936 Some(w) => quote!(::core::option::Option::Some(#w)),
2937 None => quote!(::core::option::Option::None),
2938 };
2939 quote! {{
2940 let mut e = #root::core::ExclusionConstraint::new(
2941 #name,
2942 #using,
2943 &[ #(#element_tokens),* ],
2944 );
2945 e.where_clause = #where_tokens;
2946 e
2947 }}
2948 });
2949 let composite_fk_tokens = composite_fks.iter().map(|rel| {
2950 let name = rel.name.as_str();
2951 let to = rel.to.as_str();
2952 let from_cols: Vec<&str> = rel.from.iter().map(String::as_str).collect();
2953 let on_cols: Vec<&str> = rel.on.iter().map(String::as_str).collect();
2954 quote! {
2955 #root::core::CompositeFkRelation::new(
2956 #name,
2957 #to,
2958 &[ #(#from_cols),* ],
2959 &[ #(#on_cols),* ],
2960 )
2961 }
2962 });
2963 let generic_fk_tokens = generic_fks.iter().map(|rel| {
2964 let name = rel.name.as_str();
2965 let ct_col = rel.ct_column.as_str();
2966 let pk_col = rel.pk_column.as_str();
2967 quote!(#root::core::GenericRelation::new(#name, #ct_col, #pk_col))
2968 });
2969 let default_order_tokens = default_order.iter().map(|(col, desc, _)| {
2972 let col_lit = col.as_str();
2973 quote! { (#col_lit, #desc) }
2974 });
2975
2976 let global_scope_tokens = global_scopes.iter().map(|s| {
2981 let name = s.name.as_str();
2982 let mut apply = s.apply.clone();
2984 if let Some(first) = apply.segments.first_mut() {
2985 if first.ident == "Self" {
2986 first.ident = struct_name.clone();
2987 }
2988 }
2989 quote!(#root::core::GlobalScope::new(#name, #apply))
2990 });
2991
2992 let m2m_tokens = m2m_relations.iter().map(|rel| {
2993 let name = rel.name.as_str();
2994 let to = rel.to.as_str();
2995 let through = rel.through.as_str();
2996 let src = rel.src.as_str();
2997 let dst = rel.dst.as_str();
2998 let auto_create = rel.auto_create;
2999 quote! {{
3000 let mut m = #root::core::M2MRelation::new(#name, #to, #through, #src, #dst);
3001 m.auto_create = #auto_create;
3002 m
3003 }}
3004 });
3005 let reverse_relations_override = if reverse_has_relations.is_empty() {
3011 quote!()
3012 } else {
3013 let entries = reverse_has_relations.iter().map(|rel| {
3014 let name = rel.name.as_str();
3015 let child = &rel.child;
3016 let child_fk_column = rel.child_fk_column.as_str();
3017 let self_pk_column = rel.self_pk_column();
3018 quote! {
3019 #root::core::ReverseRelation::new(
3020 #name,
3021 <#child as #root::core::Model>::SCHEMA,
3022 #child_fk_column,
3023 #self_pk_column,
3024 )
3025 }
3026 });
3027 quote! {
3028 fn reverse_relations() -> &'static [#root::core::ReverseRelation] {
3029 const RELS: &[#root::core::ReverseRelation] = &[ #(#entries),* ];
3030 RELS
3031 }
3032 }
3033 };
3034 let generic_reverse_relations_override = if generic_has_relations.is_empty() {
3037 quote!()
3038 } else {
3039 let entries = generic_has_relations.iter().map(|rel| {
3040 let name = rel.name.as_str();
3041 let child = &rel.child;
3042 let ct_column = rel.ct_column.as_str();
3043 let pk_column = rel.pk_column.as_str();
3044 let self_pk_column = rel.self_pk_column();
3045 quote! {
3046 #root::core::GenericReverseRelation::new(
3047 #name,
3048 <#child as #root::core::Model>::SCHEMA,
3049 #ct_column,
3050 #pk_column,
3051 #self_pk_column,
3052 )
3053 }
3054 });
3055 quote! {
3056 fn generic_reverse_relations() -> &'static [#root::core::GenericReverseRelation] {
3057 const RELS: &[#root::core::GenericReverseRelation] = &[ #(#entries),* ];
3058 RELS
3059 }
3060 }
3061 };
3062 let fields_slice = const_slice(
3063 "e!(#root::core::FieldSchema),
3064 field_schemas.iter().cloned(),
3065 );
3066 let m2m_slice = const_slice("e!(#root::core::M2MRelation), m2m_tokens);
3067 let indexes_slice = const_slice("e!(#root::core::IndexSchema), indexes_tokens);
3068 let checks_slice = const_slice("e!(#root::core::CheckConstraint), checks_tokens);
3069 let excludes_slice = const_slice("e!(#root::core::ExclusionConstraint), excludes_tokens);
3070 let composite_fk_slice = const_slice(
3071 "e!(#root::core::CompositeFkRelation),
3072 composite_fk_tokens,
3073 );
3074 let generic_fk_slice = const_slice("e!(#root::core::GenericRelation), generic_fk_tokens);
3075 let audited_update_override = if audit_track.is_some() {
3076 quote! {
3077 fn __rustango_audited_update() -> ::core::option::Option<#root::audit::AuditedUpdate> {
3078 ::core::option::Option::Some(Self::__rustango_update_audited)
3079 }
3080 fn __rustango_audited_delete() -> ::core::option::Option<#root::audit::AuditedDelete> {
3081 ::core::option::Option::Some(Self::__rustango_delete_audited)
3082 }
3083 fn __rustango_audited_create() -> ::core::option::Option<#root::audit::AuditedCreate> {
3084 ::core::option::Option::Some(Self::__rustango_create_audited)
3085 }
3086 fn __rustango_audited_update_record(
3087 ) -> ::core::option::Option<#root::audit::AuditedUpdateRecord> {
3088 ::core::option::Option::Some(Self::__rustango_update_record_audited)
3089 }
3090 fn __rustango_audited_cascade() -> ::core::option::Option<#root::audit::AuditedCascade> {
3091 ::core::option::Option::Some(Self::__rustango_cascade_audited)
3092 }
3093 }
3094 } else {
3095 quote!()
3096 };
3097 let global_scope_slice = const_slice("e!(#root::core::GlobalScope), global_scope_tokens);
3098 quote! {
3099 impl #root::core::Model for #struct_name {
3100 const SCHEMA: &'static #root::core::ModelSchema = &{
3101 #root::core::ModelSchema::from_parts(#root::core::ModelSchemaParts {
3102 name: #model_name,
3103 table: #table,
3104 fields: #fields_slice,
3105 display: #display_tokens,
3106 app_label: #app_label_tokens,
3107 admin: #admin_tokens,
3108 soft_delete_column: #soft_delete_tokens,
3109 permissions: #permissions,
3110 audit_track: #audit_track_tokens,
3111 m2m: #m2m_slice,
3112 indexes: #indexes_slice,
3113 check_constraints: #checks_slice,
3114 exclusion_constraints: #excludes_slice,
3115 composite_relations: #composite_fk_slice,
3116 generic_relations: #generic_fk_slice,
3117 scope: #scope_tokens,
3118 default_order: &[ #(#default_order_tokens),* ],
3119 is_view: #is_view,
3120 verbose_name: #verbose_name_tokens,
3121 verbose_name_plural: #verbose_name_plural_tokens,
3122 managed: #managed,
3123 base_manager_name: #base_manager_name_tokens,
3124 order_with_respect_to: #order_with_respect_to_tokens,
3125 proxy: #proxy,
3126 required_db_features: &[ #(#required_db_features_lits),* ],
3127 required_db_vendor: #required_db_vendor_tokens,
3128 default_related_name: #default_related_name_tokens,
3129 db_table_comment: #db_table_comment_tokens,
3130 get_latest_by: #get_latest_by_tokens,
3131 extra_permissions: &[ #(#extra_permission_tokens),* ],
3132 default_permissions: &[ #(#default_permission_tokens),* ],
3133 global_scopes: #global_scope_slice,
3134 access: #access_tokens,
3135 })
3136 };
3137
3138 #reverse_relations_override
3139 #generic_reverse_relations_override
3140 #audited_update_override
3141 }
3142 }
3143}
3144
3145fn admin_config_tokens(admin: Option<&AdminAttrs>) -> TokenStream2 {
3149 let root = rustango_root();
3150 let Some(admin) = admin else {
3151 return quote!(::core::option::Option::None);
3152 };
3153
3154 let list_display = admin
3155 .list_display
3156 .as_ref()
3157 .map(|(v, _)| v.as_slice())
3158 .unwrap_or(&[]);
3159 let list_display_lits = list_display.iter().map(|s| s.as_str());
3160
3161 let search_fields = admin
3162 .search_fields
3163 .as_ref()
3164 .map(|(v, _)| v.as_slice())
3165 .unwrap_or(&[]);
3166 let search_fields_lits = search_fields.iter().map(|s| s.as_str());
3167
3168 let readonly_fields = admin
3169 .readonly_fields
3170 .as_ref()
3171 .map(|(v, _)| v.as_slice())
3172 .unwrap_or(&[]);
3173 let readonly_fields_lits = readonly_fields.iter().map(|s| s.as_str());
3174
3175 let list_filter = admin
3176 .list_filter
3177 .as_ref()
3178 .map(|(v, _)| v.as_slice())
3179 .unwrap_or(&[]);
3180 let list_filter_lits = list_filter.iter().map(|s| s.as_str());
3181
3182 let actions = admin
3183 .actions
3184 .as_ref()
3185 .map(|(v, _)| v.as_slice())
3186 .unwrap_or(&[]);
3187 let actions_lits = actions.iter().map(|s| s.as_str());
3188
3189 let fieldsets = admin
3190 .fieldsets
3191 .as_ref()
3192 .map(|(v, _)| v.as_slice())
3193 .unwrap_or(&[]);
3194 let fieldset_tokens = const_slice(
3195 "e!(#root::core::Fieldset),
3196 fieldsets.iter().map(|(title, fields)| {
3197 let title = title.as_str();
3198 let field_lits = fields.iter().map(String::as_str);
3199 quote!(#root::core::Fieldset::new(#title, &[ #( #field_lits ),* ]))
3200 }),
3201 );
3202
3203 let list_display_links = admin
3204 .list_display_links
3205 .as_ref()
3206 .map(|(v, _)| v.as_slice())
3207 .unwrap_or(&[]);
3208 let list_display_links_lits = list_display_links.iter().map(|s| s.as_str());
3209
3210 let search_help_text = admin.search_help_text.as_deref().unwrap_or("");
3211 let actions_on_top = admin.actions_on_top.unwrap_or(true);
3212 let actions_on_bottom = admin.actions_on_bottom.unwrap_or(false);
3213 let date_hierarchy = admin.date_hierarchy.as_deref().unwrap_or("");
3214
3215 let prepopulated = admin
3216 .prepopulated_fields
3217 .as_ref()
3218 .map(|(v, _)| v.as_slice())
3219 .unwrap_or(&[]);
3220 let prepopulated_tokens = const_slice(
3221 "e!(#root::core::PrepopulatedField),
3222 prepopulated.iter().map(|(target, sources)| {
3223 let target = target.as_str();
3224 let source_lits = sources.iter().map(String::as_str);
3225 quote!(#root::core::PrepopulatedField::new(#target, &[ #( #source_lits ),* ]))
3226 }),
3227 );
3228
3229 let raw_id_fields = admin
3230 .raw_id_fields
3231 .as_ref()
3232 .map(|(v, _)| v.as_slice())
3233 .unwrap_or(&[]);
3234 let raw_id_fields_lits = raw_id_fields.iter().map(|s| s.as_str());
3235
3236 let autocomplete_fields = admin
3237 .autocomplete_fields
3238 .as_ref()
3239 .map(|(v, _)| v.as_slice())
3240 .unwrap_or(&[]);
3241 let autocomplete_fields_lits = autocomplete_fields.iter().map(|s| s.as_str());
3242
3243 let list_select_related_tokens = match admin.list_select_related.as_deref() {
3245 None | Some("all") => quote!(#root::core::ListSelectRelated::All),
3246 Some("none") => quote!(#root::core::ListSelectRelated::None),
3247 Some(raw) => {
3248 let names: Vec<&str> = raw
3249 .split(',')
3250 .map(str::trim)
3251 .filter(|s| !s.is_empty())
3252 .collect();
3253 quote!(#root::core::ListSelectRelated::Only(&[ #( #names ),* ]))
3254 }
3255 };
3256
3257 let formfield_pairs: Vec<(&str, &str)> = admin
3260 .formfield_overrides
3261 .as_ref()
3262 .map(|(v, _)| v.iter().map(|(f, w)| (f.as_str(), w.as_str())).collect())
3263 .unwrap_or_default();
3264 let formfield_tokens = formfield_pairs.iter().map(|(field, widget)| {
3265 let field = *field;
3266 let widget = *widget;
3267 quote!((#field, #widget))
3268 });
3269
3270 let list_per_page = admin.list_per_page.unwrap_or(0);
3271
3272 let ordering_pairs = admin
3273 .ordering
3274 .as_ref()
3275 .map(|(v, _)| v.as_slice())
3276 .unwrap_or(&[]);
3277 let ordering_tokens = ordering_pairs.iter().map(|(name, desc)| {
3278 let name = name.as_str();
3279 let desc = *desc;
3280 quote!((#name, #desc))
3281 });
3282
3283 quote! {
3284 ::core::option::Option::Some({
3285 const ADMIN: &#root::core::AdminConfig = &{
3286 #root::core::AdminConfig::from_parts(#root::core::AdminConfigParts {
3287 list_display: &[ #( #list_display_lits ),* ],
3288 search_fields: &[ #( #search_fields_lits ),* ],
3289 list_per_page: #list_per_page,
3290 ordering: &[ #( #ordering_tokens ),* ],
3291 readonly_fields: &[ #( #readonly_fields_lits ),* ],
3292 list_filter: &[ #( #list_filter_lits ),* ],
3293 actions: &[ #( #actions_lits ),* ],
3294 fieldsets: #fieldset_tokens,
3295 list_display_links: &[ #( #list_display_links_lits ),* ],
3296 search_help_text: #search_help_text,
3297 actions_on_top: #actions_on_top,
3298 actions_on_bottom: #actions_on_bottom,
3299 date_hierarchy: #date_hierarchy,
3300 prepopulated_fields: #prepopulated_tokens,
3301 raw_id_fields: &[ #( #raw_id_fields_lits ),* ],
3302 autocomplete_fields: &[ #( #autocomplete_fields_lits ),* ],
3303 list_select_related: #list_select_related_tokens,
3304 formfield_overrides: &[ #( #formfield_tokens ),* ],
3305 })
3306 };
3307 ADMIN
3308 })
3309 }
3310}
3311
3312fn inherent_impl_tokens(
3313 struct_name: &syn::Ident,
3314 fields: &CollectedFields,
3315 primary_key: Option<&(syn::Ident, String)>,
3316 column_consts: &TokenStream2,
3317 audited_fields: Option<&[&ColumnEntry]>,
3318 upsert_unique: Option<&[String]>,
3319 manager_fns: &[syn::Ident],
3320) -> TokenStream2 {
3321 let root = rustango_root();
3322
3323 let pool_insert_method = if audited_fields.is_some() && !fields.has_auto {
3341 quote!()
3350 } else if audited_fields.is_some() && fields.has_auto {
3351 quote!()
3354 } else if fields.has_auto {
3355 let pushes = &fields.insert_pushes;
3356 let filled = &fields.rust_filled_idents;
3358 let was_unset: Vec<syn::Ident> = filled
3359 .iter()
3360 .map(|i| quote::format_ident!("__{}_was_unset", ident_name(i)))
3361 .collect();
3362 let insert_or_ignore_call = quote! {
3363 #( let #was_unset = matches!(&self.#filled, #root::sql::Auto::Unset); )*
3364 let mut _columns: ::std::vec::Vec<&'static str> =
3365 ::std::vec::Vec::new();
3366 let mut _values: ::std::vec::Vec<#root::core::SqlValue> =
3367 ::std::vec::Vec::new();
3368 #( #pushes )*
3369 let _query = #root::core::InsertQuery::new(
3370 <Self as #root::core::Model>::SCHEMA,
3371 _columns,
3372 _values,
3373 );
3374 let _result = #root::sql::insert_or_ignore(pool, &_query).await;
3375 if !matches!(_result, ::core::result::Result::Ok(true)) {
3376 #( if #was_unset { self.#filled = #root::sql::Auto::Unset; } )*
3377 }
3378 _result
3379 };
3380 if fields.returning_cols.is_empty() {
3387 quote! {
3388
3389
3390 pub async fn insert_or_ignore(
3398 &mut self,
3399 pool: &#root::sql::Pool,
3400 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
3401 #insert_or_ignore_call
3402 }
3403 }
3404 } else {
3405 quote! {
3406
3407
3408 pub async fn insert_or_ignore(
3425 &mut self,
3426 pool: &#root::sql::Pool,
3427 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
3428 #insert_or_ignore_call
3429 }
3430 }
3431 }
3432 } else {
3433 let insert_columns = &fields.insert_columns;
3434 let insert_values = &fields.insert_values;
3435 quote! {
3436
3437
3438 pub async fn insert_or_ignore(
3458 &self,
3459 pool: &#root::sql::Pool,
3460 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
3461 let _query = #root::core::InsertQuery::new(
3462 <Self as #root::core::Model>::SCHEMA,
3463 ::std::vec![ #( #insert_columns ),* ],
3464 ::std::vec![ #( #insert_values ),* ],
3465 );
3466 #root::sql::insert_or_ignore(pool, &_query).await
3467 }
3468 }
3469 };
3470
3471 let audit_pair_tokens: Vec<TokenStream2> = audited_fields
3474 .map(|tracked| {
3475 tracked
3476 .iter()
3477 .map(|c| {
3478 let column_lit = c.column.as_str();
3479 let ident = &c.ident;
3480 quote! {
3481 (
3482 #column_lit,
3483 #root::core::__json_value(&self.#ident),
3484 )
3485 }
3486 })
3487 .collect()
3488 })
3489 .unwrap_or_default();
3490 let audit_pk_to_string = if let Some((pk_ident, _)) = primary_key {
3491 if fields.pk_is_auto {
3492 quote!(self.#pk_ident.get().map(|v| ::std::format!("{}", v)).unwrap_or_default())
3493 } else {
3494 quote!(::std::format!("{}", &self.#pk_ident))
3495 }
3496 } else {
3497 quote!(::std::string::String::new())
3498 };
3499 let delete_audited_body = if primary_key.is_some() {
3502 quote! {
3503 ::std::boxed::Box::pin(#root::audit::delete_many_with_audit::<Self>(
3504 pool,
3505 query,
3506 |_r: &Self| _r.__rustango_audit_entry(#root::audit::AuditOp::Delete),
3507 ))
3508 }
3509 } else {
3510 quote! {
3511 let _ = (pool, query);
3512 ::std::boxed::Box::pin(async {
3513 ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
3514 table: <Self as #root::core::Model>::SCHEMA.table,
3515 })
3516 })
3517 }
3518 };
3519 let cascade_audited_body = if primary_key.is_some() {
3520 quote! {
3521 ::std::boxed::Box::pin(#root::audit::cascade_rows::<Self>(
3522 tx,
3523 <Self as #root::core::Model>::SCHEMA,
3524 rows,
3525 |_r: &Self| _r.__rustango_audit_entry(#root::audit::AuditOp::Delete),
3526 ))
3527 }
3528 } else {
3529 quote! {
3530 let _ = (tx, rows);
3531 ::std::boxed::Box::pin(async {
3532 ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
3533 table: <Self as #root::core::Model>::SCHEMA.table,
3534 })
3535 })
3536 }
3537 };
3538 let update_audited_body = if primary_key.is_some() {
3539 quote! {
3540 ::std::boxed::Box::pin(#root::audit::update_many_with_audit::<Self>(
3541 pool,
3542 query,
3543 move |_r: &Self| _r.__rustango_audit_entry(op),
3544 ))
3545 }
3546 } else {
3547 quote! {
3548 let _ = (pool, query, op);
3549 ::std::boxed::Box::pin(async {
3550 ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
3551 table: <Self as #root::core::Model>::SCHEMA.table,
3552 })
3553 })
3554 }
3555 };
3556 let audit_entry_method = if audited_fields.is_some() {
3557 let pairs = audit_pair_tokens.iter();
3558 let pk_str = audit_pk_to_string.clone();
3559 quote! {
3560 #[doc(hidden)]
3562 pub fn __rustango_audit_pairs(
3563 &self,
3564 ) -> ::std::vec::Vec<(&'static str, #root::__serde_json::Value)> {
3565 ::std::vec![ #( #pairs ),* ]
3566 }
3567
3568 #[doc(hidden)]
3570 pub fn __rustango_audit_pk(&self) -> ::std::string::String {
3571 #pk_str
3572 }
3573
3574 #[doc(hidden)]
3576 pub fn __rustango_audit_entry(
3577 &self,
3578 operation: #root::audit::AuditOp,
3579 ) -> #root::audit::PendingEntry {
3580 #root::audit::PendingEntry {
3581 entity_table: <Self as #root::core::Model>::SCHEMA.table,
3582 entity_pk: self.__rustango_audit_pk(),
3583 operation,
3584 source: #root::audit::current_source(),
3585 changes: #root::audit::snapshot_changes(&self.__rustango_audit_pairs()),
3586 }
3587 }
3588
3589 #[doc(hidden)]
3591 pub fn __rustango_update_audited<'a>(
3592 pool: &'a #root::sql::Pool,
3593 query: &'a #root::core::UpdateQuery,
3594 op: #root::audit::AuditOp,
3595 ) -> ::std::pin::Pin<::std::boxed::Box<
3596 dyn ::core::future::Future<
3597 Output = ::core::result::Result<u64, #root::sql::ExecError>,
3598 > + ::core::marker::Send + 'a,
3599 >> {
3600 #update_audited_body
3601 }
3602
3603 #[doc(hidden)]
3605 pub fn __rustango_create_audited<'a, 't>(
3606 tx: &'a mut #root::sql::PoolTx<'t>,
3607 pk: #root::core::SqlValue,
3608 ) -> ::std::pin::Pin<::std::boxed::Box<
3609 dyn ::core::future::Future<
3610 Output = ::core::result::Result<(), #root::sql::ExecError>,
3611 > + ::core::marker::Send + 'a,
3612 >> {
3613 ::std::boxed::Box::pin(#root::audit::record_create::<Self>(
3614 tx,
3615 <Self as #root::core::Model>::SCHEMA,
3616 pk,
3617 |_r: &Self| _r.__rustango_audit_entry(#root::audit::AuditOp::Create),
3618 ))
3619 }
3620
3621 #[doc(hidden)]
3623 pub fn __rustango_update_record_audited<'a, 't>(
3624 tx: &'a mut #root::sql::PoolTx<'t>,
3625 pk: #root::core::SqlValue,
3626 ) -> ::std::pin::Pin<::std::boxed::Box<
3627 dyn ::core::future::Future<
3628 Output = ::core::result::Result<(), #root::sql::ExecError>,
3629 > + ::core::marker::Send + 'a,
3630 >> {
3631 ::std::boxed::Box::pin(#root::audit::record_update::<Self>(
3632 tx,
3633 <Self as #root::core::Model>::SCHEMA,
3634 pk,
3635 |_r: &Self| _r.__rustango_audit_entry(#root::audit::AuditOp::Update),
3636 ))
3637 }
3638
3639 #[doc(hidden)]
3641 pub fn __rustango_cascade_audited<'a, 't>(
3642 tx: &'a mut #root::sql::PoolTx<'t>,
3643 rows: #root::core::WhereExpr,
3644 ) -> ::std::pin::Pin<::std::boxed::Box<
3645 dyn ::core::future::Future<
3646 Output = ::core::result::Result<
3647 ::std::vec::Vec<(#root::core::SqlValue, #root::audit::PendingEntry)>,
3648 #root::sql::ExecError,
3649 >,
3650 > + ::core::marker::Send + 'a,
3651 >> {
3652 #cascade_audited_body
3653 }
3654
3655 #[doc(hidden)]
3657 pub fn __rustango_delete_audited<'a>(
3658 pool: &'a #root::sql::Pool,
3659 query: &'a #root::core::DeleteQuery,
3660 ) -> ::std::pin::Pin<::std::boxed::Box<
3661 dyn ::core::future::Future<
3662 Output = ::core::result::Result<u64, #root::sql::ExecError>,
3663 > + ::core::marker::Send + 'a,
3664 >> {
3665 #delete_audited_body
3666 }
3667 }
3668 } else {
3669 quote!()
3670 };
3671 let make_entry = |op_path: TokenStream2, written: Option<TokenStream2>| -> TokenStream2 {
3674 if let (Some(v), Some(col)) = (written, fields.soft_delete_column.as_deref()) {
3675 quote! {
3676 let mut _audit_entry = self.__rustango_audit_entry(#op_path);
3677 _audit_entry.set_tracked(#col, #v);
3678 }
3679 } else {
3680 quote!(let _audit_entry = self.__rustango_audit_entry(#op_path);)
3681 }
3682 };
3683 let soft_deleted_json = quote!(#root::__serde_json::to_value(&_now)
3684 .unwrap_or(#root::__serde_json::Value::Null));
3685 let restored_json = quote!(#root::__serde_json::Value::Null);
3686 let make_pool_update = |op_path: TokenStream2, written: TokenStream2| -> TokenStream2 {
3689 if audited_fields.is_some() {
3690 let entry = make_entry(op_path, Some(written));
3691 quote! {
3692 #entry
3693 #root::audit::save_one_with_audit(pool, &_query, &_audit_entry).await
3694 }
3695 } else {
3696 quote!(#root::sql::update_pool(pool, &_query).await)
3697 }
3698 };
3699 let pool_softdelete_run =
3700 make_pool_update(quote!(#root::audit::AuditOp::SoftDelete), soft_deleted_json);
3701 let pool_restore_run = make_pool_update(quote!(#root::audit::AuditOp::Restore), restored_json);
3702
3703 let save_ret = quote!(::core::result::Result<u64, #root::sql::ExecError>);
3707 let save_keep = quote!(matches!(_saved, ::core::result::Result::Ok(n) if n > 0));
3708 let save_partial_methods = |pk_ident: &syn::Ident,
3709 pk_column_lit: &str,
3710 run: TokenStream2|
3711 -> TokenStream2 {
3712 let assignments = &fields.update_assignments;
3713 let assignment_cols = &fields.upsert_update_columns;
3715 let write = restore_auto_now_unless(
3716 &fields.auto_now_idents,
3717 &save_ret,
3718 &save_keep,
3719 "e! {
3720 let mut _filtered: ::std::vec::Vec<#root::core::Assignment> =
3723 ::std::vec::Vec::with_capacity(_wanted_cols.len());
3724 #(
3725 if _wanted_cols.contains(#assignment_cols) {
3726 _filtered.push(#assignments);
3727 }
3728 )*
3729 if _filtered.is_empty() {
3730 #root::__tracing::warn!(
3735 target: "rustango::save_partial",
3736 model = _schema.name,
3737 "save_partial: every named field maps to a non-assignable column — no-op"
3738 );
3739 return ::core::result::Result::Ok(0);
3740 }
3741 let _query = #root::core::UpdateQuery::new(
3742 _schema,
3743 _filtered,
3744 #root::core::WhereExpr::Predicate(#root::core::Filter::new(
3745 #pk_column_lit,
3746 #root::core::Op::Eq,
3747 ::core::convert::Into::<#root::core::SqlValue>::into(
3748 ::core::clone::Clone::clone(&self.#pk_ident)
3749 ),
3750 )),
3751 );
3752 #run
3753 },
3754 );
3755 quote! {
3756 pub async fn save_partial(
3784 &mut self,
3785 fields: &[&str],
3786 pool: &#root::sql::Pool,
3787 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
3788 if fields.is_empty() {
3789 #root::__tracing::warn!(
3790 target: "rustango::save_partial",
3791 model = <Self as #root::core::Model>::SCHEMA.name,
3792 "save_partial called with empty field list — no-op"
3793 );
3794 return ::core::result::Result::Ok(0);
3795 }
3796 let _schema = <Self as #root::core::Model>::SCHEMA;
3797 let mut _wanted_cols: ::std::collections::HashSet<&'static str> =
3799 ::std::collections::HashSet::with_capacity(fields.len());
3800 for f in fields {
3801 match _schema.field(f) {
3802 ::core::option::Option::Some(fs) => {
3803 _wanted_cols.insert(fs.column);
3804 }
3805 ::core::option::Option::None => {
3806 return ::core::result::Result::Err(
3807 #root::sql::ExecError::Query(
3808 #root::core::QueryError::UnknownField {
3809 model: _schema.name,
3810 field: (*f).to_owned(),
3811 }
3812 )
3813 );
3814 }
3815 }
3816 }
3817 #write
3818 }
3819
3820 pub async fn save_partial_typed<
3840 L: #root::core::TypedFieldList<Self>,
3841 >(
3842 &mut self,
3843 fields: L,
3844 pool: &#root::sql::Pool,
3845 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
3846 let _names = fields.rust_field_names();
3847 let _refs: ::std::vec::Vec<&str> =
3848 _names.iter().copied().collect();
3849 self.save_partial(&_refs, pool).await
3850 }
3851 }
3852 };
3853 let pool_save_method = if let Some((pk_ident, pk_col)) = primary_key {
3854 let pk_column_lit = pk_col.as_str();
3855 if audited_fields.is_some() {
3856 quote!()
3857 } else {
3858 let partial = save_partial_methods(
3859 pk_ident,
3860 pk_column_lit,
3861 quote!(#root::sql::update_pool(pool, &_query).await),
3862 );
3863 quote! {
3864 pub async fn save(
3872 &mut self,
3873 pool: &#root::sql::Pool,
3874 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
3875 self.__rustango_save_exec(pool).await
3876 }
3877
3878 #partial
3879 }
3880 }
3881 } else {
3882 quote!()
3883 };
3884
3885 let pool_insert_method = if audited_fields.is_some() {
3892 if let Some(_) = primary_key {
3893 let returning_cols: Vec<proc_macro2::TokenStream> = if !fields.returning_cols.is_empty()
3895 {
3896 fields.returning_cols.clone()
3897 } else {
3898 primary_key
3905 .map(|(_, col)| {
3906 let lit = col.as_str();
3907 vec![quote!(#lit)]
3908 })
3909 .unwrap_or_default()
3910 };
3911 let assigned: Vec<syn::Ident> = fields
3913 .auto_field_idents
3914 .iter()
3915 .map(|(ident, _, _)| ident.clone())
3916 .collect();
3917 let body = restore_auto_now_unless(
3918 &assigned,
3919 "e!(::core::result::Result<(), #root::sql::ExecError>),
3920 "e!(_saved.is_ok()),
3921 "e! {
3922 let mut _query = self.__rustango_insert_query();
3923 if _query.returning.is_empty() {
3925 _query.returning = ::std::vec![ #( #returning_cols ),* ];
3926 }
3927 #root::audit::insert_one_with_audit(pool, &_query, self, |_m: &Self, _op| {
3928 _m.__rustango_audit_entry(_op)
3929 })
3930 .await
3931 },
3932 );
3933 quote! {
3934 pub async fn insert(
3942 &mut self,
3943 pool: &#root::sql::Pool,
3944 ) -> ::core::result::Result<(), #root::sql::ExecError> {
3945 #body
3946 }
3947 }
3948 } else {
3949 quote!()
3950 }
3951 } else {
3952 pool_insert_method
3954 };
3955
3956 let pool_save_method = if let Some(tracked) = audited_fields {
3977 if let Some((pk_ident, pk_col)) = primary_key {
3978 let pk_column_lit = pk_col.as_str();
3979 let partial = save_partial_methods(
3980 pk_ident,
3981 pk_column_lit,
3982 quote! {
3983 self.__rustango_save_with_diff(
3984 pool,
3985 &_query,
3986 ::core::option::Option::Some(&_wanted_cols),
3987 )
3988 .await
3989 },
3990 );
3991 let mk_before_pairs =
3996 |getter: &dyn Fn(&ColumnEntry) -> TokenStream2| -> Vec<TokenStream2> {
3997 tracked
3998 .iter()
3999 .map(|c| {
4000 let getter = getter(c);
4001 let column_lit = c.column.as_str();
4002 let value_ty = &c.value_ty;
4003 quote! {
4004 (
4005 #column_lit,
4006 match #getter::<#value_ty>(
4007 _audit_before_row, #column_lit,
4008 ) {
4009 ::core::result::Result::Ok(v) => {
4010 #root::core::__json_value(&v)
4011 }
4012 ::core::result::Result::Err(_) => #root::__serde_json::Value::Null,
4013 },
4014 )
4015 }
4016 })
4017 .collect()
4018 };
4019 let before_pairs_pg = mk_before_pairs(&|_| quote!(#root::sql::try_get_returning));
4020 let before_pairs_my = mk_before_pairs(&|c| {
4021 if c.uuid_column {
4022 quote!(#root::sql::try_get_flat_my)
4023 } else {
4024 quote!(#root::sql::try_get_returning_my)
4025 }
4026 });
4027 let before_pairs_sqlite =
4028 mk_before_pairs(&|_| quote!(#root::sql::try_get_returning_sqlite));
4029 let tracked_cols: Vec<&str> = tracked.iter().map(|c| c.column.as_str()).collect();
4030 let pk_value_for_bind = if fields.pk_is_auto {
4031 quote!(self.#pk_ident.get().copied().unwrap_or_default())
4032 } else {
4033 quote!(::core::clone::Clone::clone(&self.#pk_ident))
4034 };
4035 let assignments = &fields.update_assignments;
4036 let unset_dispatch = if fields.has_auto {
4037 quote! {
4038 if matches!(self.#pk_ident, #root::sql::Auto::Unset) {
4039 return self.insert(pool).await.map(|()| 1u64);
4040 }
4041 }
4042 } else {
4043 quote!()
4044 };
4045 let write = restore_auto_now_unless(
4046 &fields.auto_now_idents,
4047 &save_ret,
4048 &save_keep,
4049 "e! {
4050 let _query = #root::core::UpdateQuery::new(
4051 <Self as #root::core::Model>::SCHEMA,
4052 ::std::vec![ #( #assignments ),* ],
4053 #root::core::WhereExpr::Predicate(#root::core::Filter::new(
4054 #pk_column_lit,
4055 #root::core::Op::Eq,
4056 ::core::convert::Into::<#root::core::SqlValue>::into(
4057 ::core::clone::Clone::clone(&self.#pk_ident)
4058 ),
4059 )),
4060 );
4061 self.__rustango_save_with_diff(pool, &_query, ::core::option::Option::None)
4062 .await
4063 .and_then(#root::sql::__macro_internals::row_saved)
4064 },
4065 );
4066 quote! {
4067 pub async fn save(
4079 &mut self,
4080 pool: &#root::sql::Pool,
4081 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4082 #unset_dispatch
4083 #write
4084 }
4085
4086 #[doc(hidden)]
4088 async fn __rustango_save_with_diff(
4089 &self,
4090 pool: &#root::sql::Pool,
4091 query: &#root::core::UpdateQuery,
4092 only: ::core::option::Option<&::std::collections::HashSet<&'static str>>,
4093 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4094 let mut _after_pairs = self.__rustango_audit_pairs();
4095 if let ::core::option::Option::Some(only) = only {
4096 _after_pairs.retain(|(col, _)| only.contains(col));
4097 }
4098 let _before_query = #root::audit::before_image_query(
4099 <Self as #root::core::Model>::SCHEMA,
4100 #pk_column_lit,
4101 ::core::convert::Into::<#root::core::SqlValue>::into(
4102 #pk_value_for_bind,
4103 ),
4104 &[ #( #tracked_cols ),* ],
4105 );
4106 #root::audit::save_one_with_diff(
4107 pool,
4108 query,
4109 &_before_query,
4110 <Self as #root::core::Model>::SCHEMA.table,
4111 self.__rustango_audit_pk(),
4112 _after_pairs,
4113 |_audit_before_row| ::std::vec![ #( #before_pairs_pg ),* ],
4114 |_audit_before_row| ::std::vec![ #( #before_pairs_my ),* ],
4115 |_audit_before_row| ::std::vec![ #( #before_pairs_sqlite ),* ],
4116 ).await
4117 }
4118
4119 #partial
4120 }
4121 } else {
4122 quote!()
4123 }
4124 } else {
4125 pool_save_method
4126 };
4127
4128 let pool_delete_method = {
4135 let pk_column_lit = primary_key.map(|(_, col)| col.as_str()).unwrap_or("id");
4136 let pk_ident_for_pool = primary_key.map(|(ident, _)| ident);
4137 if let Some(pk_ident) = pk_ident_for_pool {
4138 if audited_fields.is_some() {
4139 quote! {
4140 pub async fn delete(
4147 &self,
4148 pool: &#root::sql::Pool,
4149 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4150 let _query = #root::core::DeleteQuery::by_pk(
4151 <Self as #root::core::Model>::SCHEMA,
4152 #pk_column_lit,
4153 ::core::convert::Into::<#root::core::SqlValue>::into(
4154 ::core::clone::Clone::clone(&self.#pk_ident)
4155 ),
4156 );
4157 let _audit_entry =
4158 self.__rustango_audit_entry(#root::audit::AuditOp::Delete);
4159 #root::audit::delete_one_with_audit(
4160 pool, &_query, &_audit_entry,
4161 ).await
4162 }
4163 }
4164 } else {
4165 quote! {
4166 pub async fn delete(
4174 &self,
4175 pool: &#root::sql::Pool,
4176 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4177 self.__rustango_delete_exec(pool).await
4178 }
4179 }
4180 }
4181 } else {
4182 quote!()
4183 }
4184 };
4185
4186 let (bulk_delete_run, destroy_run, bulk_update_run, truncate_run, truncate_cascade_run) =
4189 if audited_fields.is_some() {
4190 (
4191 quote!(#root::audit::delete_many_with_audit::<Self>(pool, &_query, |_r: &Self| {
4192 _r.__rustango_audit_entry(#root::audit::AuditOp::Delete)
4193 })
4194 .await),
4195 quote!(#root::audit::delete_each_with_audit::<Self>(pool, &_queries, |_r: &Self| {
4196 _r.__rustango_audit_entry(#root::audit::AuditOp::Delete)
4197 })
4198 .await),
4199 quote!(#root::audit::update_many_with_audit::<Self>(pool, &_query, |_r: &Self| {
4200 _r.__rustango_audit_entry(#root::audit::AuditOp::Update)
4201 })
4202 .await),
4203 quote!(#root::audit::truncate_with_audit(
4204 pool,
4205 <Self as #root::core::Model>::SCHEMA.table,
4206 #root::sql::TruncateMode::Plain,
4207 )
4208 .await),
4209 quote!(#root::audit::truncate_with_audit(
4210 pool,
4211 <Self as #root::core::Model>::SCHEMA.table,
4212 #root::sql::TruncateMode::Cascade,
4213 )
4214 .await
4215 .map(drop)),
4216 )
4217 } else {
4218 (
4219 quote!(#root::sql::delete_pool(pool, &_query).await),
4220 quote!(#root::sql::__rustango_delete_all_or_none(pool, _queries).await),
4221 quote!(#root::sql::update_pool(pool, &_query).await),
4222 quote!(#root::sql::truncate(pool, <Self as #root::core::Model>::SCHEMA).await),
4223 quote!(#root::sql::truncate_cascade(pool, <Self as #root::core::Model>::SCHEMA).await),
4224 )
4225 };
4226
4227 let refresh_replicate_methods = if let Some((pk_ident, _)) = primary_key {
4240 let other_field_clones: Vec<TokenStream2> = fields
4241 .column_entries
4242 .iter()
4243 .filter(|c| &c.ident != pk_ident)
4244 .map(|c| {
4245 let ident = &c.ident;
4246 quote! {
4247 #ident: ::core::clone::Clone::clone(&self.#ident)
4248 }
4249 })
4250 .collect();
4251 let pk_clone_token = if fields.pk_is_auto {
4252 quote! { #pk_ident: #root::sql::Auto::Unset }
4253 } else {
4254 quote! { #pk_ident: ::core::clone::Clone::clone(&self.#pk_ident) }
4255 };
4256 let replicate_doc = if fields.pk_is_auto {
4257 quote! {
4258 }
4270 } else {
4271 quote! {
4272 }
4278 };
4279 let column_names: ::std::collections::HashSet<String> = fields
4293 .column_entries
4294 .iter()
4295 .map(|c| ident_name(&c.ident))
4296 .collect();
4297 let emit_if_no_field_collision = |name: &str, tokens: TokenStream2| -> TokenStream2 {
4298 if column_names.contains(name) {
4299 quote! {}
4300 } else {
4301 tokens
4302 }
4303 };
4304 let count_method = emit_if_no_field_collision(
4305 "count",
4306 quote! {
4307 pub async fn count(
4319 pool: &#root::sql::Pool,
4320 ) -> ::core::result::Result<i64, #root::sql::ExecError> {
4321 use #root::sql::CounterPool as _;
4322 #root::query::QuerySet::<Self>::default()
4323 .count(pool)
4324 .await
4325 }
4326 },
4327 );
4328 let value_method = emit_if_no_field_collision(
4329 "value",
4330 quote! {
4331 pub async fn value<U>(
4341 col: &str,
4342 pool: &#root::sql::Pool,
4343 ) -> ::core::result::Result<
4344 ::core::option::Option<U>,
4345 #root::sql::ExecError,
4346 >
4347 where
4348 U: #root::sql::FlatScalar,
4349 {
4350 let _col_static: &'static str = Self::__resolve_col(col)?;
4351 #root::query::QuerySet::<Self>::default()
4352 .values_list_flat(_col_static)
4353 .first::<U>(pool)
4354 .await
4355 }
4356 },
4357 );
4358 let sum_method = emit_if_no_field_collision(
4359 "sum",
4360 quote! {
4361 pub async fn sum<U>(
4368 col: &str,
4369 pool: &#root::sql::Pool,
4370 ) -> ::core::result::Result<
4371 ::core::option::Option<U>,
4372 #root::sql::ExecError,
4373 >
4374 where
4375 (::core::option::Option<U>,): #root::sql::MaybePgFromRow
4376 + #root::sql::MaybeMyFromRow
4377 + #root::sql::MaybeSqliteFromRow
4378 + ::core::marker::Send
4379 + ::core::marker::Unpin,
4380 {
4381 #root::query::QuerySet::<Self>::default()
4382 .sum::<U>(col, pool)
4383 .await
4384 }
4385 },
4386 );
4387 let avg_method = emit_if_no_field_collision(
4388 "avg",
4389 quote! {
4390 pub async fn avg<U>(
4396 col: &str,
4397 pool: &#root::sql::Pool,
4398 ) -> ::core::result::Result<
4399 ::core::option::Option<U>,
4400 #root::sql::ExecError,
4401 >
4402 where
4403 (::core::option::Option<U>,): #root::sql::MaybePgFromRow
4404 + #root::sql::MaybeMyFromRow
4405 + #root::sql::MaybeSqliteFromRow
4406 + ::core::marker::Send
4407 + ::core::marker::Unpin,
4408 {
4409 #root::query::QuerySet::<Self>::default()
4410 .avg::<U>(col, pool)
4411 .await
4412 }
4413 },
4414 );
4415 let min_method = emit_if_no_field_collision(
4416 "min",
4417 quote! {
4418 pub async fn min<U>(
4424 col: &str,
4425 pool: &#root::sql::Pool,
4426 ) -> ::core::result::Result<
4427 ::core::option::Option<U>,
4428 #root::sql::ExecError,
4429 >
4430 where
4431 (::core::option::Option<U>,): #root::sql::MaybePgFromRow
4432 + #root::sql::MaybeMyFromRow
4433 + #root::sql::MaybeSqliteFromRow
4434 + ::core::marker::Send
4435 + ::core::marker::Unpin,
4436 {
4437 #root::query::QuerySet::<Self>::default()
4438 .min::<U>(col, pool)
4439 .await
4440 }
4441 },
4442 );
4443 let max_method = emit_if_no_field_collision(
4444 "max",
4445 quote! {
4446 pub async fn max<U>(
4452 col: &str,
4453 pool: &#root::sql::Pool,
4454 ) -> ::core::result::Result<
4455 ::core::option::Option<U>,
4456 #root::sql::ExecError,
4457 >
4458 where
4459 (::core::option::Option<U>,): #root::sql::MaybePgFromRow
4460 + #root::sql::MaybeMyFromRow
4461 + #root::sql::MaybeSqliteFromRow
4462 + ::core::marker::Send
4463 + ::core::marker::Unpin,
4464 {
4465 #root::query::QuerySet::<Self>::default()
4466 .max::<U>(col, pool)
4467 .await
4468 }
4469 },
4470 );
4471 let first_method = emit_if_no_field_collision(
4472 "first",
4473 quote! {
4474 pub async fn first(
4481 pool: &#root::sql::Pool,
4482 ) -> ::core::result::Result<
4483 ::core::option::Option<Self>,
4484 #root::sql::ExecError,
4485 > {
4486 #root::query::QuerySet::<Self>::default()
4487 .first(pool)
4488 .await
4489 }
4490 },
4491 );
4492 let last_method = emit_if_no_field_collision(
4493 "last",
4494 quote! {
4495 pub async fn last(
4508 pool: &#root::sql::Pool,
4509 ) -> ::core::result::Result<
4510 ::core::option::Option<Self>,
4511 #root::sql::ExecError,
4512 > {
4513 #root::query::QuerySet::<Self>::default()
4514 .last(pool)
4515 .await
4516 }
4517 },
4518 );
4519 quote! {
4520 pub async fn refresh_from_db(
4539 &mut self,
4540 pool: &#root::sql::Pool,
4541 ) -> ::core::result::Result<(), #root::sql::ExecError> {
4542 match self.fresh(pool).await? {
4543 ::core::option::Option::Some(_fresh) => {
4544 *self = _fresh;
4545 ::core::result::Result::Ok(())
4546 }
4547 ::core::option::Option::None => ::core::result::Result::Err(
4548 #root::sql::ExecError::Driver(
4549 #root::sql::sqlx::Error::RowNotFound,
4550 ),
4551 ),
4552 }
4553 }
4554
4555 pub async fn increment(
4575 &self,
4576 col: &str,
4577 by: i64,
4578 pool: &#root::sql::Pool,
4579 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4580 Self::__increment_one(self, col, by, pool).await
4581 }
4582
4583 pub async fn decrement(
4592 &self,
4593 col: &str,
4594 by: i64,
4595 pool: &#root::sql::Pool,
4596 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4597 Self::__increment_one(self, col, -by, pool).await
4598 }
4599
4600 pub async fn increment_each(
4610 col: &str,
4611 by: i64,
4612 pool: &#root::sql::Pool,
4613 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4614 Self::__increment_all(col, by, pool).await
4615 }
4616
4617 pub async fn decrement_each(
4622 col: &str,
4623 by: i64,
4624 pool: &#root::sql::Pool,
4625 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4626 Self::__increment_all(col, -by, pool).await
4627 }
4628
4629 #[doc(hidden)]
4635 pub async fn __increment_one(
4636 this: &Self,
4637 col: &str,
4638 by: i64,
4639 pool: &#root::sql::Pool,
4640 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4641 let _pk_val: #root::core::SqlValue = ::core::convert::Into::into(
4642 ::core::clone::Clone::clone(&this.#pk_ident),
4643 );
4644 #root::sql::model_shortcuts::increment_one_pool::<Self>(
4645 ::core::stringify!(#pk_ident),
4646 _pk_val,
4647 col,
4648 by,
4649 pool,
4650 )
4651 .await
4652 }
4653
4654 #[doc(hidden)]
4657 pub async fn __increment_all(
4658 col: &str,
4659 by: i64,
4660 pool: &#root::sql::Pool,
4661 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4662 let _query =
4663 #root::sql::model_shortcuts::increment_all_query::<Self>(col, by)?;
4664 #bulk_update_run
4665 }
4666
4667 #[doc(hidden)]
4670 pub fn __resolve_col(
4671 col: &str,
4672 ) -> ::core::result::Result<&'static str, #root::sql::ExecError> {
4673 #root::sql::model_shortcuts::resolve_col::<Self>(col)
4674 }
4675
4676 #[doc(hidden)]
4679 #[must_use]
4680 pub fn __add_signed_expr(
4681 col_static: &'static str,
4682 signed_by: i64,
4683 ) -> #root::core::Expr {
4684 #root::sql::model_shortcuts::add_signed_expr(col_static, signed_by)
4685 }
4686
4687 pub async fn fresh(
4707 &self,
4708 pool: &#root::sql::Pool,
4709 ) -> ::core::result::Result<
4710 ::core::option::Option<Self>,
4711 #root::sql::ExecError,
4712 > {
4713 use #root::sql::FetcherPool as _;
4714 let _pk_val: #root::core::SqlValue = ::core::convert::Into::into(
4715 ::core::clone::Clone::clone(&self.#pk_ident),
4716 );
4717 let _rows: ::std::vec::Vec<Self> =
4719 #root::query::QuerySet::<Self>::default()
4720 .without_global_scopes()
4721 .filter(::core::stringify!(#pk_ident), _pk_val)
4722 .limit(1)
4723 .fetch(pool)
4724 .await?;
4725 ::core::result::Result::Ok(_rows.into_iter().next())
4726 }
4727
4728 #replicate_doc
4729 #[must_use]
4730 pub fn replicate(&self) -> Self {
4731 Self {
4732 #pk_clone_token,
4733 #( #other_field_clones, )*
4734 }
4735 }
4736
4737 #first_method
4738
4739 #last_method
4740
4741 pub async fn first_or_fail(
4751 pool: &#root::sql::Pool,
4752 ) -> ::core::result::Result<Self, #root::sql::ExecError> {
4753 match #root::query::QuerySet::<Self>::default().first(pool).await? {
4757 ::core::option::Option::Some(_row) => ::core::result::Result::Ok(_row),
4758 ::core::option::Option::None => ::core::result::Result::Err(
4759 #root::sql::ExecError::Driver(
4760 #root::sql::sqlx::Error::RowNotFound,
4761 ),
4762 ),
4763 }
4764 }
4765
4766 pub async fn pluck<U>(
4778 col: &'static str,
4779 pool: &#root::sql::Pool,
4780 ) -> ::core::result::Result<::std::vec::Vec<U>, #root::sql::ExecError>
4781 where
4782 U: #root::sql::FlatScalar,
4783 {
4784 #root::query::QuerySet::<Self>::default()
4785 .values_list_flat(col)
4786 .fetch::<U>(pool)
4787 .await
4788 }
4789
4790 pub async fn chunk<F, Fut>(
4816 n: i64,
4817 pool: &#root::sql::Pool,
4818 mut cb: F,
4819 ) -> ::core::result::Result<(), #root::sql::ExecError>
4820 where
4821 F: ::core::ops::FnMut(::std::vec::Vec<Self>) -> Fut,
4822 Fut: ::core::future::Future<
4823 Output = ::core::result::Result<(), #root::sql::ExecError>,
4824 >,
4825 {
4826 use #root::sql::FetcherPool as _;
4827 let pk_col = match <Self as #root::core::Model>::SCHEMA.primary_key() {
4829 ::core::option::Option::Some(f) => f.name,
4830 ::core::option::Option::None => {
4831 return ::core::result::Result::Ok(());
4832 }
4833 };
4834 let mut offset: i64 = 0;
4835 loop {
4836 let rows: ::std::vec::Vec<Self> =
4837 #root::query::QuerySet::<Self>::default()
4838 .order_by(&[(pk_col, false)])
4839 .limit(n)
4840 .offset(offset)
4841 .fetch(pool)
4842 .await?;
4843 if rows.is_empty() {
4844 return ::core::result::Result::Ok(());
4845 }
4846 let len = rows.len() as i64;
4847 cb(rows).await?;
4848 if len < n {
4849 return ::core::result::Result::Ok(());
4850 }
4851 offset += n;
4852 }
4853 }
4854
4855 pub async fn chunk_by_id<F, Fut>(
4867 n: i64,
4868 pool: &#root::sql::Pool,
4869 mut cb: F,
4870 ) -> ::core::result::Result<(), #root::sql::ExecError>
4871 where
4872 F: ::core::ops::FnMut(::std::vec::Vec<Self>) -> Fut,
4873 Fut: ::core::future::Future<
4874 Output = ::core::result::Result<(), #root::sql::ExecError>,
4875 >,
4876 {
4877 use #root::sql::FetcherPool as _;
4878 let pk_col = match <Self as #root::core::Model>::SCHEMA.primary_key() {
4880 ::core::option::Option::Some(f) => f.name,
4881 ::core::option::Option::None => {
4882 return ::core::result::Result::Ok(());
4883 }
4884 };
4885 let key = ::std::format!("{}__gt", pk_col);
4888 let mut last_seen: ::core::option::Option<#root::core::SqlValue> =
4889 ::core::option::Option::None;
4890 loop {
4891 let mut qs = #root::query::QuerySet::<Self>::default();
4892 if let ::core::option::Option::Some(v) = last_seen.take() {
4893 qs = qs.filter(key.as_str(), v);
4894 }
4895 let rows: ::std::vec::Vec<Self> = qs
4896 .order_by(&[(pk_col, false)])
4897 .limit(n)
4898 .fetch(pool)
4899 .await?;
4900 let len = rows.len() as i64;
4901 let ::core::option::Option::Some(max_pk) =
4904 rows.last().map(|r| r.__rustango_pk_value())
4905 else {
4906 return ::core::result::Result::Ok(());
4907 };
4908 cb(rows).await?;
4909 if len < n {
4910 return ::core::result::Result::Ok(());
4911 }
4912 last_seen = ::core::option::Option::Some(max_pk);
4913 }
4914 }
4915
4916 pub async fn each<F, Fut>(
4934 batch: i64,
4935 pool: &#root::sql::Pool,
4936 mut cb: F,
4937 ) -> ::core::result::Result<(), #root::sql::ExecError>
4938 where
4939 F: ::core::ops::FnMut(Self) -> Fut,
4940 Fut: ::core::future::Future<
4941 Output = ::core::result::Result<(), #root::sql::ExecError>,
4942 >,
4943 {
4944 use #root::sql::FetcherPool as _;
4945 let pk_col = match <Self as #root::core::Model>::SCHEMA.primary_key() {
4947 ::core::option::Option::Some(f) => f.name,
4948 ::core::option::Option::None => {
4949 return ::core::result::Result::Ok(());
4950 }
4951 };
4952 let key = ::std::format!("{}__gt", pk_col);
4953 let mut last_seen: ::core::option::Option<#root::core::SqlValue> =
4954 ::core::option::Option::None;
4955 loop {
4956 let mut qs = #root::query::QuerySet::<Self>::default();
4957 if let ::core::option::Option::Some(v) = last_seen.take() {
4958 qs = qs.filter(key.as_str(), v);
4959 }
4960 let rows: ::std::vec::Vec<Self> = qs
4961 .order_by(&[(pk_col, false)])
4962 .limit(batch)
4963 .fetch(pool)
4964 .await?;
4965 let len = rows.len() as i64;
4966 let ::core::option::Option::Some(max_pk) =
4967 rows.last().map(|r| r.__rustango_pk_value())
4968 else {
4969 return ::core::result::Result::Ok(());
4970 };
4971 for row in rows {
4972 cb(row).await?;
4973 }
4974 if len < batch {
4975 return ::core::result::Result::Ok(());
4976 }
4977 last_seen = ::core::option::Option::Some(max_pk);
4978 }
4979 }
4980
4981 pub async fn truncate(
4997 pool: &#root::sql::Pool,
4998 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4999 #truncate_run
5000 }
5001
5002 pub async fn truncate_cascade(
5011 pool: &#root::sql::Pool,
5012 ) -> ::core::result::Result<(), #root::sql::ExecError> {
5013 #truncate_cascade_run
5014 }
5015
5016 pub async fn destroy<V>(
5031 pks: impl ::core::iter::IntoIterator<Item = V>,
5032 pool: &#root::sql::Pool,
5033 ) -> ::core::result::Result<u64, #root::sql::ExecError>
5034 where
5035 V: ::core::convert::Into<#root::core::SqlValue>,
5036 {
5037 let _values: ::std::vec::Vec<#root::core::SqlValue> =
5038 pks.into_iter().map(::core::convert::Into::into).collect();
5039 if _values.is_empty() {
5040 return ::core::result::Result::Ok(0);
5041 }
5042 let _pk = <Self as #root::core::Model>::SCHEMA
5043 .primary_key()
5044 .ok_or_else(|| {
5045 #root::sql::ExecError::Sql(#root::sql::SqlError::MissingPrimaryKey)
5046 })?;
5047 let mut _queries = ::std::vec::Vec::new();
5049 for _keys in #root::sql::__rustango_in_batches::<Self>(pool, _values)? {
5050 _queries.push(
5051 #root::query::QuerySet::<Self>::default()
5052 .filter_op(
5053 _pk.name,
5054 #root::core::Op::In,
5055 #root::core::SqlValue::List(_keys),
5056 )
5057 .compile_delete()?,
5058 );
5059 }
5060 #destroy_run
5061 }
5062
5063 pub async fn where_(
5081 col: &str,
5082 val: impl ::core::convert::Into<#root::core::SqlValue>,
5083 pool: &#root::sql::Pool,
5084 ) -> ::core::result::Result<
5085 ::std::vec::Vec<Self>,
5086 #root::sql::ExecError,
5087 > {
5088 use #root::sql::FetcherPool as _;
5089 #root::query::QuerySet::<Self>::default()
5090 .filter(col, val)
5091 .fetch(pool)
5092 .await
5093 }
5094
5095 pub async fn where_in<V>(
5109 col: &str,
5110 vals: impl ::core::iter::IntoIterator<Item = V>,
5111 pool: &#root::sql::Pool,
5112 ) -> ::core::result::Result<
5113 ::std::vec::Vec<Self>,
5114 #root::sql::ExecError,
5115 >
5116 where
5117 V: ::core::convert::Into<#root::core::SqlValue>,
5118 {
5119 use #root::sql::FetcherPool as _;
5120 let _values: ::std::vec::Vec<#root::core::SqlValue> =
5121 vals.into_iter().map(::core::convert::Into::into).collect();
5122 if _values.is_empty() {
5123 return ::core::result::Result::Ok(::std::vec::Vec::new());
5124 }
5125 let _key = ::std::format!("{}__in", col);
5126 let _batches = #root::sql::__rustango_in_batches::<Self>(pool, _values)?;
5127 let _split = _batches.len() > 1;
5128 let mut _rows = ::std::vec::Vec::new();
5129 for _keys in _batches {
5130 _rows.extend(
5131 #root::query::QuerySet::<Self>::default()
5132 .filter(&_key, #root::core::SqlValue::List(_keys))
5133 .fetch(pool)
5134 .await?,
5135 );
5136 }
5137 if _split {
5138 _rows = #root::sql::__rustango_dedupe_by_pk(_rows);
5139 }
5140 ::core::result::Result::Ok(_rows)
5141 }
5142
5143 pub async fn where_not_in<V>(
5154 col: &str,
5155 vals: impl ::core::iter::IntoIterator<Item = V>,
5156 pool: &#root::sql::Pool,
5157 ) -> ::core::result::Result<
5158 ::std::vec::Vec<Self>,
5159 #root::sql::ExecError,
5160 >
5161 where
5162 V: ::core::convert::Into<#root::core::SqlValue>,
5163 {
5164 use #root::sql::FetcherPool as _;
5165 let _values: ::std::vec::Vec<#root::core::SqlValue> =
5166 vals.into_iter().map(::core::convert::Into::into).collect();
5167 if _values.is_empty() {
5168 return #root::query::QuerySet::<Self>::default()
5169 .fetch(pool)
5170 .await;
5171 }
5172 let _key = ::std::format!("{}__not_in", col);
5173 let _values = #root::sql::__rustango_one_in_list::<Self>(pool, _values)?;
5175 #root::query::QuerySet::<Self>::default()
5176 .filter(&_key, #root::core::SqlValue::List(_values))
5177 .fetch(pool)
5178 .await
5179 }
5180
5181 pub async fn where_null(
5189 col: &str,
5190 pool: &#root::sql::Pool,
5191 ) -> ::core::result::Result<
5192 ::std::vec::Vec<Self>,
5193 #root::sql::ExecError,
5194 > {
5195 use #root::sql::FetcherPool as _;
5196 let _key = ::std::format!("{}__isnull", col);
5197 #root::query::QuerySet::<Self>::default()
5198 .filter(&_key, #root::core::SqlValue::Bool(true))
5199 .fetch(pool)
5200 .await
5201 }
5202
5203 pub async fn where_not_null(
5211 col: &str,
5212 pool: &#root::sql::Pool,
5213 ) -> ::core::result::Result<
5214 ::std::vec::Vec<Self>,
5215 #root::sql::ExecError,
5216 > {
5217 use #root::sql::FetcherPool as _;
5218 let _key = ::std::format!("{}__isnull", col);
5219 #root::query::QuerySet::<Self>::default()
5220 .filter(&_key, #root::core::SqlValue::Bool(false))
5221 .fetch(pool)
5222 .await
5223 }
5224
5225 pub async fn random_n(
5238 n: i64,
5239 pool: &#root::sql::Pool,
5240 ) -> ::core::result::Result<
5241 ::std::vec::Vec<Self>,
5242 #root::sql::ExecError,
5243 > {
5244 use #root::sql::FetcherPool as _;
5245 #root::query::QuerySet::<Self>::default()
5246 .order_random()
5247 .limit(n)
5248 .fetch(pool)
5249 .await
5250 }
5251
5252 pub async fn random(
5261 pool: &#root::sql::Pool,
5262 ) -> ::core::result::Result<
5263 ::core::option::Option<Self>,
5264 #root::sql::ExecError,
5265 > {
5266 ::core::result::Result::Ok(
5267 Self::random_n(1, pool).await?.into_iter().next(),
5268 )
5269 }
5270
5271 pub async fn oldest(
5280 field: &str,
5281 pool: &#root::sql::Pool,
5282 ) -> ::core::result::Result<
5283 ::std::vec::Vec<Self>,
5284 #root::sql::ExecError,
5285 > {
5286 use #root::sql::FetcherPool as _;
5287 #root::query::QuerySet::<Self>::default()
5288 .order_by(&[(field, false)])
5289 .fetch(pool)
5290 .await
5291 }
5292
5293 pub async fn newest(
5302 field: &str,
5303 pool: &#root::sql::Pool,
5304 ) -> ::core::result::Result<
5305 ::std::vec::Vec<Self>,
5306 #root::sql::ExecError,
5307 > {
5308 use #root::sql::FetcherPool as _;
5309 #root::query::QuerySet::<Self>::default()
5310 .order_by(&[(field, true)])
5311 .fetch(pool)
5312 .await
5313 }
5314
5315 pub async fn where_year(
5325 col: &str,
5326 year: i64,
5327 pool: &#root::sql::Pool,
5328 ) -> ::core::result::Result<
5329 ::std::vec::Vec<Self>,
5330 #root::sql::ExecError,
5331 > {
5332 use #root::sql::FetcherPool as _;
5333 let _key = ::std::format!("{}__year", col);
5334 #root::query::QuerySet::<Self>::default()
5335 .filter(&_key, #root::core::SqlValue::I64(year))
5336 .fetch(pool)
5337 .await
5338 }
5339
5340 pub async fn where_month(
5349 col: &str,
5350 month: i64,
5351 pool: &#root::sql::Pool,
5352 ) -> ::core::result::Result<
5353 ::std::vec::Vec<Self>,
5354 #root::sql::ExecError,
5355 > {
5356 use #root::sql::FetcherPool as _;
5357 let _key = ::std::format!("{}__month", col);
5358 #root::query::QuerySet::<Self>::default()
5359 .filter(&_key, #root::core::SqlValue::I64(month))
5360 .fetch(pool)
5361 .await
5362 }
5363
5364 pub async fn where_day(
5373 col: &str,
5374 day: i64,
5375 pool: &#root::sql::Pool,
5376 ) -> ::core::result::Result<
5377 ::std::vec::Vec<Self>,
5378 #root::sql::ExecError,
5379 > {
5380 use #root::sql::FetcherPool as _;
5381 let _key = ::std::format!("{}__day", col);
5382 #root::query::QuerySet::<Self>::default()
5383 .filter(&_key, #root::core::SqlValue::I64(day))
5384 .fetch(pool)
5385 .await
5386 }
5387
5388 pub async fn where_hour(
5397 col: &str,
5398 hour: i64,
5399 pool: &#root::sql::Pool,
5400 ) -> ::core::result::Result<
5401 ::std::vec::Vec<Self>,
5402 #root::sql::ExecError,
5403 > {
5404 use #root::sql::FetcherPool as _;
5405 let _key = ::std::format!("{}__hour", col);
5406 #root::query::QuerySet::<Self>::default()
5407 .filter(&_key, #root::core::SqlValue::I64(hour))
5408 .fetch(pool)
5409 .await
5410 }
5411
5412 pub async fn where_minute(
5421 col: &str,
5422 minute: i64,
5423 pool: &#root::sql::Pool,
5424 ) -> ::core::result::Result<
5425 ::std::vec::Vec<Self>,
5426 #root::sql::ExecError,
5427 > {
5428 use #root::sql::FetcherPool as _;
5429 let _key = ::std::format!("{}__minute", col);
5430 #root::query::QuerySet::<Self>::default()
5431 .filter(&_key, #root::core::SqlValue::I64(minute))
5432 .fetch(pool)
5433 .await
5434 }
5435
5436 pub async fn where_like(
5446 col: &str,
5447 pattern: impl ::core::convert::Into<::std::string::String>,
5448 pool: &#root::sql::Pool,
5449 ) -> ::core::result::Result<
5450 ::std::vec::Vec<Self>,
5451 #root::sql::ExecError,
5452 > {
5453 use #root::sql::FetcherPool as _;
5454 let _key = ::std::format!("{}__like", col);
5455 #root::query::QuerySet::<Self>::default()
5456 .filter(
5457 &_key,
5458 #root::core::SqlValue::String(pattern.into()),
5459 )
5460 .fetch(pool)
5461 .await
5462 }
5463
5464 pub async fn where_ilike(
5473 col: &str,
5474 pattern: impl ::core::convert::Into<::std::string::String>,
5475 pool: &#root::sql::Pool,
5476 ) -> ::core::result::Result<
5477 ::std::vec::Vec<Self>,
5478 #root::sql::ExecError,
5479 > {
5480 use #root::sql::FetcherPool as _;
5481 let _key = ::std::format!("{}__ilike", col);
5482 #root::query::QuerySet::<Self>::default()
5483 .filter(
5484 &_key,
5485 #root::core::SqlValue::String(pattern.into()),
5486 )
5487 .fetch(pool)
5488 .await
5489 }
5490
5491 pub async fn where_starts_with(
5500 col: &str,
5501 prefix: impl ::core::convert::Into<::std::string::String>,
5502 pool: &#root::sql::Pool,
5503 ) -> ::core::result::Result<
5504 ::std::vec::Vec<Self>,
5505 #root::sql::ExecError,
5506 > {
5507 use #root::sql::FetcherPool as _;
5508 let _key = ::std::format!("{}__startswith", col);
5509 #root::query::QuerySet::<Self>::default()
5510 .filter(
5511 &_key,
5512 #root::core::SqlValue::String(prefix.into()),
5513 )
5514 .fetch(pool)
5515 .await
5516 }
5517
5518 pub async fn where_ends_with(
5527 col: &str,
5528 suffix: impl ::core::convert::Into<::std::string::String>,
5529 pool: &#root::sql::Pool,
5530 ) -> ::core::result::Result<
5531 ::std::vec::Vec<Self>,
5532 #root::sql::ExecError,
5533 > {
5534 use #root::sql::FetcherPool as _;
5535 let _key = ::std::format!("{}__endswith", col);
5536 #root::query::QuerySet::<Self>::default()
5537 .filter(
5538 &_key,
5539 #root::core::SqlValue::String(suffix.into()),
5540 )
5541 .fetch(pool)
5542 .await
5543 }
5544
5545 pub async fn where_contains(
5554 col: &str,
5555 substr: impl ::core::convert::Into<::std::string::String>,
5556 pool: &#root::sql::Pool,
5557 ) -> ::core::result::Result<
5558 ::std::vec::Vec<Self>,
5559 #root::sql::ExecError,
5560 > {
5561 use #root::sql::FetcherPool as _;
5562 let _key = ::std::format!("{}__contains", col);
5563 #root::query::QuerySet::<Self>::default()
5564 .filter(
5565 &_key,
5566 #root::core::SqlValue::String(substr.into()),
5567 )
5568 .fetch(pool)
5569 .await
5570 }
5571
5572 pub async fn where_gt(
5580 col: &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 use #root::sql::FetcherPool as _;
5588 let _key = ::std::format!("{}__gt", col);
5589 #root::query::QuerySet::<Self>::default()
5590 .filter(&_key, val)
5591 .fetch(pool)
5592 .await
5593 }
5594
5595 pub async fn where_gte(
5603 col: &str,
5604 val: impl ::core::convert::Into<#root::core::SqlValue>,
5605 pool: &#root::sql::Pool,
5606 ) -> ::core::result::Result<
5607 ::std::vec::Vec<Self>,
5608 #root::sql::ExecError,
5609 > {
5610 use #root::sql::FetcherPool as _;
5611 let _key = ::std::format!("{}__gte", col);
5612 #root::query::QuerySet::<Self>::default()
5613 .filter(&_key, val)
5614 .fetch(pool)
5615 .await
5616 }
5617
5618 pub async fn where_lt(
5626 col: &str,
5627 val: impl ::core::convert::Into<#root::core::SqlValue>,
5628 pool: &#root::sql::Pool,
5629 ) -> ::core::result::Result<
5630 ::std::vec::Vec<Self>,
5631 #root::sql::ExecError,
5632 > {
5633 use #root::sql::FetcherPool as _;
5634 let _key = ::std::format!("{}__lt", col);
5635 #root::query::QuerySet::<Self>::default()
5636 .filter(&_key, val)
5637 .fetch(pool)
5638 .await
5639 }
5640
5641 pub async fn where_lte(
5649 col: &str,
5650 val: impl ::core::convert::Into<#root::core::SqlValue>,
5651 pool: &#root::sql::Pool,
5652 ) -> ::core::result::Result<
5653 ::std::vec::Vec<Self>,
5654 #root::sql::ExecError,
5655 > {
5656 use #root::sql::FetcherPool as _;
5657 let _key = ::std::format!("{}__lte", col);
5658 #root::query::QuerySet::<Self>::default()
5659 .filter(&_key, val)
5660 .fetch(pool)
5661 .await
5662 }
5663
5664 pub async fn where_ne(
5672 col: &str,
5673 val: impl ::core::convert::Into<#root::core::SqlValue>,
5674 pool: &#root::sql::Pool,
5675 ) -> ::core::result::Result<
5676 ::std::vec::Vec<Self>,
5677 #root::sql::ExecError,
5678 > {
5679 use #root::sql::FetcherPool as _;
5680 let _key = ::std::format!("{}__ne", col);
5681 #root::query::QuerySet::<Self>::default()
5682 .filter(&_key, val)
5683 .fetch(pool)
5684 .await
5685 }
5686
5687 pub async fn where_any(
5701 cols: &[&str],
5702 val: impl ::core::convert::Into<#root::core::SqlValue>,
5703 pool: &#root::sql::Pool,
5704 ) -> ::core::result::Result<
5705 ::std::vec::Vec<Self>,
5706 #root::sql::ExecError,
5707 > {
5708 Self::__where_multi(cols, val, false, pool).await
5709 }
5710
5711 pub async fn where_all(
5718 cols: &[&str],
5719 val: impl ::core::convert::Into<#root::core::SqlValue>,
5720 pool: &#root::sql::Pool,
5721 ) -> ::core::result::Result<
5722 ::std::vec::Vec<Self>,
5723 #root::sql::ExecError,
5724 > {
5725 Self::__where_multi(cols, val, true, pool).await
5726 }
5727
5728 #[doc(hidden)]
5732 pub async fn __where_multi(
5733 cols: &[&str],
5734 val: impl ::core::convert::Into<#root::core::SqlValue>,
5735 all: bool,
5736 pool: &#root::sql::Pool,
5737 ) -> ::core::result::Result<
5738 ::std::vec::Vec<Self>,
5739 #root::sql::ExecError,
5740 > {
5741 #root::sql::model_shortcuts::where_multi_pool::<Self>(cols, val, all, pool)
5742 .await
5743 }
5744
5745 pub async fn take(
5755 n: i64,
5756 pool: &#root::sql::Pool,
5757 ) -> ::core::result::Result<
5758 ::std::vec::Vec<Self>,
5759 #root::sql::ExecError,
5760 > {
5761 use #root::sql::FetcherPool as _;
5762 #root::query::QuerySet::<Self>::default()
5763 .limit(n)
5764 .fetch(pool)
5765 .await
5766 }
5767
5768 pub async fn for_page(
5780 page: i64,
5781 per_page: i64,
5782 pool: &#root::sql::Pool,
5783 ) -> ::core::result::Result<
5784 ::std::vec::Vec<Self>,
5785 #root::sql::ExecError,
5786 > {
5787 use #root::sql::FetcherPool as _;
5788 let _offset = #root::list_params::page_offset(page, per_page);
5789 #root::query::QuerySet::<Self>::default()
5790 .limit(per_page)
5791 .offset(_offset)
5792 .fetch(pool)
5793 .await
5794 }
5795
5796 pub async fn paginate(
5812 page: i64,
5813 per_page: i64,
5814 pool: &#root::sql::Pool,
5815 ) -> ::core::result::Result<
5816 (::std::vec::Vec<Self>, i64),
5817 #root::sql::ExecError,
5818 > {
5819 let total = {
5825 use #root::sql::CounterPool as _;
5826 #root::query::QuerySet::<Self>::default()
5827 .count(pool)
5828 .await?
5829 };
5830 let rows = Self::for_page(page, per_page, pool).await?;
5831 ::core::result::Result::Ok((rows, total))
5832 }
5833
5834 pub async fn update_where(
5848 where_col: &str,
5849 where_val: impl ::core::convert::Into<#root::core::SqlValue>,
5850 set_col: &str,
5851 set_val: impl ::core::convert::Into<#root::core::SqlValue>,
5852 pool: &#root::sql::Pool,
5853 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
5854 let _query = #root::query::QuerySet::<Self>::default()
5855 .filter(where_col, where_val)
5856 .update()
5857 .set(set_col, set_val)
5858 .compile()?;
5859 #bulk_update_run
5860 }
5861
5862 pub async fn delete_where(
5870 where_col: &str,
5871 where_val: impl ::core::convert::Into<#root::core::SqlValue>,
5872 pool: &#root::sql::Pool,
5873 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
5874 let _query = #root::query::QuerySet::<Self>::default()
5875 .filter_op(where_col, #root::core::Op::Eq, where_val)
5876 .compile_delete()?;
5877 #bulk_delete_run
5878 }
5879
5880 pub async fn update_all(
5893 set_col: &str,
5894 set_val: impl ::core::convert::Into<#root::core::SqlValue>,
5895 pool: &#root::sql::Pool,
5896 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
5897 let _query = #root::query::QuerySet::<Self>::default()
5898 .update()
5899 .set(set_col, set_val)
5900 .compile()?;
5901 #bulk_update_run
5902 }
5903
5904 pub async fn where_not_like(
5913 col: &str,
5914 pattern: impl ::core::convert::Into<::std::string::String>,
5915 pool: &#root::sql::Pool,
5916 ) -> ::core::result::Result<
5917 ::std::vec::Vec<Self>,
5918 #root::sql::ExecError,
5919 > {
5920 use #root::sql::FetcherPool as _;
5921 let _key = ::std::format!("{}__not_like", col);
5922 #root::query::QuerySet::<Self>::default()
5923 .filter(
5924 &_key,
5925 #root::core::SqlValue::String(pattern.into()),
5926 )
5927 .fetch(pool)
5928 .await
5929 }
5930
5931 pub async fn where_not_ilike(
5940 col: &str,
5941 pattern: impl ::core::convert::Into<::std::string::String>,
5942 pool: &#root::sql::Pool,
5943 ) -> ::core::result::Result<
5944 ::std::vec::Vec<Self>,
5945 #root::sql::ExecError,
5946 > {
5947 use #root::sql::FetcherPool as _;
5948 let _key = ::std::format!("{}__not_ilike", col);
5949 #root::query::QuerySet::<Self>::default()
5950 .filter(
5951 &_key,
5952 #root::core::SqlValue::String(pattern.into()),
5953 )
5954 .fetch(pool)
5955 .await
5956 }
5957
5958 pub async fn where_not_between(
5967 col: &str,
5968 lo: impl ::core::convert::Into<#root::core::SqlValue>,
5969 hi: impl ::core::convert::Into<#root::core::SqlValue>,
5970 pool: &#root::sql::Pool,
5971 ) -> ::core::result::Result<
5972 ::std::vec::Vec<Self>,
5973 #root::sql::ExecError,
5974 > {
5975 use #root::sql::FetcherPool as _;
5976 let _key = ::std::format!("{}__not_between", col);
5977 let _vals = #root::core::SqlValue::List(::std::vec![
5978 ::core::convert::Into::into(lo),
5979 ::core::convert::Into::into(hi),
5980 ]);
5981 #root::query::QuerySet::<Self>::default()
5982 .filter(&_key, _vals)
5983 .fetch(pool)
5984 .await
5985 }
5986
5987 #[must_use]
5990 pub fn table_name() -> &'static str {
5991 <Self as #root::core::Model>::SCHEMA.table
5992 }
5993
5994 #[must_use]
5999 pub fn primary_key_column() -> ::core::option::Option<&'static str> {
6000 <Self as #root::core::Model>::SCHEMA
6001 .primary_key()
6002 .map(|f| f.column)
6003 }
6004
6005 pub async fn where_between(
6014 col: &str,
6015 lo: impl ::core::convert::Into<#root::core::SqlValue>,
6016 hi: impl ::core::convert::Into<#root::core::SqlValue>,
6017 pool: &#root::sql::Pool,
6018 ) -> ::core::result::Result<
6019 ::std::vec::Vec<Self>,
6020 #root::sql::ExecError,
6021 > {
6022 use #root::sql::FetcherPool as _;
6023 let _key = ::std::format!("{}__between", col);
6024 let _vals = #root::core::SqlValue::List(::std::vec![
6025 ::core::convert::Into::into(lo),
6026 ::core::convert::Into::into(hi),
6027 ]);
6028 #root::query::QuerySet::<Self>::default()
6029 .filter(&_key, _vals)
6030 .fetch(pool)
6031 .await
6032 }
6033
6034 pub async fn first_where(
6049 col: &str,
6050 val: impl ::core::convert::Into<#root::core::SqlValue>,
6051 pool: &#root::sql::Pool,
6052 ) -> ::core::result::Result<
6053 ::core::option::Option<Self>,
6054 #root::sql::ExecError,
6055 > {
6056 #root::query::QuerySet::<Self>::default()
6057 .filter(col, val)
6058 .first(pool)
6059 .await
6060 }
6061
6062 #value_method
6063
6064 pub async fn latest(
6078 field: &str,
6079 pool: &#root::sql::Pool,
6080 ) -> ::core::result::Result<
6081 ::core::option::Option<Self>,
6082 #root::sql::ExecError,
6083 > {
6084 #root::query::QuerySet::<Self>::default()
6085 .latest(field, pool)
6086 .await
6087 }
6088
6089 pub async fn earliest(
6095 field: &str,
6096 pool: &#root::sql::Pool,
6097 ) -> ::core::result::Result<
6098 ::core::option::Option<Self>,
6099 #root::sql::ExecError,
6100 > {
6101 #root::query::QuerySet::<Self>::default()
6102 .earliest(field, pool)
6103 .await
6104 }
6105
6106 #count_method
6107
6108 pub async fn exists(
6117 pool: &#root::sql::Pool,
6118 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
6119 use #root::sql::ExistsPool as _;
6120 #root::query::QuerySet::<Self>::default()
6121 .exists(pool)
6122 .await
6123 }
6124
6125 pub async fn doesnt_exist(
6132 pool: &#root::sql::Pool,
6133 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
6134 Self::exists(pool).await.map(|e| !e)
6135 }
6136
6137 pub async fn contains_pk(
6151 pk: impl ::core::convert::Into<#root::core::SqlValue> + ::core::marker::Send,
6152 pool: &#root::sql::Pool,
6153 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
6154 use #root::sql::ExistsPool as _;
6155 #root::query::QuerySet::<Self>::default()
6156 .contains_pk(pool, pk)
6157 .await
6158 }
6159
6160 #sum_method
6161 #avg_method
6162 #min_method
6163 #max_method
6164
6165 pub async fn all(
6181 pool: &#root::sql::Pool,
6182 ) -> ::core::result::Result<::std::vec::Vec<Self>, #root::sql::ExecError>
6183 {
6184 use #root::sql::FetcherPool as _;
6185 #root::query::QuerySet::<Self>::default()
6186 .fetch(pool)
6187 .await
6188 }
6189
6190 pub async fn find_many<V>(
6213 pks: impl ::core::iter::IntoIterator<Item = V>,
6214 pool: &#root::sql::Pool,
6215 ) -> ::core::result::Result<
6216 ::std::vec::Vec<Self>,
6217 #root::sql::ExecError,
6218 >
6219 where
6220 V: ::core::convert::Into<#root::core::SqlValue>,
6221 {
6222 use #root::sql::FetcherPool as _;
6223 let _values: ::std::vec::Vec<#root::core::SqlValue> =
6224 pks.into_iter().map(::core::convert::Into::into).collect();
6225 if _values.is_empty() {
6226 return ::core::result::Result::Ok(::std::vec::Vec::new());
6227 }
6228 let _key = ::std::format!("{}__in", ::core::stringify!(#pk_ident));
6229 let _batches = #root::sql::__rustango_in_batches::<Self>(pool, _values)?;
6230 let _split = _batches.len() > 1;
6231 let mut _rows = ::std::vec::Vec::new();
6232 for _keys in _batches {
6233 _rows.extend(
6234 #root::query::QuerySet::<Self>::default()
6235 .filter(&_key, #root::core::SqlValue::List(_keys))
6236 .fetch(pool)
6237 .await?,
6238 );
6239 }
6240 if _split {
6241 _rows = #root::sql::__rustango_dedupe_by_pk(_rows);
6242 }
6243 ::core::result::Result::Ok(_rows)
6244 }
6245
6246 pub async fn find(
6261 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6262 pool: &#root::sql::Pool,
6263 ) -> ::core::result::Result<::core::option::Option<Self>, #root::sql::ExecError>
6264 {
6265 use #root::sql::FetcherPool as _;
6266 let _pk_val: #root::core::SqlValue = pk.into();
6267 let mut _rows: ::std::vec::Vec<Self> =
6268 #root::query::QuerySet::<Self>::default()
6269 .filter(::core::stringify!(#pk_ident), _pk_val)
6270 .limit(1)
6271 .fetch(pool)
6272 .await?;
6273 ::core::result::Result::Ok(_rows.into_iter().next())
6274 }
6275
6276 pub async fn find_or_fail(
6293 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6294 pool: &#root::sql::Pool,
6295 ) -> ::core::result::Result<Self, #root::sql::ExecError> {
6296 match Self::find(pk, pool).await? {
6297 ::core::option::Option::Some(_row) => ::core::result::Result::Ok(_row),
6298 ::core::option::Option::None => ::core::result::Result::Err(
6299 #root::sql::ExecError::Driver(
6300 #root::sql::sqlx::Error::RowNotFound,
6301 ),
6302 ),
6303 }
6304 }
6305
6306 pub async fn find_many_or_fail<V>(
6326 pks: impl ::core::iter::IntoIterator<Item = V>,
6327 pool: &#root::sql::Pool,
6328 ) -> ::core::result::Result<
6329 ::std::vec::Vec<Self>,
6330 #root::sql::ExecError,
6331 >
6332 where
6333 V: ::core::convert::Into<#root::core::SqlValue>,
6334 {
6335 let _values: ::std::vec::Vec<#root::core::SqlValue> =
6336 pks.into_iter().map(::core::convert::Into::into).collect();
6337 if _values.is_empty() {
6338 return ::core::result::Result::Ok(::std::vec::Vec::new());
6339 }
6340 use #root::sql::ExistsPool as _;
6341 let mut _seen: ::std::collections::HashSet<
6342 ::std::string::String,
6343 > = ::std::collections::HashSet::new();
6344 let mut _keys: ::std::vec::Vec<#root::core::SqlValue> =
6345 ::std::vec::Vec::new();
6346 for v in &_values {
6347 if _seen.insert(v.to_display_string()) {
6348 _keys.push(::core::clone::Clone::clone(v));
6349 }
6350 }
6351 let _rows = Self::find_many(_values, pool).await?;
6352 let _found: ::std::collections::HashSet<::std::string::String> = _rows
6353 .iter()
6354 .map(|r| r.__rustango_pk_value().to_display_string())
6355 .collect();
6356 for _key in _keys {
6359 if _found.contains(&_key.to_display_string()) {
6360 continue;
6361 }
6362 let _hit = #root::query::QuerySet::<Self>::default()
6363 .filter(::core::stringify!(#pk_ident), _key)
6364 .exists(pool)
6365 .await?;
6366 if !_hit {
6367 return ::core::result::Result::Err(
6368 #root::sql::ExecError::Driver(
6369 #root::sql::sqlx::Error::RowNotFound,
6370 ),
6371 );
6372 }
6373 }
6374 ::core::result::Result::Ok(_rows)
6375 }
6376
6377 pub async fn find_or<F>(
6392 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6393 pool: &#root::sql::Pool,
6394 fallback: F,
6395 ) -> ::core::result::Result<Self, #root::sql::ExecError>
6396 where
6397 F: ::core::ops::FnOnce() -> Self,
6398 {
6399 ::core::result::Result::Ok(
6400 Self::find(pk, pool).await?.unwrap_or_else(fallback),
6401 )
6402 }
6403
6404 pub async fn find_or_new<F>(
6418 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6419 pool: &#root::sql::Pool,
6420 fallback: F,
6421 ) -> ::core::result::Result<(Self, bool), #root::sql::ExecError>
6422 where
6423 F: ::core::ops::FnOnce() -> Self,
6424 {
6425 match Self::find(pk, pool).await? {
6426 ::core::option::Option::Some(_row) => ::core::result::Result::Ok((_row, true)),
6427 ::core::option::Option::None => {
6428 ::core::result::Result::Ok((fallback(), false))
6429 }
6430 }
6431 }
6432
6433 pub async fn find_or_insert<F>(
6457 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6458 pool: &#root::sql::Pool,
6459 fallback: F,
6460 ) -> ::core::result::Result<(Self, bool), #root::sql::ExecError>
6461 where
6462 F: ::core::ops::FnOnce() -> Self,
6463 {
6464 if let ::core::option::Option::Some(_row) = Self::find(pk, pool).await? {
6465 return ::core::result::Result::Ok((_row, true));
6466 }
6467 let mut _new = fallback();
6468 _new.save(pool).await?;
6469 ::core::result::Result::Ok((_new, false))
6470 }
6471
6472 pub async fn first_or<F>(
6479 pool: &#root::sql::Pool,
6480 fallback: F,
6481 ) -> ::core::result::Result<Self, #root::sql::ExecError>
6482 where
6483 F: ::core::ops::FnOnce() -> Self,
6484 {
6485 ::core::result::Result::Ok(
6489 #root::query::QuerySet::<Self>::default()
6490 .first(pool)
6491 .await?
6492 .unwrap_or_else(fallback),
6493 )
6494 }
6495
6496 pub async fn sole(
6506 col: &str,
6507 val: impl ::core::convert::Into<#root::core::SqlValue>,
6508 pool: &#root::sql::Pool,
6509 ) -> ::core::result::Result<Self, #root::sql::ExecError> {
6510 let mut _rows = Self::where_(col, val, pool).await?;
6511 match _rows.len() {
6512 0 => ::core::result::Result::Err(
6513 #root::sql::ExecError::Driver(
6514 #root::sql::sqlx::Error::RowNotFound,
6515 ),
6516 ),
6517 1 => ::core::result::Result::Ok(_rows.remove(0)),
6518 n => ::core::result::Result::Err(
6519 #root::sql::ExecError::MultipleRowsReturned {
6520 op: "sole",
6521 table: <Self as #root::core::Model>::SCHEMA.name,
6522 count: n,
6523 },
6524 ),
6525 }
6526 }
6527 }
6528 } else {
6529 quote!()
6530 };
6531
6532 let tx_emit = |op: TokenStream2| -> TokenStream2 {
6535 if audited_fields.is_some() && primary_key.is_some() {
6536 quote! {
6537 #root::audit::__emit_many_tx(
6538 tx,
6539 &[self.__rustango_audit_entry(#root::audit::AuditOp::#op)],
6540 )
6541 .await?;
6542 }
6543 } else {
6544 quote!()
6545 }
6546 };
6547 let tx_insert_method = {
6548 let self_ref = if fields.has_auto {
6549 quote!(&mut self)
6550 } else {
6551 quote!(&self)
6552 };
6553 let emit = tx_emit(quote!(Create));
6554 let refuse_pkless = if audited_fields.is_some() && primary_key.is_none() {
6555 quote! {
6556 return ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
6557 table: <Self as #root::core::Model>::SCHEMA.table,
6558 });
6559 }
6560 } else {
6561 quote!()
6562 };
6563 quote! {
6564 #[allow(unreachable_code)]
6570 pub async fn insert_tx(
6571 #self_ref,
6572 tx: &mut #root::sql::PoolTx<'_>,
6573 ) -> ::core::result::Result<(), #root::sql::ExecError> {
6574 #refuse_pkless
6575 self.__rustango_insert_exec(&mut *tx).await?;
6576 #emit
6577 ::core::result::Result::Ok(())
6578 }
6579 }
6580 };
6581 let tx_save_method = if let Some((pk_ident, _)) = primary_key {
6582 let emit_create = tx_emit(quote!(Create));
6583 let emit_update = tx_emit(quote!(Update));
6584 let was_unset = if fields.pk_is_auto {
6585 quote!(matches!(self.#pk_ident, #root::sql::Auto::Unset))
6586 } else {
6587 quote!(false)
6588 };
6589 quote! {
6590 pub async fn save_tx(
6596 &mut self,
6597 tx: &mut #root::sql::PoolTx<'_>,
6598 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6599 let _was_unset = #was_unset;
6600 let _n = self.__rustango_save_exec(&mut *tx).await?;
6601 if _n > 0 {
6602 if _was_unset {
6603 #emit_create
6604 } else {
6605 #emit_update
6606 }
6607 }
6608 ::core::result::Result::Ok(_n)
6609 }
6610 }
6611 } else {
6612 quote!()
6613 };
6614 let tx_delete_method = if primary_key.is_some() {
6615 let emit = tx_emit(quote!(Delete));
6616 quote! {
6617 pub async fn delete_tx(
6623 &self,
6624 tx: &mut #root::sql::PoolTx<'_>,
6625 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6626 let _n = self.__rustango_delete_exec(&mut *tx).await?;
6627 if _n > 0 {
6628 #emit
6629 }
6630 ::core::result::Result::Ok(_n)
6631 }
6632 }
6633 } else {
6634 quote!()
6635 };
6636
6637 let refuse = |method: &str| -> TokenStream2 {
6641 let reason = format!("`{method}` cannot audit; use the `&Pool` method");
6642 quote!(#root::sql::__macro_internals::refuse_audited::<Self>(#reason)?;)
6643 };
6644 let refuse_save = refuse("save_on");
6645 let save_method = if let Some((pk_ident, pk_column)) = primary_key {
6646 let pk_column_lit = pk_column.as_str();
6647 let assignments = &fields.update_assignments;
6648 let dispatch_unset = if fields.pk_is_auto {
6649 quote! {
6650 if matches!(self.#pk_ident, #root::sql::Auto::Unset) {
6651 return self.__rustango_insert_exec(_executor).await.map(|()| 1u64);
6652 }
6653 }
6654 } else {
6655 quote!()
6656 };
6657 let write = restore_auto_now_unless(
6658 &fields.auto_now_idents,
6659 &save_ret,
6660 &save_keep,
6661 "e! {
6662 let _query = #root::core::UpdateQuery::new(
6663 <Self as #root::core::Model>::SCHEMA,
6664 ::std::vec![ #( #assignments ),* ],
6665 #root::core::WhereExpr::Predicate(#root::core::Filter::new(
6666 #pk_column_lit,
6667 #root::core::Op::Eq,
6668 ::core::convert::Into::<#root::core::SqlValue>::into(
6669 ::core::clone::Clone::clone(&self.#pk_ident)
6670 ),
6671 )),
6672 );
6673 #root::sql::__macro_internals::update_on(_executor, &_query)
6674 .await
6675 .and_then(#root::sql::__macro_internals::row_saved)
6676 },
6677 );
6678 let upsert = if fields.pk_is_auto {
6679 let upsert_cols = &fields.upsert_update_columns;
6680 let upsert_pushes = &fields.insert_pushes;
6681 let (gen_idents, gen_cols): (Vec<_>, Vec<_>) =
6682 fields.uuid_v7_fields.iter().cloned().unzip();
6683 let upsert_returning = &fields.returning_cols;
6684 let auto_now_idents = &fields.auto_now_idents;
6685 let upsert_target_columns: Vec<String> =
6688 upsert_unique.map_or_else(|| vec![pk_column.clone()], <[String]>::to_vec);
6689 let upsert_target_lits = upsert_target_columns
6690 .iter()
6691 .map(String::as_str)
6692 .collect::<Vec<_>>();
6693 let conflict_clause = if fields.upsert_update_columns.is_empty() {
6694 quote!(#root::core::ConflictClause::DoNothing)
6695 } else {
6696 quote!(#root::core::ConflictClause::DoUpdate {
6697 target: ::std::vec![ #( #upsert_target_lits ),* ],
6698 update_columns: ::std::vec![ #( #upsert_cols ),* ],
6699 })
6700 };
6701 let unit_ret = quote!(::core::result::Result<(), #root::sql::ExecError>);
6702 let unit_keep = quote!(_saved.is_ok());
6703 let exec_body = restore_auto_now_unless(
6704 auto_now_idents,
6705 &unit_ret,
6706 &unit_keep,
6707 "e! {
6708 let _query = self.__rustango_upsert_query();
6709 let _row = #root::sql::__macro_internals::insert_returning_on(
6710 _executor, _query,
6711 )
6712 .await?;
6713 #root::sql::apply_auto_pk(_row, self)
6714 },
6715 );
6716 let pool_body = if audited_fields.is_some() {
6717 restore_auto_now_unless(
6718 auto_now_idents,
6719 &unit_ret,
6720 &unit_keep,
6721 "e! {
6722 let _query = self.__rustango_upsert_query();
6723 #root::audit::insert_one_with_audit(pool, &_query, self, |_m: &Self, _op| {
6724 _m.__rustango_audit_entry(_op)
6725 })
6726 .await
6727 },
6728 )
6729 } else {
6730 quote!(self.__rustango_upsert_exec(pool).await)
6731 };
6732 let refuse_upsert = refuse("upsert_on");
6733 quote! {
6734 pub async fn upsert(
6742 &mut self,
6743 pool: &#root::sql::Pool,
6744 ) -> ::core::result::Result<(), #root::sql::ExecError> {
6745 #pool_body
6746 }
6747
6748 pub async fn upsert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6754 &mut self,
6755 _executor: _E,
6756 ) -> ::core::result::Result<(), #root::sql::ExecError> {
6757 #refuse_upsert
6758 self.__rustango_upsert_exec(_executor).await
6759 }
6760
6761 #[doc(hidden)]
6762 pub async fn __rustango_upsert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6763 &mut self,
6764 _executor: _E,
6765 ) -> ::core::result::Result<(), #root::sql::ExecError> {
6766 #exec_body
6767 }
6768
6769 #[doc(hidden)]
6771 pub fn __rustango_upsert_query(&mut self) -> #root::core::InsertQuery {
6772 let mut _columns: ::std::vec::Vec<&'static str> = ::std::vec::Vec::new();
6773 let mut _values: ::std::vec::Vec<#root::core::SqlValue> =
6774 ::std::vec::Vec::new();
6775 #[allow(unused_mut)]
6776 let mut _generated: ::std::vec::Vec<&'static str> = ::std::vec::Vec::new();
6777 #(
6778 if matches!(&self.#gen_idents, #root::sql::Auto::Unset) {
6779 _generated.push(#gen_cols);
6780 }
6781 )*
6782 #( self.#auto_now_idents = #root::sql::Auto::Set(#root::__chrono::Utc::now()); )*
6783 #( #upsert_pushes )*
6784 #root::core::InsertQuery::new(
6785 <Self as #root::core::Model>::SCHEMA,
6786 _columns,
6787 _values,
6788 )
6789 .returning(::std::vec![ #( #upsert_returning ),* ])
6790 .on_conflict(#conflict_clause)
6791 .generated(_generated)
6792 }
6793 }
6794 } else {
6795 quote!()
6796 };
6797 Some(quote! {
6798 pub async fn save_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6805 &mut self,
6806 _executor: _E,
6807 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6808 #refuse_save
6809 self.__rustango_save_exec(_executor).await
6810 }
6811
6812 #[doc(hidden)]
6813 pub async fn __rustango_save_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6814 &mut self,
6815 _executor: _E,
6816 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6817 #dispatch_unset
6818 #write
6819 }
6820
6821 #[deprecated(since = "0.62.0", note = "use `save`")]
6826 pub async fn save_pool(
6827 &mut self,
6828 pool: &#root::sql::Pool,
6829 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6830 self.save(pool).await
6831 }
6832
6833 #upsert
6834 })
6835 } else {
6836 None
6837 };
6838
6839 let pk_methods = primary_key.map(|(pk_ident, pk_column)| {
6840 let pk_column_lit = pk_column.as_str();
6841 let refuse_delete = refuse("delete_on");
6842 let soft_delete_methods = if let Some(col) = fields.soft_delete_column.as_deref() {
6849 let col_lit = col;
6850 let sd_field_ident = fields
6851 .soft_delete_field_ident
6852 .clone()
6853 .expect("soft_delete_column without ident");
6854 let refuse_soft_delete = refuse("soft_delete_on");
6855 let refuse_restore = refuse("restore_on");
6856 let mark_deleted = quote! {
6858 if _n > 0 {
6859 self.#sd_field_ident = ::core::option::Option::Some(_now);
6860 }
6861 };
6862 let mark_restored = quote! {
6863 if _n > 0 {
6864 self.#sd_field_ident = ::core::option::Option::None;
6865 }
6866 };
6867 quote! {
6868 pub async fn soft_delete_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6875 &mut self,
6876 _executor: _E,
6877 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6878 #refuse_soft_delete
6879 let _now = #root::__chrono::Utc::now();
6880 let _query = #root::soft_delete::__mark_query(
6881 <Self as #root::core::Model>::SCHEMA,
6882 #col_lit,
6883 #pk_column_lit,
6884 ::core::convert::Into::<#root::core::SqlValue>::into(
6885 ::core::clone::Clone::clone(&self.#pk_ident)
6886 ),
6887 ::core::option::Option::Some(_now),
6888 );
6889 let _n = #root::sql::__macro_internals::update_on(_executor, &_query).await?;
6890 #mark_deleted
6891 ::core::result::Result::Ok(_n)
6892 }
6893
6894 pub async fn restore_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6900 &mut self,
6901 _executor: _E,
6902 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6903 #refuse_restore
6904 let _query = #root::soft_delete::__mark_query(
6905 <Self as #root::core::Model>::SCHEMA,
6906 #col_lit,
6907 #pk_column_lit,
6908 ::core::convert::Into::<#root::core::SqlValue>::into(
6909 ::core::clone::Clone::clone(&self.#pk_ident)
6910 ),
6911 ::core::option::Option::None,
6912 );
6913 let _n = #root::sql::__macro_internals::update_on(_executor, &_query).await?;
6914 #mark_restored
6915 ::core::result::Result::Ok(_n)
6916 }
6917
6918 pub async fn soft_delete(
6933 &mut self,
6934 pool: &#root::sql::Pool,
6935 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6936 let _now = #root::__chrono::Utc::now();
6937 let _query = #root::soft_delete::__mark_query(
6938 <Self as #root::core::Model>::SCHEMA,
6939 #col_lit,
6940 #pk_column_lit,
6941 ::core::convert::Into::<#root::core::SqlValue>::into(
6942 ::core::clone::Clone::clone(&self.#pk_ident)
6943 ),
6944 ::core::option::Option::Some(_now),
6945 );
6946 let _n = { #pool_softdelete_run }?;
6947 #mark_deleted
6948 ::core::result::Result::Ok(_n)
6949 }
6950
6951 pub async fn restore(
6961 &mut self,
6962 pool: &#root::sql::Pool,
6963 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6964 let _query = #root::soft_delete::__mark_query(
6965 <Self as #root::core::Model>::SCHEMA,
6966 #col_lit,
6967 #pk_column_lit,
6968 ::core::convert::Into::<#root::core::SqlValue>::into(
6969 ::core::clone::Clone::clone(&self.#pk_ident)
6970 ),
6971 ::core::option::Option::None,
6972 );
6973 let _n = { #pool_restore_run }?;
6974 #mark_restored
6975 ::core::result::Result::Ok(_n)
6976 }
6977
6978 pub async fn force_delete(
6992 &self,
6993 pool: &#root::sql::Pool,
6994 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6995 Self::delete(self, pool).await
6996 }
6997
6998 pub fn trashed(&self) -> bool {
7007 ::core::option::Option::is_some(&self.#sd_field_ident)
7008 }
7009
7010 pub async fn active(
7023 pool: &#root::sql::Pool,
7024 ) -> ::core::result::Result<
7025 ::std::vec::Vec<Self>,
7026 #root::sql::ExecError,
7027 > {
7028 use #root::sql::FetcherPool as _;
7029 #root::query::QuerySet::<Self>::default()
7030 .active()
7031 .fetch(pool)
7032 .await
7033 }
7034
7035 pub async fn only_trashed(
7047 pool: &#root::sql::Pool,
7048 ) -> ::core::result::Result<
7049 ::std::vec::Vec<Self>,
7050 #root::sql::ExecError,
7051 > {
7052 use #root::sql::FetcherPool as _;
7053 #root::query::QuerySet::<Self>::default()
7054 .only_trashed()
7055 .fetch(pool)
7056 .await
7057 }
7058
7059 pub async fn with_trashed(
7075 pool: &#root::sql::Pool,
7076 ) -> ::core::result::Result<
7077 ::std::vec::Vec<Self>,
7078 #root::sql::ExecError,
7079 > {
7080 use #root::sql::FetcherPool as _;
7081 #root::query::QuerySet::<Self>::default()
7082 .with_trashed()
7083 .fetch(pool)
7084 .await
7085 }
7086
7087 }
7088 } else {
7089 quote!()
7090 };
7091 quote! {
7092
7093
7094 pub async fn delete_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7100 &self,
7101 _executor: _E,
7102 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
7103 #refuse_delete
7104 self.__rustango_delete_exec(_executor).await
7105 }
7106
7107 #[doc(hidden)]
7108 pub async fn __rustango_delete_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7109 &self,
7110 _executor: _E,
7111 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
7112 let _query = #root::core::DeleteQuery::by_pk(
7113 <Self as #root::core::Model>::SCHEMA,
7114 #pk_column_lit,
7115 ::core::convert::Into::<#root::core::SqlValue>::into(
7116 ::core::clone::Clone::clone(&self.#pk_ident)
7117 ),
7118 );
7119 #root::sql::__macro_internals::delete_on(_executor, &_query).await
7120 }
7121
7122 #[deprecated(since = "0.62.0", note = "use `delete`")]
7127 pub async fn delete_pool(
7128 &self,
7129 pool: &#root::sql::Pool,
7130 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
7131 self.delete(pool).await
7132 }
7133
7134
7135 #pool_delete_method
7136 #pool_insert_method
7137 #audit_entry_method
7138 #pool_save_method
7139 #refresh_replicate_methods
7140 #tx_delete_method
7141 #tx_insert_method
7142 #tx_save_method
7143 #soft_delete_methods
7144
7145 pub fn is(&self, other: &Self) -> bool {
7154 self.#pk_ident == other.#pk_ident
7155 }
7156
7157 pub fn is_not(&self, other: &Self) -> bool {
7160 self.#pk_ident != other.#pk_ident
7161 }
7162
7163 #[must_use]
7171 pub fn get_key(&self) -> #root::core::SqlValue {
7172 ::core::convert::Into::into(::core::clone::Clone::clone(&self.#pk_ident))
7173 }
7174
7175 }
7176 });
7177
7178 let refuse_insert = refuse("insert_on");
7179 let audited_insert = audited_fields.is_some() && primary_key.is_some();
7182 let insert_pool_body = if audited_fields.is_some() {
7183 quote! {
7184 ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
7185 table: <Self as #root::core::Model>::SCHEMA.table,
7186 })
7187 }
7188 } else {
7189 quote!(self.__rustango_insert_exec(pool).await)
7190 };
7191 let plain_insert_auto = if audited_insert {
7192 quote!()
7193 } else {
7194 quote! {
7195 pub async fn insert(
7201 &mut self,
7202 pool: &#root::sql::Pool,
7203 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7204 #insert_pool_body
7205 }
7206 }
7207 };
7208 let plain_insert_plain = if audited_insert {
7209 quote!()
7210 } else {
7211 quote! {
7212 pub async fn insert(
7217 &self,
7218 pool: &#root::sql::Pool,
7219 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7220 #insert_pool_body
7221 }
7222 }
7223 };
7224 let insert_method = if fields.has_auto {
7225 let pushes = &fields.insert_pushes;
7226 let returning_cols = &fields.returning_cols;
7227 quote! {
7228 #plain_insert_auto
7229 pub async fn insert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7235 &mut self,
7236 _executor: _E,
7237 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7238 #refuse_insert
7239 self.__rustango_insert_exec(_executor).await
7240 }
7241
7242 #[doc(hidden)]
7243 pub async fn __rustango_insert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7244 &mut self,
7245 _executor: _E,
7246 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7247 let _query = self.__rustango_insert_query();
7248 if _query.returning.is_empty() {
7250 return #root::sql::__macro_internals::insert_owned_on(_executor, _query).await;
7251 }
7252 let _row = #root::sql::__macro_internals::insert_returning_on(
7253 _executor, _query,
7254 )
7255 .await?;
7256 #root::sql::apply_auto_pk(_row, self)
7257 }
7258
7259 #[doc(hidden)]
7261 pub fn __rustango_insert_query(&mut self) -> #root::core::InsertQuery {
7262 let mut _columns: ::std::vec::Vec<&'static str> = ::std::vec::Vec::new();
7263 let mut _values: ::std::vec::Vec<#root::core::SqlValue> = ::std::vec::Vec::new();
7264 #( #pushes )*
7265 #root::core::InsertQuery::new(
7266 <Self as #root::core::Model>::SCHEMA,
7267 _columns,
7268 _values,
7269 )
7270 .returning(::std::vec![ #( #returning_cols ),* ])
7271 }
7272
7273 #[deprecated(since = "0.62.0", note = "use `insert`")]
7278 pub async fn insert_pool(
7279 &mut self,
7280 pool: &#root::sql::Pool,
7281 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7282 self.insert(pool).await
7283 }
7284 }
7285 } else {
7286 let insert_columns = &fields.insert_columns;
7287 let insert_values = &fields.insert_values;
7288 let pool_self = if audited_fields.is_some() {
7290 quote!(&mut self)
7291 } else {
7292 quote!(&self)
7293 };
7294 quote! {
7295 #plain_insert_plain
7296 pub async fn insert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7302 &self,
7303 _executor: _E,
7304 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7305 #refuse_insert
7306 self.__rustango_insert_exec(_executor).await
7307 }
7308
7309 #[doc(hidden)]
7310 pub async fn __rustango_insert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7311 &self,
7312 _executor: _E,
7313 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7314 #root::sql::__macro_internals::insert_owned_on(
7315 _executor,
7316 self.__rustango_insert_query(),
7317 )
7318 .await
7319 }
7320
7321 #[doc(hidden)]
7323 pub fn __rustango_insert_query(&self) -> #root::core::InsertQuery {
7324 #root::core::InsertQuery::new(
7325 <Self as #root::core::Model>::SCHEMA,
7326 ::std::vec![ #( #insert_columns ),* ],
7327 ::std::vec![ #( #insert_values ),* ],
7328 )
7329 }
7330
7331 #[deprecated(since = "0.62.0", note = "use `insert`")]
7336 pub async fn insert_pool(
7337 #pool_self,
7338 pool: &#root::sql::Pool,
7339 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7340 self.insert(pool).await
7341 }
7342 }
7343 };
7344
7345 let bulk_pool_body = if audited_fields.is_some() {
7348 quote! {
7349 let mut _tx = #root::sql::__macro_internals::write_tx(pool).await?;
7350 Self::__rustango_bulk_insert_exec(rows, &mut *_tx).await?;
7351 if rows.iter().any(|_row| _row.__rustango_audit_pk().is_empty()) {
7353 return ::core::result::Result::Err(#root::sql::ExecError::AuditUnsupported {
7354 table: <Self as #root::core::Model>::SCHEMA.table,
7355 reason: "MySQL has no RETURNING to read the written rows",
7356 });
7357 }
7358 let _entries: ::std::vec::Vec<#root::audit::PendingEntry> = rows
7359 .iter()
7360 .map(|_row| _row.__rustango_audit_entry(#root::audit::AuditOp::Create))
7361 .collect();
7362 #root::audit::__emit_many_tx(&mut _tx, &_entries).await?;
7363 _tx.commit().await?;
7364 ::core::result::Result::Ok(())
7365 }
7366 } else {
7367 quote!(Self::__rustango_bulk_insert_exec(rows, pool).await)
7368 };
7369 let refuse_bulk = refuse("bulk_insert_on");
7370 let bulk_insert_method = if fields.has_auto {
7371 let cols_no_auto = &fields.bulk_columns_no_auto;
7372 let cols_all = &fields.bulk_columns_all;
7373 let pushes_no_auto = &fields.bulk_pushes_no_auto;
7374 let pushes_all = &fields.bulk_pushes_all;
7375 let returning_cols = &fields.returning_cols;
7376 let uniformity = &fields.bulk_auto_uniformity;
7377 let fill_in_place = &fields.bulk_fill_in_place;
7378 let first_auto_ident = fields
7380 .first_db_auto
7381 .as_ref()
7382 .map(|(ident, _)| ident)
7383 .or(fields.first_auto_ident.as_ref())
7384 .expect("has_auto implies first_auto_ident is Some");
7385 quote! {
7386 pub async fn bulk_insert(
7396 rows: &mut [Self],
7397 pool: &#root::sql::Pool,
7398 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7399 #bulk_pool_body
7400 }
7401
7402 pub async fn bulk_insert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7408 rows: &mut [Self],
7409 _executor: _E,
7410 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7411 #refuse_bulk
7412 Self::__rustango_bulk_insert_exec(rows, _executor).await
7413 }
7414
7415 #[doc(hidden)]
7416 pub async fn __rustango_bulk_insert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7417 rows: &mut [Self],
7418 _executor: _E,
7419 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7420 if rows.is_empty() {
7421 return ::core::result::Result::Ok(());
7422 }
7423 for _row in rows.iter_mut() {
7424 #( #fill_in_place )*
7425 }
7426 let (_columns, _all_rows) = Self::__rustango_bulk_rows(rows)?;
7427 let _query = #root::core::BulkInsertQuery::new(
7428 <Self as #root::core::Model>::SCHEMA,
7429 _columns,
7430 _all_rows,
7431 )
7432 .returning(::std::vec![ #( #returning_cols ),* ]);
7433 let _returned =
7434 #root::sql::__macro_internals::bulk_insert_owned_on(_executor, _query).await?;
7435 if _returned.is_empty() {
7436 return ::core::result::Result::Ok(());
7437 }
7438 if _returned.len() != rows.len() {
7439 return ::core::result::Result::Err(#root::sql::ExecError::Sql(
7440 #root::sql::SqlError::BulkInsertReturningMismatch {
7441 expected: rows.len(),
7442 actual: _returned.len(),
7443 },
7444 ));
7445 }
7446 for (_row, _row_mut) in _returned.into_iter().zip(rows.iter_mut()) {
7447 #root::sql::apply_auto_pk(_row, _row_mut)?;
7448 }
7449 ::core::result::Result::Ok(())
7450 }
7451
7452 #[doc(hidden)]
7456 pub fn __rustango_bulk_rows(
7457 rows: &[Self],
7458 ) -> ::core::result::Result<
7459 (
7460 ::std::vec::Vec<&'static str>,
7461 ::std::vec::Vec<::std::vec::Vec<#root::core::SqlValue>>,
7462 ),
7463 #root::sql::ExecError,
7464 > {
7465 let _first_unset = match rows.first() {
7466 ::core::option::Option::Some(_r) => {
7467 matches!(_r.#first_auto_ident, #root::sql::Auto::Unset)
7468 }
7469 ::core::option::Option::None => true,
7470 };
7471 #( #uniformity )*
7472 let mut _all_rows: ::std::vec::Vec<
7473 ::std::vec::Vec<#root::core::SqlValue>,
7474 > = ::std::vec::Vec::with_capacity(rows.len());
7475 let _columns: ::std::vec::Vec<&'static str> = if _first_unset {
7476 for _row in rows.iter() {
7477 let mut _row_vals: ::std::vec::Vec<#root::core::SqlValue> =
7478 ::std::vec::Vec::new();
7479 #( #pushes_no_auto )*
7480 _all_rows.push(_row_vals);
7481 }
7482 ::std::vec![ #( #cols_no_auto ),* ]
7483 } else {
7484 for _row in rows.iter() {
7485 let mut _row_vals: ::std::vec::Vec<#root::core::SqlValue> =
7486 ::std::vec::Vec::new();
7487 #( #pushes_all )*
7488 _all_rows.push(_row_vals);
7489 }
7490 ::std::vec![ #( #cols_all ),* ]
7491 };
7492 ::core::result::Result::Ok((_columns, _all_rows))
7493 }
7494 }
7495 } else {
7496 let cols_all = &fields.bulk_columns_all;
7497 let pushes_all = &fields.bulk_pushes_all;
7498 quote! {
7499 pub async fn bulk_insert(
7506 rows: &[Self],
7507 pool: &#root::sql::Pool,
7508 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7509 #bulk_pool_body
7510 }
7511
7512 pub async fn bulk_insert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7518 rows: &[Self],
7519 _executor: _E,
7520 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7521 #refuse_bulk
7522 Self::__rustango_bulk_insert_exec(rows, _executor).await
7523 }
7524
7525 #[doc(hidden)]
7526 pub async fn __rustango_bulk_insert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7527 rows: &[Self],
7528 _executor: _E,
7529 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7530 if rows.is_empty() {
7531 return ::core::result::Result::Ok(());
7532 }
7533 let (_columns, _all_rows) = Self::__rustango_bulk_rows(rows)?;
7534 let _query = #root::core::BulkInsertQuery::new(
7535 <Self as #root::core::Model>::SCHEMA,
7536 _columns,
7537 _all_rows,
7538 );
7539 #root::sql::__macro_internals::bulk_insert_owned_on(_executor, _query)
7540 .await
7541 .map(drop)
7542 }
7543
7544 #[doc(hidden)]
7546 pub fn __rustango_bulk_rows(
7547 rows: &[Self],
7548 ) -> ::core::result::Result<
7549 (
7550 ::std::vec::Vec<&'static str>,
7551 ::std::vec::Vec<::std::vec::Vec<#root::core::SqlValue>>,
7552 ),
7553 #root::sql::ExecError,
7554 > {
7555 let mut _all_rows: ::std::vec::Vec<
7556 ::std::vec::Vec<#root::core::SqlValue>,
7557 > = ::std::vec::Vec::with_capacity(rows.len());
7558 for _row in rows.iter() {
7559 let mut _row_vals: ::std::vec::Vec<#root::core::SqlValue> =
7560 ::std::vec::Vec::new();
7561 #( #pushes_all )*
7562 _all_rows.push(_row_vals);
7563 }
7564 ::core::result::Result::Ok((::std::vec![ #( #cols_all ),* ], _all_rows))
7565 }
7566 }
7567 };
7568
7569 let run_conflict_bulk_insert = match (audited_fields.is_some(), primary_key.is_some()) {
7576 (false, _) => quote!(#root::sql::bulk_insert_pool(pool, &_query).await),
7577 (true, true) => quote! {
7578 #root::audit::bulk_insert_with_audit::<Self>(pool, &_query, |_r: &Self, _op| {
7579 _r.__rustango_audit_entry(_op)
7580 })
7581 .await
7582 },
7583 (true, false) => quote! {
7584 ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
7585 table: <Self as #root::core::Model>::SCHEMA.table,
7586 })
7587 },
7588 };
7589 let bulk_upsert_pool_method = {
7590 let (gen_idents, gen_cols): (Vec<_>, Vec<_>) =
7591 fields.uuid_v7_fields.iter().cloned().unzip();
7592 quote! {
7593 pub async fn bulk_upsert_pool(
7622 rows: &[Self],
7623 target: &[&'static str],
7624 update_cols: &[&'static str],
7625 pool: &#root::sql::Pool,
7626 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7627 if rows.is_empty() {
7628 return ::core::result::Result::Ok(());
7629 }
7630 let (_columns, _all_rows) = Self::__rustango_bulk_rows(rows)?;
7631 #[allow(unused_mut)]
7632 let mut _generated: ::std::vec::Vec<&'static str> = ::std::vec::Vec::new();
7633 #(
7634 if rows.iter().all(|_r| matches!(&_r.#gen_idents, #root::sql::Auto::Unset)) {
7635 _generated.push(#gen_cols);
7636 }
7637 )*
7638 let _query = #root::core::BulkInsertQuery::new(
7639 <Self as #root::core::Model>::SCHEMA,
7640 _columns,
7641 _all_rows,
7642 )
7643 .on_conflict_do_update(target, update_cols)
7644 .generated(_generated);
7645 #run_conflict_bulk_insert
7646 }
7647
7648 pub async fn bulk_insert_or_ignore_pool(
7658 rows: &[Self],
7659 pool: &#root::sql::Pool,
7660 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7661 if rows.is_empty() {
7662 return ::core::result::Result::Ok(());
7663 }
7664 let (_columns, _all_rows) = Self::__rustango_bulk_rows(rows)?;
7665 let _query = #root::core::BulkInsertQuery::new(
7666 <Self as #root::core::Model>::SCHEMA,
7667 _columns,
7668 _all_rows,
7669 )
7670 .on_conflict_do_nothing();
7671 #run_conflict_bulk_insert
7672 }
7673 }
7674 };
7675
7676 let bulk_update_rows_run = if audited_fields.is_some() {
7685 quote!(#root::audit::bulk_update_with_audit::<Self>(pool, &_query, |_r: &Self| {
7686 _r.__rustango_audit_entry(#root::audit::AuditOp::Update)
7687 })
7688 .await)
7689 } else {
7690 quote!(#root::sql::bulk_update_pool(pool, &_query).await)
7691 };
7692 let bulk_update_method = match &fields.primary_key {
7693 None => quote! {},
7694 Some((pk_ident, pk_col)) => {
7695 let mut col_arms: Vec<TokenStream2> = Vec::new();
7699 let mut val_arms: Vec<TokenStream2> = Vec::new();
7700 for entry in &fields.column_entries {
7701 if &entry.column == pk_col {
7702 continue;
7703 }
7704 let col = &entry.column;
7705 let ident = &entry.ident;
7706 col_arms.push(quote! { #col => #col, });
7707 val_arms.push(quote! {
7708 #col => _row_vals.push(
7709 ::core::convert::Into::<#root::core::SqlValue>::into(
7710 ::core::clone::Clone::clone(&_o.#ident)
7711 )
7712 ),
7713 });
7714 }
7715 quote! {
7716 pub async fn bulk_update(
7742 objs: &[Self],
7743 fields: &[&str],
7744 pool: &#root::sql::Pool,
7745 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
7746 if objs.is_empty() || fields.is_empty() {
7747 return ::core::result::Result::Ok(0);
7748 }
7749 let _model_name = <Self as #root::core::Model>::SCHEMA.name;
7750 let mut _update_columns: ::std::vec::Vec<&'static str> =
7751 ::std::vec::Vec::with_capacity(fields.len());
7752 for &_f in fields {
7753 let _col: &'static str = match _f {
7754 #pk_col => {
7755 return ::core::result::Result::Err(
7756 ::core::convert::Into::into(
7757 #root::core::QueryError::BulkUpdatePrimaryKey {
7758 model: _model_name,
7759 field: ::std::string::ToString::to_string(_f),
7760 }
7761 )
7762 );
7763 }
7764 #( #col_arms )*
7765 _ => {
7766 return ::core::result::Result::Err(
7767 ::core::convert::Into::into(
7768 #root::core::QueryError::UnknownField {
7769 model: _model_name,
7770 field: ::std::string::ToString::to_string(_f),
7771 }
7772 )
7773 );
7774 }
7775 };
7776 #[allow(unreachable_code)]
7785 _update_columns.push(_col);
7786 }
7787 let mut _rows: ::std::vec::Vec<
7788 ::std::vec::Vec<#root::core::SqlValue>,
7789 > = ::std::vec::Vec::with_capacity(objs.len());
7790 for _o in objs.iter() {
7791 let mut _row_vals: ::std::vec::Vec<#root::core::SqlValue> =
7792 ::std::vec::Vec::with_capacity(fields.len() + 1);
7793 _row_vals.push(
7795 ::core::convert::Into::<#root::core::SqlValue>::into(
7796 ::core::clone::Clone::clone(&_o.#pk_ident)
7797 )
7798 );
7799 for &_f in fields {
7800 match _f {
7801 #( #val_arms )*
7802 _ => {}
7805 }
7806 }
7807 _rows.push(_row_vals);
7808 }
7809 let _query = #root::core::BulkUpdateQuery::new(
7810 <Self as #root::core::Model>::SCHEMA,
7811 _update_columns,
7812 _rows,
7813 );
7814 #bulk_update_rows_run
7815 }
7816 }
7817 }
7818 };
7819
7820 let pk_value_helper = primary_key.map(|(pk_ident, _)| {
7821 quote! {
7822 #[doc(hidden)]
7827 pub fn __rustango_pk_value(&self) -> #root::core::SqlValue {
7828 ::core::convert::Into::<#root::core::SqlValue>::into(
7829 ::core::clone::Clone::clone(&self.#pk_ident)
7830 )
7831 }
7832 }
7833 });
7834
7835 let has_pk_value_impl = primary_key.map(|(pk_ident, _)| {
7836 let column_arms = fields.column_entries.iter().map(|entry| {
7837 let col = &entry.column;
7838 let ident = &entry.ident;
7839 quote! {
7840 #col => ::core::option::Option::Some(
7841 ::core::convert::Into::<#root::core::SqlValue>::into(
7842 ::core::clone::Clone::clone(&self.#ident)
7843 )
7844 ),
7845 }
7846 });
7847 quote! {
7848 impl #root::sql::HasPkValue for #struct_name {
7849 fn __rustango_pk_value_impl(&self) -> #root::core::SqlValue {
7850 ::core::convert::Into::<#root::core::SqlValue>::into(
7851 ::core::clone::Clone::clone(&self.#pk_ident)
7852 )
7853 }
7854 fn __rustango_column_value(
7855 &self,
7856 column: &str,
7857 ) -> ::core::option::Option<#root::core::SqlValue> {
7858 match column {
7859 #( #column_arms )*
7860 _ => ::core::option::Option::None,
7861 }
7862 }
7863 }
7864 }
7865 });
7866
7867 let fk_pk_access_impl = fk_pk_access_impl_tokens(struct_name, &fields.fk_relations);
7868
7869 let assign_auto_pk_pool_impl = {
7875 let auto_assigns = &fields.auto_assigns;
7876 let auto_assigns_sqlite: Vec<TokenStream2> = fields
7882 .auto_field_idents
7883 .iter()
7884 .map(|(ident, column, uuid)| {
7885 if *uuid {
7887 return quote! {
7888 self.#ident = #root::sql::Auto::Set(#root::sql::try_get_flat_sqlite(
7889 _returning_row, #column
7890 )?);
7891 };
7892 }
7893 quote! {
7894 self.#ident = #root::sql::try_get_returning_sqlite(
7895 _returning_row, #column
7896 )?;
7897 }
7898 })
7899 .collect();
7900 let generated_assigns: Vec<TokenStream2> = fields
7904 .generated_field_idents
7905 .iter()
7906 .map(|(ident, column)| {
7907 quote! {
7908 self.#ident = #root::sql::try_get_returning(_returning_row, #column)?;
7909 }
7910 })
7911 .collect();
7912 let generated_assigns_sqlite: Vec<TokenStream2> = fields
7913 .generated_field_idents
7914 .iter()
7915 .map(|(ident, column)| {
7916 quote! {
7917 self.#ident = #root::sql::try_get_returning_sqlite(
7918 _returning_row, #column
7919 )?;
7920 }
7921 })
7922 .collect();
7923 let mysql_body = if let Some((first, value_ty)) = fields.first_db_auto.as_ref() {
7924 quote! {
7942 let _converted = <#value_ty as #root::sql::MysqlAutoIdSet>
7943 ::rustango_from_mysql_auto_id(_id)?;
7944 self.#first = #root::sql::Auto::Set(_converted);
7945 ::core::result::Result::Ok(())
7946 }
7947 } else {
7948 quote! {
7949 let _ = _id;
7950 ::core::result::Result::Ok(())
7951 }
7952 };
7953 quote! {
7954 impl #root::sql::AssignAutoPkPool for #struct_name {
7955 fn __rustango_assign_from_pg_row(
7956 &mut self,
7957 _returning_row: &#root::sql::PgReturningRow,
7958 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7959 #( #auto_assigns )*
7960 #( #generated_assigns )*
7961 ::core::result::Result::Ok(())
7962 }
7963 fn __rustango_assign_from_mysql_id(
7964 &mut self,
7965 _id: i64,
7966 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7967 #mysql_body
7968 }
7969 fn __rustango_assign_from_sqlite_row(
7970 &mut self,
7971 _returning_row: &#root::sql::SqliteReturningRow,
7972 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7973 #( #auto_assigns_sqlite )*
7974 #( #generated_assigns_sqlite )*
7975 ::core::result::Result::Ok(())
7976 }
7977 }
7978 }
7979 };
7980
7981 let from_aliased_row_inits = &fields.from_aliased_row_inits;
7982 let aliased_row_helper = quote! {
7983 #[doc(hidden)]
7989 #[cfg(feature = "postgres")]
7990 pub fn __rustango_from_aliased_row(
7991 row: &#root::sql::sqlx::postgres::PgRow,
7992 prefix: &str,
7993 ) -> ::core::result::Result<Self, #root::sql::sqlx::Error> {
7994 ::core::result::Result::Ok(Self {
7995 #( #from_aliased_row_inits ),*
7996 })
7997 }
7998 };
7999 let from_aliased_row_inits_my = &fields.from_aliased_row_inits_my;
8002 let aliased_row_helper_my = quote! {
8003 #root::__impl_my_aliased_row_decoder!(#struct_name, |row, prefix| {
8004 #( #from_aliased_row_inits_my ),*
8005 });
8006 };
8007
8008 let aliased_row_helper_sqlite = quote! {
8011 #root::__impl_sqlite_aliased_row_decoder!(#struct_name, |row, prefix| {
8012 #( #from_aliased_row_inits ),*
8013 });
8014 };
8015
8016 let load_related_impl = load_related_impl_tokens(struct_name, &fields.fk_relations);
8017 let load_related_impl_my = load_related_impl_my_tokens(struct_name, &fields.fk_relations);
8018 let load_related_impl_sqlite =
8019 load_related_impl_sqlite_tokens(struct_name, &fields.fk_relations);
8020
8021 let extra_manager_fns: Vec<TokenStream2> = manager_fns
8027 .iter()
8028 .map(|fn_ident| {
8029 let model_name_str = struct_name.to_string();
8030 let fn_name_str = fn_ident.to_string();
8031 let doc = format!(
8032 "Custom-named QuerySet accessor for [`{model_name_str}`]. \
8033 Generated by `#[rustango(manager_fn = \"{fn_name_str}\")]` — \
8034 equivalent to `Self::objects()`. Chains with any \
8035 `impl ... for QuerySet<{model_name_str}> {{ ... }}` \
8036 extension methods."
8037 );
8038 quote! {
8039 #[doc = #doc]
8040 #[must_use]
8041 pub fn #fn_ident() -> #root::query::QuerySet<#struct_name> {
8042 #root::query::QuerySet::new()
8043 }
8044 }
8045 })
8046 .collect();
8047
8048 quote! {
8049 impl #struct_name {
8050 #[must_use]
8052 pub fn objects() -> #root::query::QuerySet<#struct_name> {
8053 #root::query::QuerySet::new()
8054 }
8055
8056 #[must_use]
8069 pub fn query() -> #root::query::QuerySet<#struct_name> {
8070 #root::query::QuerySet::new()
8071 }
8072
8073 #( #extra_manager_fns )*
8074
8075 #insert_method
8076
8077 #bulk_insert_method
8078
8079 #bulk_upsert_pool_method
8080
8081 #bulk_update_method
8082
8083 #save_method
8084
8085 #pk_methods
8086
8087 #pk_value_helper
8088
8089 #aliased_row_helper
8090
8091 #column_consts
8092 }
8093
8094 #aliased_row_helper_my
8095
8096 #aliased_row_helper_sqlite
8097
8098 #load_related_impl
8099
8100 #load_related_impl_my
8101
8102 #load_related_impl_sqlite
8103
8104 #has_pk_value_impl
8105
8106 #fk_pk_access_impl
8107
8108 #assign_auto_pk_pool_impl
8109 }
8110}
8111
8112fn column_const_tokens(module_ident: &syn::Ident, entries: &[ColumnEntry]) -> TokenStream2 {
8114 let lines = entries.iter().map(|e| {
8115 let ident = &e.ident;
8116 let col_ty = column_type_ident(ident);
8117 let doc = format!("Typed column for the `{}` field.", e.name);
8119 quote! {
8120 #[doc = #doc]
8121 #[allow(non_upper_case_globals)]
8122 pub const #ident: #module_ident::#col_ty = #module_ident::#col_ty;
8123 }
8124 });
8125 quote! { #(#lines)* }
8126}
8127
8128fn column_module_tokens(
8131 module_ident: &syn::Ident,
8132 struct_name: &syn::Ident,
8133 entries: &[ColumnEntry],
8134) -> TokenStream2 {
8135 let root = rustango_root();
8136 let items = entries.iter().map(|e| {
8137 let col_ty = column_type_ident(&e.ident);
8138 let value_ty = &e.value_ty;
8139 let name = &e.name;
8140 let column = &e.column;
8141 let field_type_tokens = &e.field_type_tokens;
8142 quote! {
8143 #[derive(::core::clone::Clone, ::core::marker::Copy)]
8144 pub struct #col_ty;
8145
8146 impl #root::core::Column for #col_ty {
8147 type Model = super::#struct_name;
8148 type Value = #value_ty;
8149 const NAME: &'static str = #name;
8150 const COLUMN: &'static str = #column;
8151 const FIELD_TYPE: #root::core::FieldType = #field_type_tokens;
8152 }
8153 }
8154 });
8155 quote! {
8156 #[doc(hidden)]
8157 #[allow(non_camel_case_types, non_snake_case)]
8158 pub mod #module_ident {
8159 #[allow(unused_imports)]
8164 use super::*;
8165 #(#items)*
8166 }
8167 }
8168}
8169
8170fn column_type_ident(field_ident: &syn::Ident) -> syn::Ident {
8171 let name = ident_name(field_ident);
8172 syn::Ident::new(&format!("{name}_col"), field_ident.span())
8173}
8174
8175fn restore_auto_now_unless(
8178 idents: &[syn::Ident],
8179 ret: &TokenStream2,
8180 keep: &TokenStream2,
8181 body: &TokenStream2,
8182) -> TokenStream2 {
8183 if idents.is_empty() {
8184 return body.clone();
8185 }
8186 let prev: Vec<syn::Ident> = (0..idents.len())
8187 .map(|i| quote::format_ident!("__rustango_auto_now_prev_{}", i))
8188 .collect();
8189 quote! {
8190 #( let #prev = ::core::clone::Clone::clone(&self.#idents); )*
8191 let _saved: #ret = async { #body }.await;
8192 if !(#keep) {
8193 #( self.#idents = #prev; )*
8194 }
8195 _saved
8196 }
8197}
8198
8199fn ident_name(ident: &syn::Ident) -> String {
8201 syn::ext::IdentExt::unraw(ident).to_string()
8202}
8203
8204fn ident_from_name(name: &str, span: proc_macro2::Span) -> syn::Ident {
8206 match syn::parse_str::<syn::Ident>(name) {
8207 Ok(mut ident) => {
8208 ident.set_span(span);
8209 ident
8210 }
8211 Err(_) => syn::Ident::new_raw(name, span),
8212 }
8213}
8214
8215fn column_module_ident(struct_name: &syn::Ident) -> syn::Ident {
8216 syn::Ident::new(
8217 &format!("__rustango_cols_{struct_name}"),
8218 struct_name.span(),
8219 )
8220}
8221
8222fn from_row_impl_tokens(
8223 struct_name: &syn::Ident,
8224 from_row_inits: &[TokenStream2],
8225 from_row_inits_my: &[TokenStream2],
8226) -> TokenStream2 {
8227 let root = rustango_root();
8228 quote! {
8239 #[cfg(feature = "postgres")]
8240 impl<'r> #root::sql::sqlx::FromRow<'r, #root::sql::sqlx::postgres::PgRow>
8241 for #struct_name
8242 {
8243 fn from_row(
8244 row: &'r #root::sql::sqlx::postgres::PgRow,
8245 ) -> ::core::result::Result<Self, #root::sql::sqlx::Error> {
8246 ::core::result::Result::Ok(Self {
8247 #( #from_row_inits ),*
8248 })
8249 }
8250 }
8251
8252 #root::__impl_my_from_row!(#struct_name, |row| {
8253 #( #from_row_inits_my ),*
8254 });
8255
8256 #root::__impl_sqlite_from_row!(#struct_name, |row| {
8257 #( #from_row_inits ),*
8258 });
8259 }
8260}
8261
8262struct ContainerAttrs {
8263 table: Option<String>,
8264 display: Option<(String, proc_macro2::Span)>,
8265 app: Option<String>,
8272 admin: Option<AdminAttrs>,
8277 audit: Option<AuditAttrs>,
8283 permissions: bool,
8287 m2m: Vec<M2MAttr>,
8291 generic_m2m: Vec<GenericM2MAttr>,
8296 indexes: Vec<IndexAttr>,
8302 checks: Vec<CheckAttr>,
8305 excludes: Vec<ExcludeAttr>,
8311 composite_fks: Vec<CompositeFkAttr>,
8315 generic_fks: Vec<GenericFkAttr>,
8319 scope: Option<String>,
8325 access: Option<String>,
8328 manager_fns: Vec<syn::Ident>,
8333 default_order: Vec<(String, bool, proc_macro2::Span)>,
8339 is_view: bool,
8344 managed: bool,
8350 base_manager_name: Option<String>,
8354 order_with_respect_to: Option<String>,
8359 proxy: bool,
8365 required_db_features: Vec<String>,
8370 required_db_vendor: Option<String>,
8376 default_related_name: Option<String>,
8382 db_table_comment: Option<String>,
8387 get_latest_by: Option<(String, bool, proc_macro2::Span)>,
8393 extra_permissions: Vec<(String, String)>,
8398 default_permissions: Vec<String>,
8405 verbose_name: Option<String>,
8411 verbose_name_plural: Option<String>,
8415 global_scopes: Vec<GlobalScopeAttr>,
8421 through_relations: Vec<ThroughAttr>,
8430 reverse_has_relations: Vec<ReverseHasAttr>,
8439 generic_has_relations: Vec<GenericHasAttr>,
8444}
8445
8446struct GlobalScopeAttr {
8452 name: String,
8453 apply: syn::Path,
8454}
8455
8456struct ThroughAttr {
8486 name: String,
8488 far: syn::Ident,
8492 far_fk_column: String,
8498 intermediate: syn::Ident,
8502 intermediate_fk_column: String,
8506 intermediate_pk_column: String,
8511}
8512
8513struct ReverseHasAttr {
8552 name: String,
8555 child: syn::Ident,
8559 child_fk_column: String,
8563 self_pk_column: Option<String>,
8566 self_pk_span: Option<proc_macro2::Span>,
8568}
8569
8570impl ReverseHasAttr {
8571 fn self_pk_column(&self) -> &str {
8572 self.self_pk_column.as_deref().unwrap_or("id")
8573 }
8574}
8575
8576struct GenericHasAttr {
8582 name: String,
8584 child: syn::Ident,
8587 ct_column: String,
8590 pk_column: String,
8593 self_pk_column: Option<String>,
8596 self_pk_span: Option<proc_macro2::Span>,
8598}
8599
8600impl GenericHasAttr {
8601 fn self_pk_column(&self) -> &str {
8602 self.self_pk_column.as_deref().unwrap_or("id")
8603 }
8604}
8605
8606struct IndexAttr {
8608 span: proc_macro2::Span,
8610 name: Option<String>,
8612 columns: Vec<String>,
8614 unique: bool,
8616 method: String,
8622 where_clause: Option<String>,
8626 include: Vec<String>,
8629}
8630
8631struct CheckAttr {
8633 name: String,
8634 expr: String,
8635}
8636
8637struct ExcludeAttr {
8642 name: String,
8644 using: String,
8646 elements: Vec<(String, String)>,
8649 where_clause: Option<String>,
8651}
8652
8653struct CompositeFkAttr {
8659 name: String,
8661 to: String,
8663 from: Vec<String>,
8665 on: Vec<String>,
8667}
8668
8669struct GenericFkAttr {
8674 name: String,
8676 ct_column: String,
8678 pk_column: String,
8680}
8681
8682struct M2MAttr {
8684 name: String,
8686 to: String,
8688 through: String,
8690 src: String,
8692 dst: String,
8694 auto_create: bool,
8699}
8700
8701struct GenericM2MAttr {
8706 name: String,
8708 through: String,
8710 pk_column: String,
8712 ct_column: String,
8715 related_column: String,
8717}
8718
8719#[derive(Default)]
8725struct AuditAttrs {
8726 track: Option<(Vec<String>, proc_macro2::Span)>,
8730}
8731
8732#[derive(Default)]
8737struct AdminAttrs {
8738 list_display: Option<(Vec<String>, proc_macro2::Span)>,
8739 search_fields: Option<(Vec<String>, proc_macro2::Span)>,
8740 list_per_page: Option<usize>,
8741 ordering: Option<(Vec<(String, bool)>, proc_macro2::Span)>,
8742 readonly_fields: Option<(Vec<String>, proc_macro2::Span)>,
8743 list_filter: Option<(Vec<String>, proc_macro2::Span)>,
8744 actions: Option<(Vec<String>, proc_macro2::Span)>,
8747 fieldsets: Option<(Vec<(String, Vec<String>)>, proc_macro2::Span)>,
8751 list_display_links: Option<(Vec<String>, proc_macro2::Span)>,
8755 search_help_text: Option<String>,
8759 actions_on_top: Option<bool>,
8762 actions_on_bottom: Option<bool>,
8766 date_hierarchy: Option<String>,
8771 prepopulated_fields: Option<(Vec<(String, Vec<String>)>, proc_macro2::Span)>,
8777 raw_id_fields: Option<(Vec<String>, proc_macro2::Span)>,
8781 autocomplete_fields: Option<(Vec<String>, proc_macro2::Span)>,
8786 list_select_related: Option<String>,
8791 formfield_overrides: Option<(Vec<(String, String)>, proc_macro2::Span)>,
8797}
8798
8799#[derive(Clone, Copy, PartialEq, Eq)]
8802enum ModelAttrName {
8803 Rustango,
8804 Rgo,
8805}
8806
8807impl ModelAttrName {
8808 fn of(attr: &syn::Attribute) -> Option<Self> {
8809 let path = attr.path();
8810 if path.is_ident("rustango") {
8811 Some(Self::Rustango)
8812 } else if path.is_ident("rgo") {
8813 Some(Self::Rgo)
8814 } else {
8815 None
8816 }
8817 }
8818
8819 fn as_str(self) -> &'static str {
8820 match self {
8821 Self::Rustango => "rustango",
8822 Self::Rgo => "rgo",
8823 }
8824 }
8825}
8826
8827fn model_attrs(attrs: &[syn::Attribute]) -> syn::Result<Vec<&syn::Attribute>> {
8830 let mut first: Option<ModelAttrName> = None;
8831 let mut out = Vec::new();
8832 for attr in attrs {
8833 let Some(name) = ModelAttrName::of(attr) else {
8834 continue;
8835 };
8836 match first {
8837 Some(seen) if seen != name => {
8838 return Err(syn::Error::new_spanned(
8839 attr.path(),
8840 format!(
8841 "`#[{}(...)]` and `#[{}(...)]` are the same attribute; \
8842 use one name on an item or field",
8843 seen.as_str(),
8844 name.as_str(),
8845 ),
8846 ));
8847 }
8848 _ => first = Some(name),
8849 }
8850 out.push(attr);
8851 }
8852 Ok(out)
8853}
8854
8855fn parse_container_attrs(input: &DeriveInput) -> syn::Result<ContainerAttrs> {
8856 let mut out = ContainerAttrs {
8857 table: None,
8858 display: None,
8859 app: None,
8860 admin: None,
8861 audit: None,
8862 permissions: true,
8871 m2m: Vec::new(),
8872 generic_m2m: Vec::new(),
8873 indexes: Vec::new(),
8874 checks: Vec::new(),
8875 excludes: Vec::new(),
8876 composite_fks: Vec::new(),
8877 generic_fks: Vec::new(),
8878 scope: None,
8879 access: None,
8880 manager_fns: Vec::new(),
8881 default_order: Vec::new(),
8882 is_view: false,
8883 managed: true,
8884 verbose_name: None,
8885 verbose_name_plural: None,
8886 base_manager_name: None,
8887 order_with_respect_to: None,
8888 proxy: false,
8889 required_db_features: Vec::new(),
8890 required_db_vendor: None,
8891 default_related_name: None,
8892 db_table_comment: None,
8893 get_latest_by: None,
8894 extra_permissions: Vec::new(),
8895 default_permissions: Vec::new(),
8896 global_scopes: Vec::new(),
8897 through_relations: Vec::new(),
8898 reverse_has_relations: Vec::new(),
8899 generic_has_relations: Vec::new(),
8900 };
8901 for attr in model_attrs(&input.attrs)? {
8902 let written = ModelAttrName::of(attr).map_or("rustango", ModelAttrName::as_str);
8904 attr.parse_nested_meta(|meta| {
8905 if meta.path.is_ident("table") {
8906 let s: LitStr = meta.value()?.parse()?;
8907 let name = s.value();
8908 validate_table_name(&name, s.span())?;
8918 out.table = Some(name);
8919 return Ok(());
8920 }
8921 if meta.path.is_ident("display") {
8922 let s: LitStr = meta.value()?.parse()?;
8923 out.display = Some((s.value(), s.span()));
8924 return Ok(());
8925 }
8926 if meta.path.is_ident("app") {
8927 let s: LitStr = meta.value()?.parse()?;
8928 out.app = Some(s.value());
8929 return Ok(());
8930 }
8931 if meta.path.is_ident("scope") {
8932 let s: LitStr = meta.value()?.parse()?;
8933 let val = s.value();
8934 if !matches!(val.to_ascii_lowercase().as_str(), "registry" | "tenant") {
8935 return Err(meta.error(format!(
8936 "`scope` must be \"registry\" or \"tenant\", got {val:?}"
8937 )));
8938 }
8939 out.scope = Some(val);
8940 return Ok(());
8941 }
8942 if meta.path.is_ident("access") {
8943 let s: LitStr = meta.value()?.parse()?;
8944 let val = s.value();
8945 if !matches!(val.as_str(), "open" | "admin_only" | "never") {
8946 return Err(meta.error(format!(
8947 "`access` must be \"open\", \"admin_only\" or \"never\", got {val:?}"
8948 )));
8949 }
8950 out.access = Some(val);
8951 return Ok(());
8952 }
8953 if meta.path.is_ident("admin") {
8954 let mut admin = AdminAttrs::default();
8955 meta.parse_nested_meta(|inner| {
8956 if inner.path.is_ident("list_display") {
8957 let s: LitStr = inner.value()?.parse()?;
8958 admin.list_display =
8959 Some((split_field_list(&s.value()), s.span()));
8960 return Ok(());
8961 }
8962 if inner.path.is_ident("search_fields") {
8963 let s: LitStr = inner.value()?.parse()?;
8964 admin.search_fields =
8965 Some((split_field_list(&s.value()), s.span()));
8966 return Ok(());
8967 }
8968 if inner.path.is_ident("readonly_fields") {
8969 let s: LitStr = inner.value()?.parse()?;
8970 admin.readonly_fields =
8971 Some((split_field_list(&s.value()), s.span()));
8972 return Ok(());
8973 }
8974 if inner.path.is_ident("list_per_page") {
8975 let lit: syn::LitInt = inner.value()?.parse()?;
8976 admin.list_per_page = Some(lit.base10_parse::<usize>()?);
8977 return Ok(());
8978 }
8979 if inner.path.is_ident("ordering") {
8980 let s: LitStr = inner.value()?.parse()?;
8981 admin.ordering = Some((
8982 parse_ordering_list(&s.value()),
8983 s.span(),
8984 ));
8985 return Ok(());
8986 }
8987 if inner.path.is_ident("list_filter") {
8988 let s: LitStr = inner.value()?.parse()?;
8989 admin.list_filter =
8990 Some((split_field_list(&s.value()), s.span()));
8991 return Ok(());
8992 }
8993 if inner.path.is_ident("actions") {
8994 let s: LitStr = inner.value()?.parse()?;
8995 admin.actions =
8996 Some((split_field_list(&s.value()), s.span()));
8997 return Ok(());
8998 }
8999 if inner.path.is_ident("fieldsets") {
9000 let s: LitStr = inner.value()?.parse()?;
9001 admin.fieldsets =
9002 Some((parse_fieldset_list(&s.value()), s.span()));
9003 return Ok(());
9004 }
9005 if inner.path.is_ident("list_display_links") {
9006 let s: LitStr = inner.value()?.parse()?;
9007 admin.list_display_links =
9008 Some((split_field_list(&s.value()), s.span()));
9009 return Ok(());
9010 }
9011 if inner.path.is_ident("search_help_text") {
9012 let s: LitStr = inner.value()?.parse()?;
9013 admin.search_help_text = Some(s.value());
9014 return Ok(());
9015 }
9016 if inner.path.is_ident("actions_on_top") {
9017 let lit: syn::LitBool = inner.value()?.parse()?;
9018 admin.actions_on_top = Some(lit.value);
9019 return Ok(());
9020 }
9021 if inner.path.is_ident("actions_on_bottom") {
9022 let lit: syn::LitBool = inner.value()?.parse()?;
9023 admin.actions_on_bottom = Some(lit.value);
9024 return Ok(());
9025 }
9026 if inner.path.is_ident("date_hierarchy") {
9027 let s: LitStr = inner.value()?.parse()?;
9028 admin.date_hierarchy = Some(s.value());
9029 return Ok(());
9030 }
9031 if inner.path.is_ident("prepopulated_fields") {
9032 let s: LitStr = inner.value()?.parse()?;
9033 admin.prepopulated_fields =
9034 Some((parse_prepopulated_list(&s.value()), s.span()));
9035 return Ok(());
9036 }
9037 if inner.path.is_ident("raw_id_fields") {
9038 let s: LitStr = inner.value()?.parse()?;
9039 admin.raw_id_fields =
9040 Some((split_field_list(&s.value()), s.span()));
9041 return Ok(());
9042 }
9043 if inner.path.is_ident("autocomplete_fields") {
9044 let s: LitStr = inner.value()?.parse()?;
9045 admin.autocomplete_fields =
9046 Some((split_field_list(&s.value()), s.span()));
9047 return Ok(());
9048 }
9049 if inner.path.is_ident("list_select_related") {
9050 let s: LitStr = inner.value()?.parse()?;
9051 admin.list_select_related = Some(s.value());
9052 return Ok(());
9053 }
9054 if inner.path.is_ident("formfield_overrides") {
9055 let s: LitStr = inner.value()?.parse()?;
9056 admin.formfield_overrides =
9057 Some((parse_formfield_overrides(&s.value()), s.span()));
9058 return Ok(());
9059 }
9060 Err(inner.error(
9061 "unknown admin attribute (supported: \
9062 `list_display`, `list_display_links`, \
9063 `search_fields`, `search_help_text`, \
9064 `readonly_fields`, \
9065 `list_filter`, `list_per_page`, `ordering`, `actions`, \
9066 `actions_on_top`, `actions_on_bottom`, \
9067 `date_hierarchy`, \
9068 `prepopulated_fields`, \
9069 `raw_id_fields`, \
9070 `autocomplete_fields`, \
9071 `list_select_related`, \
9072 `formfield_overrides`, \
9073 `fieldsets`)",
9074 ))
9075 })?;
9076 out.admin = Some(admin);
9077 return Ok(());
9078 }
9079 if meta.path.is_ident("manager") {
9080 return Err(meta.error(
9082 "`manager(ext = ...)` was removed (#2132): declare your own \
9083 `trait FooManagerExt: Sized { … }` and implement it for `QuerySet<Foo>`",
9084 ));
9085 }
9086 if meta.path.is_ident("manager_fn") {
9087 let s: LitStr = meta.value()?.parse()?;
9092 let name = s.value();
9093 if name.is_empty() {
9094 return Err(meta.error("`manager_fn = \"...\"` cannot be empty"));
9095 }
9096 if name == "objects" {
9097 return Err(meta.error(
9098 "`manager_fn = \"objects\"` collides with the default \
9099 accessor — pick a different name",
9100 ));
9101 }
9102 let ident = syn::Ident::new(&name, s.span());
9103 if out.manager_fns.iter().any(|prev| *prev == ident) {
9104 return Err(meta.error(format!(
9105 "duplicate `manager_fn = \"{name}\"`"
9106 )));
9107 }
9108 out.manager_fns.push(ident);
9109 return Ok(());
9110 }
9111 if meta.path.is_ident("default_order") {
9112 let s: LitStr = meta.value()?.parse()?;
9117 let raw = s.value();
9118 let span = s.span();
9119 let mut parsed: Vec<(String, bool, proc_macro2::Span)> =
9120 Vec::new();
9121 for entry in raw.split(',') {
9122 let trimmed = entry.trim();
9123 if trimmed.is_empty() {
9124 return Err(syn::Error::new(
9125 span,
9126 "`default_order = \"...\"` has an empty entry — \
9127 check for a stray comma",
9128 ));
9129 }
9130 let (desc, name) = if let Some(rest) = trimmed.strip_prefix('-') {
9131 (true, rest.trim().to_owned())
9132 } else if let Some(rest) = trimmed.strip_prefix('+') {
9133 (false, rest.trim().to_owned())
9134 } else {
9135 (false, trimmed.to_owned())
9136 };
9137 if name.is_empty() {
9138 return Err(syn::Error::new(
9139 span,
9140 "`default_order` entry has no column name after the prefix",
9141 ));
9142 }
9143 if parsed.iter().any(|(n, _, _)| *n == name) {
9144 return Err(syn::Error::new(
9145 span,
9146 format!("duplicate column `{name}` in `default_order`"),
9147 ));
9148 }
9149 parsed.push((name, desc, span));
9150 }
9151 if parsed.is_empty() {
9152 return Err(syn::Error::new(
9153 span,
9154 "`default_order = \"...\"` cannot be empty",
9155 ));
9156 }
9157 out.default_order = parsed;
9158 return Ok(());
9159 }
9160 if meta.path.is_ident("global_scope") {
9161 let span = meta.path.span();
9167 let mut scope_name: Option<String> = None;
9168 let mut apply_path: Option<syn::Path> = None;
9169 meta.parse_nested_meta(|inner| {
9170 if inner.path.is_ident("name") {
9171 let s: LitStr = inner.value()?.parse()?;
9172 let raw = s.value();
9173 if raw.trim().is_empty() {
9174 return Err(syn::Error::new(
9175 s.span(),
9176 "`global_scope(name = \"...\")` must not be empty",
9177 ));
9178 }
9179 scope_name = Some(raw);
9180 return Ok(());
9181 }
9182 if inner.path.is_ident("apply") {
9183 let p: syn::Path = inner.value()?.parse()?;
9184 apply_path = Some(p);
9185 return Ok(());
9186 }
9187 Err(inner.error(
9188 "unknown `global_scope` attribute (supported: \
9189 `name`, `apply`)",
9190 ))
9191 })?;
9192 let Some(name) = scope_name else {
9193 return Err(syn::Error::new(
9194 span,
9195 "`global_scope` requires `name = \"...\"`",
9196 ));
9197 };
9198 let Some(apply) = apply_path else {
9199 return Err(syn::Error::new(
9200 span,
9201 "`global_scope` requires `apply = fn_path`",
9202 ));
9203 };
9204 if out.global_scopes.iter().any(|s| s.name == name) {
9205 return Err(syn::Error::new(
9206 span,
9207 format!(
9208 "duplicate global scope name `{name}` — \
9209 pick a unique identifier so \
9210 `QuerySet::without_global_scope(\"{name}\")` \
9211 is unambiguous"
9212 ),
9213 ));
9214 }
9215 out.global_scopes.push(GlobalScopeAttr { name, apply });
9216 return Ok(());
9217 }
9218 if meta.path.is_ident("through") {
9219 let span = meta.path.span();
9224 let mut accessor_name: Option<String> = None;
9225 let mut far_ident: Option<syn::Ident> = None;
9226 let mut far_fk_column: Option<String> = None;
9227 let mut intermediate_ident: Option<syn::Ident> = None;
9228 let mut intermediate_fk_column: Option<String> = None;
9229 let mut intermediate_pk_column: Option<String> = None;
9230 fn parse_nonempty_string(
9231 inner: &syn::meta::ParseNestedMeta<'_>,
9232 field: &str,
9233 ) -> syn::Result<String> {
9234 let s: LitStr = inner.value()?.parse()?;
9235 let raw = s.value();
9236 let trimmed = raw.trim();
9237 if trimmed.is_empty() {
9238 return Err(syn::Error::new(
9239 s.span(),
9240 format!("`through({field} = \"...\")` must not be empty"),
9241 ));
9242 }
9243 Ok(trimmed.to_owned())
9244 }
9245 meta.parse_nested_meta(|inner| {
9246 if inner.path.is_ident("name") {
9247 accessor_name = Some(parse_nonempty_string(&inner, "name")?);
9248 return Ok(());
9249 }
9250 if inner.path.is_ident("far") {
9251 let s: LitStr = inner.value()?.parse()?;
9257 let raw = s.value();
9258 let trimmed = raw.trim();
9259 if trimmed.is_empty() {
9260 return Err(syn::Error::new(
9261 s.span(),
9262 "`through(far = \"...\")` must not be empty",
9263 ));
9264 }
9265 far_ident = Some(syn::Ident::new(trimmed, s.span()));
9266 return Ok(());
9267 }
9268 if inner.path.is_ident("far_fk_column") {
9269 far_fk_column =
9270 Some(parse_nonempty_string(&inner, "far_fk_column")?);
9271 return Ok(());
9272 }
9273 if inner.path.is_ident("intermediate") {
9274 let s: LitStr = inner.value()?.parse()?;
9275 let raw = s.value();
9276 let trimmed = raw.trim();
9277 if trimmed.is_empty() {
9278 return Err(syn::Error::new(
9279 s.span(),
9280 "`through(intermediate = \"...\")` must not be empty",
9281 ));
9282 }
9283 intermediate_ident = Some(syn::Ident::new(trimmed, s.span()));
9284 return Ok(());
9285 }
9286 if inner.path.is_ident("intermediate_fk_column") {
9287 intermediate_fk_column =
9288 Some(parse_nonempty_string(&inner, "intermediate_fk_column")?);
9289 return Ok(());
9290 }
9291 if inner.path.is_ident("intermediate_pk_column") {
9292 intermediate_pk_column =
9293 Some(parse_nonempty_string(&inner, "intermediate_pk_column")?);
9294 return Ok(());
9295 }
9296 Err(inner.error(
9297 "unknown `through` attribute (supported: \
9298 `name`, `far`, `far_fk_column`, \
9299 `intermediate`, `intermediate_fk_column`, \
9300 `intermediate_pk_column`)",
9301 ))
9302 })?;
9303 let Some(name) = accessor_name else {
9304 return Err(syn::Error::new(
9305 span,
9306 "`through` requires `name = \"...\"`",
9307 ));
9308 };
9309 let Some(far) = far_ident else {
9310 return Err(syn::Error::new(
9311 span,
9312 "`through` requires `far = \"FarModelType\"`",
9313 ));
9314 };
9315 let Some(far_fk_column) = far_fk_column else {
9316 return Err(syn::Error::new(
9317 span,
9318 "`through` requires `far_fk_column = \"<column>\"`",
9319 ));
9320 };
9321 let Some(intermediate) = intermediate_ident else {
9322 return Err(syn::Error::new(
9323 span,
9324 "`through` requires `intermediate = \"IntermediateModelType\"`",
9325 ));
9326 };
9327 let Some(intermediate_fk_column) = intermediate_fk_column else {
9328 return Err(syn::Error::new(
9329 span,
9330 "`through` requires `intermediate_fk_column = \"<column>\"`",
9331 ));
9332 };
9333 let intermediate_pk_column =
9334 intermediate_pk_column.unwrap_or_else(|| "id".to_owned());
9335 if out.through_relations.iter().any(|t| t.name == name) {
9336 return Err(syn::Error::new(
9337 span,
9338 format!(
9339 "duplicate `through(name = \"{name}\")` — \
9340 pick a unique accessor name"
9341 ),
9342 ));
9343 }
9344 out.through_relations.push(ThroughAttr {
9345 name,
9346 far,
9347 far_fk_column,
9348 intermediate,
9349 intermediate_fk_column,
9350 intermediate_pk_column,
9351 });
9352 return Ok(());
9353 }
9354 if meta.path.is_ident("reverse_has") {
9355 let span = meta.path.span();
9359 let mut accessor_name: Option<String> = None;
9360 let mut child_ident: Option<syn::Ident> = None;
9361 let mut child_fk_column: Option<String> = None;
9362 let mut self_pk_column: Option<String> = None;
9363 let mut self_pk_span: Option<proc_macro2::Span> = None;
9364 meta.parse_nested_meta(|inner| {
9365 if inner.path.is_ident("name") {
9366 let s: LitStr = inner.value()?.parse()?;
9367 let raw = s.value();
9368 if raw.trim().is_empty() {
9369 return Err(syn::Error::new(
9370 s.span(),
9371 "`reverse_has(name = \"...\")` must not be empty",
9372 ));
9373 }
9374 accessor_name = Some(raw);
9375 return Ok(());
9376 }
9377 if inner.path.is_ident("child") {
9378 let s: LitStr = inner.value()?.parse()?;
9379 let raw = s.value();
9380 let trimmed = raw.trim();
9381 if trimmed.is_empty() {
9382 return Err(syn::Error::new(
9383 s.span(),
9384 "`reverse_has(child = \"...\")` must not be empty",
9385 ));
9386 }
9387 child_ident = Some(syn::Ident::new(trimmed, s.span()));
9388 return Ok(());
9389 }
9390 if inner.path.is_ident("child_fk_column") {
9391 let s: LitStr = inner.value()?.parse()?;
9392 let raw = s.value();
9393 let trimmed = raw.trim();
9394 if trimmed.is_empty() {
9395 return Err(syn::Error::new(
9396 s.span(),
9397 "`reverse_has(child_fk_column = \"...\")` must not be empty",
9398 ));
9399 }
9400 child_fk_column = Some(trimmed.to_owned());
9401 return Ok(());
9402 }
9403 if inner.path.is_ident("self_pk_column") {
9404 let s: LitStr = inner.value()?.parse()?;
9405 let raw = s.value();
9406 let trimmed = raw.trim();
9407 if trimmed.is_empty() {
9408 return Err(syn::Error::new(
9409 s.span(),
9410 "`reverse_has(self_pk_column = \"...\")` must not be empty",
9411 ));
9412 }
9413 self_pk_column = Some(trimmed.to_owned());
9414 self_pk_span = Some(s.span());
9415 return Ok(());
9416 }
9417 Err(inner.error(
9418 "unknown `reverse_has` attribute (supported: \
9419 `name`, `child`, `child_fk_column`, \
9420 `self_pk_column`)",
9421 ))
9422 })?;
9423 let Some(name) = accessor_name else {
9424 return Err(syn::Error::new(
9425 span,
9426 "`reverse_has` requires `name = \"...\"`",
9427 ));
9428 };
9429 let Some(child) = child_ident else {
9430 return Err(syn::Error::new(
9431 span,
9432 "`reverse_has` requires `child = \"ChildModelType\"`",
9433 ));
9434 };
9435 let Some(child_fk_column) = child_fk_column else {
9436 return Err(syn::Error::new(
9437 span,
9438 "`reverse_has` requires `child_fk_column = \"<column>\"`",
9439 ));
9440 };
9441 if out.reverse_has_relations.iter().any(|r| r.name == name) {
9442 return Err(syn::Error::new(
9443 span,
9444 format!(
9445 "duplicate `reverse_has(name = \"{name}\")` — \
9446 pick a unique accessor name"
9447 ),
9448 ));
9449 }
9450 out.reverse_has_relations.push(ReverseHasAttr {
9451 name,
9452 child,
9453 child_fk_column,
9454 self_pk_column,
9455 self_pk_span,
9456 });
9457 return Ok(());
9458 }
9459 if meta.path.is_ident("generic_has") {
9460 let span = meta.path.span();
9466 let mut accessor_name: Option<String> = None;
9467 let mut child_ident: Option<syn::Ident> = None;
9468 let mut ct_column: Option<String> = None;
9469 let mut pk_column: Option<String> = None;
9470 let mut self_pk_column: Option<String> = None;
9471 let mut self_pk_span: Option<proc_macro2::Span> = None;
9472 meta.parse_nested_meta(|inner| {
9473 if inner.path.is_ident("name") {
9474 let s: LitStr = inner.value()?.parse()?;
9475 let raw = s.value();
9476 if raw.trim().is_empty() {
9477 return Err(syn::Error::new(
9478 s.span(),
9479 "`generic_has(name = \"...\")` must not be empty",
9480 ));
9481 }
9482 accessor_name = Some(raw);
9483 return Ok(());
9484 }
9485 if inner.path.is_ident("child") {
9486 let s: LitStr = inner.value()?.parse()?;
9487 let trimmed = s.value().trim().to_owned();
9488 if trimmed.is_empty() {
9489 return Err(syn::Error::new(
9490 s.span(),
9491 "`generic_has(child = \"...\")` must not be empty",
9492 ));
9493 }
9494 child_ident = Some(syn::Ident::new(&trimmed, s.span()));
9495 return Ok(());
9496 }
9497 if inner.path.is_ident("ct_column") {
9498 let s: LitStr = inner.value()?.parse()?;
9499 let trimmed = s.value().trim().to_owned();
9500 if trimmed.is_empty() {
9501 return Err(syn::Error::new(
9502 s.span(),
9503 "`generic_has(ct_column = \"...\")` must not be empty",
9504 ));
9505 }
9506 ct_column = Some(trimmed);
9507 return Ok(());
9508 }
9509 if inner.path.is_ident("pk_column") {
9510 let s: LitStr = inner.value()?.parse()?;
9511 let trimmed = s.value().trim().to_owned();
9512 if trimmed.is_empty() {
9513 return Err(syn::Error::new(
9514 s.span(),
9515 "`generic_has(pk_column = \"...\")` must not be empty",
9516 ));
9517 }
9518 pk_column = Some(trimmed);
9519 return Ok(());
9520 }
9521 if inner.path.is_ident("self_pk_column") {
9522 let s: LitStr = inner.value()?.parse()?;
9523 let trimmed = s.value().trim().to_owned();
9524 if trimmed.is_empty() {
9525 return Err(syn::Error::new(
9526 s.span(),
9527 "`generic_has(self_pk_column = \"...\")` must not be empty",
9528 ));
9529 }
9530 self_pk_column = Some(trimmed);
9531 self_pk_span = Some(s.span());
9532 return Ok(());
9533 }
9534 Err(inner.error(
9535 "unknown `generic_has` attribute (supported: \
9536 `name`, `child`, `ct_column`, `pk_column`, \
9537 `self_pk_column`)",
9538 ))
9539 })?;
9540 let Some(name) = accessor_name else {
9541 return Err(syn::Error::new(
9542 span,
9543 "`generic_has` requires `name = \"...\"`",
9544 ));
9545 };
9546 let Some(child) = child_ident else {
9547 return Err(syn::Error::new(
9548 span,
9549 "`generic_has` requires `child = \"ChildModelType\"`",
9550 ));
9551 };
9552 let ct_column = ct_column.unwrap_or_else(|| "content_type_id".to_owned());
9553 let pk_column = pk_column.unwrap_or_else(|| "object_pk".to_owned());
9554 if out.generic_has_relations.iter().any(|r| r.name == name) {
9555 return Err(syn::Error::new(
9556 span,
9557 format!(
9558 "duplicate `generic_has(name = \"{name}\")` — \
9559 pick a unique accessor name"
9560 ),
9561 ));
9562 }
9563 out.generic_has_relations.push(GenericHasAttr {
9564 name,
9565 child,
9566 ct_column,
9567 pk_column,
9568 self_pk_column,
9569 self_pk_span,
9570 });
9571 return Ok(());
9572 }
9573 if meta.path.is_ident("audit") {
9574 let mut audit = AuditAttrs::default();
9575 meta.parse_nested_meta(|inner| {
9576 if inner.path.is_ident("track") {
9577 let s: LitStr = inner.value()?.parse()?;
9578 audit.track =
9579 Some((split_field_list(&s.value()), s.span()));
9580 return Ok(());
9581 }
9582 Err(inner.error(
9583 "unknown audit attribute (supported: `track`)",
9584 ))
9585 })?;
9586 out.audit = Some(audit);
9587 return Ok(());
9588 }
9589 if meta.path.is_ident("permissions") {
9590 if let Ok(v) = meta.value() {
9595 let lit: syn::LitBool = v.parse()?;
9596 out.permissions = lit.value;
9597 } else {
9598 out.permissions = true;
9599 }
9600 return Ok(());
9601 }
9602 if meta.path.is_ident("view") {
9603 if let Ok(v) = meta.value() {
9609 let lit: syn::LitBool = v.parse()?;
9610 out.is_view = lit.value;
9611 } else {
9612 out.is_view = true;
9613 }
9614 return Ok(());
9615 }
9616 if meta.path.is_ident("managed") {
9617 let v = meta.value()?;
9625 let lit: syn::LitBool = v.parse()?;
9626 out.managed = lit.value;
9627 return Ok(());
9628 }
9629 if meta.path.is_ident("verbose_name") {
9630 let s: LitStr = meta.value()?.parse()?;
9631 out.verbose_name = Some(s.value());
9632 return Ok(());
9633 }
9634 if meta.path.is_ident("verbose_name_plural") {
9635 let s: LitStr = meta.value()?.parse()?;
9636 out.verbose_name_plural = Some(s.value());
9637 return Ok(());
9638 }
9639 if meta.path.is_ident("db_table_comment") {
9640 let s: LitStr = meta.value()?.parse()?;
9643 out.db_table_comment = Some(s.value());
9644 return Ok(());
9645 }
9646 if meta.path.is_ident("proxy") {
9647 let value = if meta.input.peek(syn::Token![=]) {
9656 meta.value()?.parse::<syn::LitBool>()?.value
9657 } else {
9658 true
9659 };
9660 out.proxy = value;
9661 return Ok(());
9662 }
9663 if meta.path.is_ident("order_with_respect_to") {
9664 let s: LitStr = meta.value()?.parse()?;
9678 let raw = s.value();
9679 if raw.is_empty() {
9680 return Err(syn::Error::new(
9681 s.span(),
9682 "`order_with_respect_to` must be a non-empty FK field name",
9683 ));
9684 }
9685 let valid = raw
9686 .chars()
9687 .all(|c| c == '_' || c.is_ascii_alphanumeric())
9688 && !raw.chars().next().is_some_and(|c| c.is_ascii_digit());
9689 if !valid {
9690 return Err(syn::Error::new(
9691 s.span(),
9692 format!(
9693 "`order_with_respect_to` must be a valid Rust \
9694 identifier (letters / digits / underscores, \
9695 not starting with a digit); got `{raw}`"
9696 ),
9697 ));
9698 }
9699 out.order_with_respect_to = Some(raw);
9700 return Ok(());
9701 }
9702 if meta.path.is_ident("required_db_features") {
9703 let s: LitStr = meta.value()?.parse()?;
9718 let raw = s.value();
9719 let features: Vec<String> = raw
9720 .split(',')
9721 .map(str::trim)
9722 .filter(|s| !s.is_empty())
9723 .map(str::to_owned)
9724 .collect();
9725 if features.is_empty() {
9726 return Err(syn::Error::new(
9727 s.span(),
9728 "`required_db_features` must list at least one \
9729 comma-separated capability token",
9730 ));
9731 }
9732 out.required_db_features = features;
9733 return Ok(());
9734 }
9735 if meta.path.is_ident("required_db_vendor") {
9736 let s: LitStr = meta.value()?.parse()?;
9744 let raw = s.value().to_ascii_lowercase();
9745 match raw.as_str() {
9746 "postgresql" | "postgres" | "pg" => {
9747 out.required_db_vendor = Some("postgres".to_owned());
9748 }
9749 "mysql" | "mariadb" => {
9750 out.required_db_vendor = Some("mysql".to_owned());
9751 }
9752 "sqlite" | "sqlite3" => {
9753 out.required_db_vendor = Some("sqlite".to_owned());
9754 }
9755 _ => {
9756 return Err(syn::Error::new(
9757 s.span(),
9758 format!(
9759 "unknown required_db_vendor `{raw}` — \
9760 expected `postgres` (aliases: `postgresql`, `pg`), \
9761 `mysql` (alias: `mariadb`), or `sqlite` \
9762 (alias: `sqlite3`)"
9763 ),
9764 ));
9765 }
9766 }
9767 return Ok(());
9768 }
9769 if meta.path.is_ident("base_manager_name") {
9770 let s: LitStr = meta.value()?.parse()?;
9779 let raw = s.value();
9780 if raw.is_empty() {
9781 return Err(syn::Error::new(
9782 s.span(),
9783 "`base_manager_name` must be a non-empty string",
9784 ));
9785 }
9786 let valid = raw
9787 .chars()
9788 .all(|c| c == '_' || c.is_ascii_alphanumeric())
9789 && !raw.chars().next().is_some_and(|c| c.is_ascii_digit());
9790 if !valid {
9791 return Err(syn::Error::new(
9792 s.span(),
9793 format!(
9794 "`base_manager_name` must be a valid Rust \
9795 identifier (letters / digits / underscores, \
9796 not starting with a digit); got `{raw}`"
9797 ),
9798 ));
9799 }
9800 out.base_manager_name = Some(raw);
9801 return Ok(());
9802 }
9803 if meta.path.is_ident("default_related_name") {
9804 let s: LitStr = meta.value()?.parse()?;
9815 let raw = s.value();
9816 if raw.is_empty() {
9817 return Err(syn::Error::new(
9818 s.span(),
9819 "`default_related_name` must be a non-empty string",
9820 ));
9821 }
9822 let valid = raw
9823 .chars()
9824 .all(|c| c == '_' || c.is_ascii_lowercase() || c.is_ascii_digit())
9825 && !raw.chars().next().is_some_and(|c| c.is_ascii_digit());
9826 if !valid {
9827 return Err(syn::Error::new(
9828 s.span(),
9829 format!(
9830 "`default_related_name` must be snake_case ASCII \
9831 (lowercase letters / digits / underscores, not \
9832 starting with a digit); got `{raw}`"
9833 ),
9834 ));
9835 }
9836 out.default_related_name = Some(raw);
9837 return Ok(());
9838 }
9839 if meta.path.is_ident("extra_permissions") {
9840 let s: LitStr = meta.value()?.parse()?;
9842 let raw = s.value();
9843 let mut pairs = Vec::new();
9844 for entry in raw.split(',') {
9845 let entry = entry.trim();
9846 if entry.is_empty() {
9847 continue;
9848 }
9849 let (codename, label) = match entry.split_once(':') {
9850 Some((c, l)) => (c.trim().to_owned(), l.trim().to_owned()),
9851 None => (entry.to_owned(), entry.to_owned()),
9852 };
9853 if codename.is_empty() {
9854 return Err(meta.error(
9855 "`extra_permissions` entries must be `codename:label` pairs",
9856 ));
9857 }
9858 pairs.push((codename, label));
9859 }
9860 if pairs.is_empty() {
9861 return Err(meta
9862 .error("`extra_permissions = \"…\"` must list at least one pair"));
9863 }
9864 out.extra_permissions = pairs;
9865 return Ok(());
9866 }
9867 if meta.path.is_ident("default_permissions") {
9868 let s: LitStr = meta.value()?.parse()?;
9871 let raw = s.value();
9872 let mut actions: Vec<String> = Vec::new();
9873 for entry in raw.split(',') {
9874 let action = entry.trim().to_ascii_lowercase();
9875 if action.is_empty() {
9876 continue;
9877 }
9878 match action.as_str() {
9879 "add" | "change" | "delete" | "view" => {}
9880 other => {
9881 return Err(syn::Error::new(
9882 s.span(),
9883 format!(
9884 "unknown default_permissions action `{other}` — \
9885 expected one of `add`, `change`, `delete`, `view`"
9886 ),
9887 ));
9888 }
9889 }
9890 if !actions.contains(&action) {
9891 actions.push(action);
9892 }
9893 }
9894 if actions.is_empty() {
9895 return Err(syn::Error::new(
9896 s.span(),
9897 "`default_permissions = \"…\"` must list at least one action; \
9898 use `permissions = false` on the container if you want NO \
9899 permissions seeded for this model.",
9900 ));
9901 }
9902 out.default_permissions = actions;
9903 return Ok(());
9904 }
9905 if meta.path.is_ident("get_latest_by") {
9906 let s: LitStr = meta.value()?.parse()?;
9908 let raw = s.value();
9909 let trimmed = raw.trim();
9910 if trimmed.is_empty() {
9911 return Err(meta.error("`get_latest_by` must name a column"));
9912 }
9913 let (col, desc) = if let Some(stripped) = trimmed.strip_prefix('-') {
9914 (stripped.to_owned(), true)
9915 } else if let Some(stripped) = trimmed.strip_prefix('+') {
9916 (stripped.to_owned(), false)
9917 } else {
9918 (trimmed.to_owned(), false)
9919 };
9920 if col.is_empty() {
9921 return Err(meta.error("`get_latest_by` must name a column"));
9922 }
9923 out.get_latest_by = Some((col, desc, s.span()));
9924 return Ok(());
9925 }
9926 if meta.path.is_ident("unique_together") {
9927 let (columns, name) = parse_together_attr(&meta, "unique_together", written)?;
9936 out.indexes.push(IndexAttr {
9937 span: meta.path.span(),
9938 name,
9939 columns,
9940 unique: true,
9941 method: "btree".to_owned(),
9942 where_clause: None,
9943 include: Vec::new(),
9944 });
9945 return Ok(());
9946 }
9947 if meta.path.is_ident("index_together") {
9948 let (columns, name) = parse_together_attr(&meta, "index_together", written)?;
9954 out.indexes.push(IndexAttr {
9955 span: meta.path.span(),
9956 name,
9957 columns,
9958 unique: false,
9959 method: "btree".to_owned(),
9960 where_clause: None,
9961 include: Vec::new(),
9962 });
9963 return Ok(());
9964 }
9965 if meta.path.is_ident("unique_when") {
9966 let mut columns: Option<Vec<String>> = None;
9980 let mut condition: Option<String> = None;
9981 let mut name: Option<String> = None;
9982 let mut include: Vec<String> = Vec::new();
9983 meta.parse_nested_meta(|inner| {
9984 if inner.path.is_ident("columns") {
9985 let s: LitStr = inner.value()?.parse()?;
9986 columns = Some(split_field_list(&s.value()));
9987 return Ok(());
9988 }
9989 if inner.path.is_ident("condition") {
9990 let s: LitStr = inner.value()?.parse()?;
9991 condition = Some(s.value());
9992 return Ok(());
9993 }
9994 if inner.path.is_ident("name") {
9995 let s: LitStr = inner.value()?.parse()?;
9996 name = Some(s.value());
9997 return Ok(());
9998 }
9999 if inner.path.is_ident("include") {
10000 let s: LitStr = inner.value()?.parse()?;
10004 include = split_field_list(&s.value());
10005 return Ok(());
10006 }
10007 Err(inner.error(
10008 "unknown unique_when attribute (supported: \
10009 `columns = \"...\"`, `condition = \"...\"`, \
10010 `name = \"...\"`, `include = \"...\"`)",
10011 ))
10012 })?;
10013 let columns = columns.ok_or_else(|| {
10014 meta.error("`unique_when(...)` requires `columns = \"...\"`")
10015 })?;
10016 let condition = condition.ok_or_else(|| {
10017 meta.error("`unique_when(...)` requires `condition = \"...\"`")
10018 })?;
10019 if columns.is_empty() {
10020 return Err(meta.error("`unique_when(columns = \"\")` is empty"));
10021 }
10022 out.indexes.push(IndexAttr {
10023 span: meta.path.span(),
10024 name,
10025 columns,
10026 unique: true,
10027 method: "btree".to_owned(),
10028 where_clause: Some(condition),
10029 include,
10030 });
10031 return Ok(());
10032 }
10033 if meta.path.is_ident("index_when") {
10034 let mut columns: Option<Vec<String>> = None;
10051 let mut condition: Option<String> = None;
10052 let mut name: Option<String> = None;
10053 let mut method: String = "btree".to_owned();
10054 let mut include: Vec<String> = Vec::new();
10055 meta.parse_nested_meta(|inner| {
10056 if inner.path.is_ident("columns") {
10057 let s: LitStr = inner.value()?.parse()?;
10058 columns = Some(split_field_list(&s.value()));
10059 return Ok(());
10060 }
10061 if inner.path.is_ident("condition") {
10062 let s: LitStr = inner.value()?.parse()?;
10063 condition = Some(s.value());
10064 return Ok(());
10065 }
10066 if inner.path.is_ident("name") {
10067 let s: LitStr = inner.value()?.parse()?;
10068 name = Some(s.value());
10069 return Ok(());
10070 }
10071 if inner.path.is_ident("method") {
10072 let s: LitStr = inner.value()?.parse()?;
10073 method = s.value();
10074 return Ok(());
10075 }
10076 if inner.path.is_ident("include") {
10077 let s: LitStr = inner.value()?.parse()?;
10081 include = split_field_list(&s.value());
10082 return Ok(());
10083 }
10084 Err(inner.error(
10085 "unknown index_when attribute (supported: \
10086 `columns = \"...\"`, `condition = \"...\"`, \
10087 `name = \"...\"`, `method = \"btree|gin|gist|...\"`, \
10088 `include = \"...\"`)",
10089 ))
10090 })?;
10091 let columns = columns
10092 .ok_or_else(|| meta.error("`index_when(...)` requires `columns = \"...\"`"))?;
10093 let condition = condition.ok_or_else(|| {
10094 meta.error("`index_when(...)` requires `condition = \"...\"`")
10095 })?;
10096 if columns.is_empty() {
10097 return Err(meta.error("`index_when(columns = \"\")` is empty"));
10098 }
10099 out.indexes.push(IndexAttr {
10100 span: meta.path.span(),
10101 name,
10102 columns,
10103 unique: false,
10104 method,
10105 where_clause: Some(condition),
10106 include,
10107 });
10108 return Ok(());
10109 }
10110 if meta.path.is_ident("index") {
10111 let cols_lit: LitStr;
10122 let mut unique = false;
10123 let mut name: Option<String> = None;
10124 let mut method = "btree".to_owned();
10125 if meta.input.peek(syn::token::Paren) {
10126 let content;
10127 syn::parenthesized!(content in meta.input);
10128 cols_lit = content.parse()?;
10129 while content.peek(syn::Token![,]) {
10130 content.parse::<syn::Token![,]>()?;
10131 if content.is_empty() {
10132 break;
10133 }
10134 let flag: syn::Ident = content.parse()?;
10135 if flag == "unique" {
10136 unique = true;
10137 } else if flag == "name" {
10138 content.parse::<syn::Token![=]>()?;
10139 let s: LitStr = content.parse()?;
10140 name = Some(s.value());
10141 } else if flag == "method" {
10142 content.parse::<syn::Token![=]>()?;
10143 let s: LitStr = content.parse()?;
10144 let v = s.value();
10145 match v.as_str() {
10146 "btree" | "gin" | "gist" | "brin" | "spgist" | "hash"
10147 | "bloom" => method = v,
10148 other => {
10149 return Err(syn::Error::new(
10150 s.span(),
10151 format!("unknown index method `{other}` (supported: btree, gin, gist, brin, spgist, hash, bloom)"),
10152 ));
10153 }
10154 }
10155 } else {
10156 return Err(syn::Error::new(
10157 flag.span(),
10158 "unknown index flag (supported: `unique`, `name`, `method`)",
10159 ));
10160 }
10161 }
10162 } else {
10163 cols_lit = meta.value()?.parse()?;
10164 }
10165 let columns = split_field_list(&cols_lit.value());
10166 out.indexes.push(IndexAttr {
10167 span: meta.path.span(),
10168 name,
10169 columns,
10170 unique,
10171 method,
10172 where_clause: None,
10173 include: Vec::new(),
10174 });
10175 return Ok(());
10176 }
10177 if meta.path.is_ident("check") {
10178 let mut name: Option<String> = None;
10180 let mut expr: Option<String> = None;
10181 meta.parse_nested_meta(|inner| {
10182 if inner.path.is_ident("name") {
10183 let s: LitStr = inner.value()?.parse()?;
10184 name = Some(s.value());
10185 return Ok(());
10186 }
10187 if inner.path.is_ident("expr") {
10188 let s: LitStr = inner.value()?.parse()?;
10189 expr = Some(s.value());
10190 return Ok(());
10191 }
10192 Err(inner.error("unknown check attribute (supported: `name`, `expr`)"))
10193 })?;
10194 let name = name.ok_or_else(|| meta.error("check requires `name = \"...\"`"))?;
10195 let expr = expr.ok_or_else(|| meta.error("check requires `expr = \"...\"`"))?;
10196 out.checks.push(CheckAttr { name, expr });
10197 return Ok(());
10198 }
10199 if meta.path.is_ident("exclude") {
10200 let mut name: Option<String> = None;
10204 let mut using: Option<String> = None;
10205 let mut elements_raw: Option<(String, proc_macro2::Span)> = None;
10206 let mut where_clause: Option<String> = None;
10207 meta.parse_nested_meta(|inner| {
10208 if inner.path.is_ident("name") {
10209 let s: LitStr = inner.value()?.parse()?;
10210 name = Some(s.value());
10211 return Ok(());
10212 }
10213 if inner.path.is_ident("using") {
10214 let s: LitStr = inner.value()?.parse()?;
10215 using = Some(s.value());
10216 return Ok(());
10217 }
10218 if inner.path.is_ident("elements") {
10219 let s: LitStr = inner.value()?.parse()?;
10220 elements_raw = Some((s.value(), s.span()));
10221 return Ok(());
10222 }
10223 if inner.path.is_ident("where") || inner.path.is_ident("where_clause") {
10224 let s: LitStr = inner.value()?.parse()?;
10225 where_clause = Some(s.value());
10226 return Ok(());
10227 }
10228 Err(inner.error(
10229 "unknown exclude attribute (supported: `name`, `using`, `elements`, `where`)",
10230 ))
10231 })?;
10232 let name = name.ok_or_else(|| meta.error("exclude requires `name = \"...\"`"))?;
10233 let using = using.unwrap_or_else(|| "gist".to_owned());
10234 let (elements_str, elements_span) = elements_raw.ok_or_else(|| {
10235 meta.error(
10236 "exclude requires `elements = \"col WITH op, col WITH op\"`",
10237 )
10238 })?;
10239 let mut elements: Vec<(String, String)> = Vec::new();
10241 for pair in elements_str.split(',') {
10242 let pair = pair.trim();
10243 if pair.is_empty() {
10244 continue;
10245 }
10246 let mut split = pair.splitn(2, |c: char| c.is_whitespace());
10247 let col = split.next().unwrap_or("").trim();
10248 let rest = split.next().unwrap_or("").trim();
10249 let rest_lc = rest.to_ascii_lowercase();
10251 let op = rest_lc
10252 .strip_prefix("with")
10253 .map(|r| r.trim_start())
10254 .filter(|r| !r.is_empty())
10255 .map(|_| {
10256 rest[4..].trim_start().to_owned()
10259 });
10260 let Some(op) = op else {
10261 return Err(syn::Error::new(
10262 elements_span,
10263 format!(
10264 "exclude elements: `{pair}` must be `<col> WITH <op>` \
10265 (e.g. `room_id WITH =` or `during WITH &&`)"
10266 ),
10267 ));
10268 };
10269 if col.is_empty() || op.is_empty() {
10270 return Err(syn::Error::new(
10271 elements_span,
10272 format!(
10273 "exclude elements: `{pair}` must be `<col> WITH <op>` \
10274 (both sides non-empty)"
10275 ),
10276 ));
10277 }
10278 elements.push((col.to_owned(), op));
10279 }
10280 if elements.is_empty() {
10281 return Err(syn::Error::new(
10282 elements_span,
10283 "exclude requires at least one `col WITH op` element",
10284 ));
10285 }
10286 out.excludes.push(ExcludeAttr {
10287 name,
10288 using,
10289 elements,
10290 where_clause,
10291 });
10292 return Ok(());
10293 }
10294 if meta.path.is_ident("generic_fk") {
10295 let mut gfk = GenericFkAttr {
10296 name: String::new(),
10297 ct_column: String::new(),
10298 pk_column: String::new(),
10299 };
10300 meta.parse_nested_meta(|inner| {
10301 if inner.path.is_ident("name") {
10302 let s: LitStr = inner.value()?.parse()?;
10303 gfk.name = s.value();
10304 return Ok(());
10305 }
10306 if inner.path.is_ident("ct_column") {
10307 let s: LitStr = inner.value()?.parse()?;
10308 gfk.ct_column = s.value();
10309 return Ok(());
10310 }
10311 if inner.path.is_ident("pk_column") {
10312 let s: LitStr = inner.value()?.parse()?;
10313 gfk.pk_column = s.value();
10314 return Ok(());
10315 }
10316 Err(inner.error(
10317 "unknown generic_fk attribute (supported: `name`, `ct_column`, `pk_column`)",
10318 ))
10319 })?;
10320 if gfk.name.is_empty() {
10321 return Err(meta.error("generic_fk requires `name = \"...\"`"));
10322 }
10323 if gfk.ct_column.is_empty() {
10324 return Err(meta.error("generic_fk requires `ct_column = \"...\"`"));
10325 }
10326 if gfk.pk_column.is_empty() {
10327 return Err(meta.error("generic_fk requires `pk_column = \"...\"`"));
10328 }
10329 out.generic_fks.push(gfk);
10330 return Ok(());
10331 }
10332 if meta.path.is_ident("fk_composite") {
10333 let mut fk = CompositeFkAttr {
10334 name: String::new(),
10335 to: String::new(),
10336 from: Vec::new(),
10337 on: Vec::new(),
10338 };
10339 meta.parse_nested_meta(|inner| {
10340 if inner.path.is_ident("name") {
10341 let s: LitStr = inner.value()?.parse()?;
10342 fk.name = s.value();
10343 return Ok(());
10344 }
10345 if inner.path.is_ident("to") {
10346 let s: LitStr = inner.value()?.parse()?;
10347 fk.to = s.value();
10348 return Ok(());
10349 }
10350 if inner.path.is_ident("on") || inner.path.is_ident("from") {
10353 let value = inner.value()?;
10354 let content;
10355 syn::parenthesized!(content in value);
10356 let lits: syn::punctuated::Punctuated<syn::LitStr, syn::Token![,]> =
10357 content.parse_terminated(
10358 |p| p.parse::<syn::LitStr>(),
10359 syn::Token![,],
10360 )?;
10361 let cols: Vec<String> = lits.iter().map(syn::LitStr::value).collect();
10362 if inner.path.is_ident("on") {
10363 fk.on = cols;
10364 } else {
10365 fk.from = cols;
10366 }
10367 return Ok(());
10368 }
10369 Err(inner.error(
10370 "unknown fk_composite attribute (supported: `name`, `to`, `on`, `from`)",
10371 ))
10372 })?;
10373 if fk.name.is_empty() {
10374 return Err(meta.error("fk_composite requires `name = \"...\"`"));
10375 }
10376 if fk.to.is_empty() {
10377 return Err(meta.error("fk_composite requires `to = \"...\"`"));
10378 }
10379 if fk.from.is_empty() || fk.on.is_empty() {
10380 return Err(meta.error(
10381 "fk_composite requires non-empty `from = (...)` and `on = (...)` tuples",
10382 ));
10383 }
10384 if fk.from.len() != fk.on.len() {
10385 return Err(meta.error(format!(
10386 "fk_composite `from` ({} cols) and `on` ({} cols) must be the same length",
10387 fk.from.len(),
10388 fk.on.len(),
10389 )));
10390 }
10391 out.composite_fks.push(fk);
10392 return Ok(());
10393 }
10394 if meta.path.is_ident("m2m") {
10395 let mut m2m = M2MAttr {
10396 name: String::new(),
10397 to: String::new(),
10398 through: String::new(),
10399 src: String::new(),
10400 dst: String::new(),
10401 auto_create: true,
10402 };
10403 meta.parse_nested_meta(|inner| {
10404 if inner.path.is_ident("name") {
10405 let s: LitStr = inner.value()?.parse()?;
10406 m2m.name = s.value();
10407 return Ok(());
10408 }
10409 if inner.path.is_ident("to") {
10410 let s: LitStr = inner.value()?.parse()?;
10411 m2m.to = s.value();
10412 return Ok(());
10413 }
10414 if inner.path.is_ident("through") {
10415 let s: LitStr = inner.value()?.parse()?;
10416 m2m.through = s.value();
10417 return Ok(());
10418 }
10419 if inner.path.is_ident("src") {
10420 let s: LitStr = inner.value()?.parse()?;
10421 m2m.src = s.value();
10422 return Ok(());
10423 }
10424 if inner.path.is_ident("dst") {
10425 let s: LitStr = inner.value()?.parse()?;
10426 m2m.dst = s.value();
10427 return Ok(());
10428 }
10429 if inner.path.is_ident("auto_create") {
10430 let lit: syn::LitBool = inner.value()?.parse()?;
10431 m2m.auto_create = lit.value;
10432 return Ok(());
10433 }
10434 Err(inner.error("unknown m2m attribute (supported: `name`, `to`, `through`, `src`, `dst`, `auto_create`)"))
10435 })?;
10436 if m2m.name.is_empty() {
10437 return Err(meta.error("m2m requires `name = \"...\"`"));
10438 }
10439 if m2m.to.is_empty() {
10440 return Err(meta.error("m2m requires `to = \"...\"`"));
10441 }
10442 if m2m.through.is_empty() {
10443 return Err(meta.error("m2m requires `through = \"...\"`"));
10444 }
10445 if m2m.src.is_empty() {
10446 return Err(meta.error("m2m requires `src = \"...\"`"));
10447 }
10448 if m2m.dst.is_empty() {
10449 return Err(meta.error("m2m requires `dst = \"...\"`"));
10450 }
10451 out.m2m.push(m2m);
10452 return Ok(());
10453 }
10454 if meta.path.is_ident("generic_m2m") {
10455 let mut gm = GenericM2MAttr {
10456 name: String::new(),
10457 through: String::new(),
10458 pk_column: String::new(),
10459 ct_column: String::new(),
10460 related_column: String::new(),
10461 };
10462 meta.parse_nested_meta(|inner| {
10463 let field = |inner: &syn::meta::ParseNestedMeta| -> syn::Result<String> {
10464 let s: LitStr = inner.value()?.parse()?;
10465 Ok(s.value())
10466 };
10467 if inner.path.is_ident("name") {
10468 gm.name = field(&inner)?;
10469 return Ok(());
10470 }
10471 if inner.path.is_ident("through") {
10472 gm.through = field(&inner)?;
10473 return Ok(());
10474 }
10475 if inner.path.is_ident("pk_column") {
10476 gm.pk_column = field(&inner)?;
10477 return Ok(());
10478 }
10479 if inner.path.is_ident("ct_column") {
10480 gm.ct_column = field(&inner)?;
10481 return Ok(());
10482 }
10483 if inner.path.is_ident("related_column") {
10484 gm.related_column = field(&inner)?;
10485 return Ok(());
10486 }
10487 Err(inner.error("unknown generic_m2m attribute (supported: `name`, `through`, `pk_column`, `ct_column`, `related_column`)"))
10488 })?;
10489 for (val, label) in [
10490 (&gm.name, "name"),
10491 (&gm.through, "through"),
10492 (&gm.pk_column, "pk_column"),
10493 (&gm.ct_column, "ct_column"),
10494 (&gm.related_column, "related_column"),
10495 ] {
10496 if val.is_empty() {
10497 return Err(meta.error(format!("generic_m2m requires `{label} = \"...\"`")));
10498 }
10499 }
10500 out.generic_m2m.push(gm);
10501 return Ok(());
10502 }
10503 Err(unknown_key_error(&meta, "container", CONTAINER_ATTR_KEYS))
10504 })?;
10505 }
10506 Ok(out)
10507}
10508
10509fn split_field_list(raw: &str) -> Vec<String> {
10513 raw.split(',')
10514 .map(str::trim)
10515 .filter(|s| !s.is_empty())
10516 .map(str::to_owned)
10517 .collect()
10518}
10519
10520fn parse_together_attr(
10528 meta: &syn::meta::ParseNestedMeta<'_>,
10529 attr: &str,
10530 written: &str,
10531) -> syn::Result<(Vec<String>, Option<String>)> {
10532 if meta.input.peek(syn::Token![=]) {
10535 let cols_lit: LitStr = meta.value()?.parse()?;
10536 let columns = split_field_list(&cols_lit.value());
10537 check_together_columns(meta, attr, written, &columns)?;
10538 return Ok((columns, None));
10539 }
10540 let mut columns: Option<Vec<String>> = None;
10541 let mut name: Option<String> = None;
10542 meta.parse_nested_meta(|inner| {
10543 if inner.path.is_ident("columns") {
10544 let s: LitStr = inner.value()?.parse()?;
10545 columns = Some(split_field_list(&s.value()));
10546 return Ok(());
10547 }
10548 if inner.path.is_ident("name") {
10549 let s: LitStr = inner.value()?.parse()?;
10550 name = Some(s.value());
10551 return Ok(());
10552 }
10553 Err(inner.error("unknown sub-attribute (supported: `columns`, `name`)"))
10554 })?;
10555 let columns = columns.ok_or_else(|| {
10556 meta.error(format!(
10557 "{attr}(...) requires a `columns = \"col1, col2\"` argument",
10558 ))
10559 })?;
10560 check_together_columns(meta, attr, written, &columns)?;
10561 Ok((columns, name))
10562}
10563
10564fn check_together_columns(
10565 meta: &syn::meta::ParseNestedMeta<'_>,
10566 attr: &str,
10567 written: &str,
10568 columns: &[String],
10569) -> syn::Result<()> {
10570 if columns.len() < 2 {
10571 let flag = if attr == "unique_together" {
10572 "unique"
10573 } else {
10574 "index"
10575 };
10576 let single = format!("#[{written}({flag})] on the field");
10577 return Err(meta.error(format!(
10578 "{attr} expects two or more columns; for a single-column equivalent use {single}",
10579 )));
10580 }
10581 Ok(())
10582}
10583
10584fn parse_fieldset_list(raw: &str) -> Vec<(String, Vec<String>)> {
10593 raw.split('|')
10594 .map(str::trim)
10595 .filter(|s| !s.is_empty())
10596 .map(|section| {
10597 let (title, rest) = match section.split_once(':') {
10599 Some((title, rest)) if !title.contains(',') => (title.trim().to_owned(), rest),
10600 _ => (String::new(), section),
10601 };
10602 let fields = split_field_list(rest);
10603 (title, fields)
10604 })
10605 .collect()
10606}
10607
10608fn parse_prepopulated_list(raw: &str) -> Vec<(String, Vec<String>)> {
10613 raw.split(',')
10614 .map(str::trim)
10615 .filter(|s| !s.is_empty())
10616 .filter_map(|entry| {
10617 let (target, sources_raw) = entry.split_once(':')?;
10618 let target = target.trim().to_owned();
10619 if target.is_empty() {
10620 return None;
10621 }
10622 let sources: Vec<String> = sources_raw
10623 .split('+')
10624 .map(|s| s.trim().to_owned())
10625 .filter(|s| !s.is_empty())
10626 .collect();
10627 if sources.is_empty() {
10628 return None;
10629 }
10630 Some((target, sources))
10631 })
10632 .collect()
10633}
10634
10635fn parse_formfield_overrides(raw: &str) -> Vec<(String, String)> {
10641 raw.split(',')
10642 .map(str::trim)
10643 .filter(|s| !s.is_empty())
10644 .filter_map(|entry| {
10645 let (field, widget) = entry.split_once(':')?;
10646 let field = field.trim().to_owned();
10647 let widget = widget.trim().to_owned();
10648 if field.is_empty() || widget.is_empty() {
10649 return None;
10650 }
10651 Some((field, widget))
10652 })
10653 .collect()
10654}
10655
10656fn parse_ordering_list(raw: &str) -> Vec<(String, bool)> {
10659 raw.split(',')
10660 .map(str::trim)
10661 .filter(|s| !s.is_empty())
10662 .map(|spec| {
10663 spec.strip_prefix('-')
10664 .map_or((spec.to_owned(), false), |rest| {
10665 (rest.trim().to_owned(), true)
10666 })
10667 })
10668 .collect()
10669}
10670
10671struct FieldAttrs {
10672 column: Option<String>,
10673 primary_key: bool,
10674 fk: Option<String>,
10675 o2o: Option<String>,
10676 on: Option<String>,
10677 on_delete: Option<String>,
10687 related_name: Option<String>,
10695 max_length: Option<u32>,
10696 vector_dims: Option<u32>,
10700 geometry_srid: Option<u32>,
10704 min: Option<i64>,
10705 max: Option<i64>,
10706 default: Option<String>,
10707 auto_uuid: bool,
10713 default_uuid_v7: bool,
10724 auto_now_add: bool,
10729 auto_now: bool,
10735 soft_delete: bool,
10740 unique: bool,
10743 index: bool,
10747 index_unique: bool,
10748 index_name: Option<String>,
10749 index_method: String,
10752 generated_as: Option<String>,
10758 help_text: Option<String>,
10763 choices: Option<Vec<(String, String)>>,
10771 db_comment: Option<String>,
10777 verbose_name: Option<String>,
10783 editable: bool,
10790 blank: bool,
10794 case_insensitive: bool,
10799 validators: Vec<syn::Ident>,
10805 secret: Option<bool>,
10807}
10808
10809const FIELD_ATTR_KEYS: &[&str] = &[
10811 "column",
10812 "primary_key",
10813 "fk",
10814 "o2o",
10815 "on",
10816 "on_delete",
10817 "related_name",
10818 "max_length",
10819 "vector",
10820 "geometry",
10821 "min",
10822 "max",
10823 "default",
10824 "generated_as",
10825 "help_text",
10826 "choices",
10827 "db_comment",
10828 "verbose_name",
10829 "editable",
10830 "secret",
10831 "blank",
10832 "citext",
10833 "validators",
10834 "auto_uuid",
10835 "default_uuid_v7",
10836 "auto_now_add",
10837 "auto_now",
10838 "soft_delete",
10839 "unique",
10840 "index",
10841];
10842
10843const CONTAINER_ATTR_KEYS: &[&str] = &[
10845 "table",
10846 "display",
10847 "app",
10848 "scope",
10849 "access",
10850 "admin",
10851 "manager",
10852 "manager_fn",
10853 "default_order",
10854 "global_scope",
10855 "through",
10856 "reverse_has",
10857 "generic_has",
10858 "audit",
10859 "permissions",
10860 "view",
10861 "managed",
10862 "verbose_name",
10863 "verbose_name_plural",
10864 "db_table_comment",
10865 "proxy",
10866 "order_with_respect_to",
10867 "required_db_features",
10868 "required_db_vendor",
10869 "base_manager_name",
10870 "default_related_name",
10871 "extra_permissions",
10872 "default_permissions",
10873 "get_latest_by",
10874 "unique_together",
10875 "index_together",
10876 "unique_when",
10877 "index_when",
10878 "index",
10879 "check",
10880 "exclude",
10881 "generic_fk",
10882 "fk_composite",
10883 "m2m",
10884 "generic_m2m",
10885];
10886
10887fn parse_flag(meta: &syn::meta::ParseNestedMeta<'_>, key: &str) -> syn::Result<()> {
10890 if meta.input.peek(syn::Token![=]) {
10891 let lit: syn::LitBool = meta.value()?.parse()?;
10892 if !lit.value {
10893 return Err(syn::Error::new(
10894 lit.span(),
10895 format!("`{key} = false` is the default; remove the attribute"),
10896 ));
10897 }
10898 }
10899 Ok(())
10900}
10901
10902fn unknown_key_error(
10904 meta: &syn::meta::ParseNestedMeta<'_>,
10905 what: &str,
10906 keys: &[&str],
10907) -> syn::Error {
10908 let path = &meta.path;
10909 let got = path
10910 .get_ident()
10911 .map_or_else(|| quote!(#path).to_string(), ToString::to_string);
10912 let mut msg = format!("unknown rustango {what} attribute `{got}`");
10913 if let Some(near) = keys
10914 .iter()
10915 .map(|k| (edit_distance(&got, k), *k))
10916 .filter(|(d, k)| *d <= 2.max(k.len() / 4))
10917 .min_by_key(|(d, _)| *d)
10918 .map(|(_, k)| k)
10919 {
10920 msg.push_str(&format!("; did you mean `{near}`?"));
10921 }
10922 msg.push_str(&format!(" Supported: {}", keys.join(", ")));
10923 meta.error(msg)
10924}
10925
10926fn edit_distance(a: &str, b: &str) -> usize {
10928 let b: Vec<char> = b.chars().collect();
10929 let mut prev: Vec<usize> = (0..=b.len()).collect();
10930 for (i, ca) in a.chars().enumerate() {
10931 let mut cur = vec![i + 1];
10932 for (j, cb) in b.iter().enumerate() {
10933 let sub = prev[j] + usize::from(ca != *cb);
10934 cur.push(sub.min(prev[j + 1] + 1).min(cur[j] + 1));
10935 }
10936 prev = cur;
10937 }
10938 prev[b.len()]
10939}
10940
10941fn parse_field_attrs(field: &syn::Field) -> syn::Result<FieldAttrs> {
10942 let mut out = FieldAttrs {
10943 column: None,
10944 primary_key: false,
10945 fk: None,
10946 o2o: None,
10947 on: None,
10948 on_delete: None,
10949 related_name: None,
10950 max_length: None,
10951 vector_dims: None,
10952 geometry_srid: None,
10953 min: None,
10954 max: None,
10955 default: None,
10956 auto_uuid: false,
10957 default_uuid_v7: false,
10958 auto_now_add: false,
10959 auto_now: false,
10960 soft_delete: false,
10961 unique: false,
10962 index: false,
10963 index_unique: false,
10964 index_name: None,
10965 index_method: "btree".to_owned(),
10966 generated_as: None,
10967 help_text: None,
10968 choices: None,
10969 db_comment: None,
10970 verbose_name: None,
10971 editable: true,
10972 blank: false,
10973 case_insensitive: false,
10974 validators: Vec::new(),
10975 secret: None,
10976 };
10977 for attr in model_attrs(&field.attrs)? {
10978 attr.parse_nested_meta(|meta| {
10979 if meta.path.is_ident("column") {
10980 let s: LitStr = meta.value()?.parse()?;
10981 let name = s.value();
10982 validate_sql_identifier(&name, "column", s.span())?;
10983 out.column = Some(name);
10984 return Ok(());
10985 }
10986 if meta.path.is_ident("primary_key") {
10987 parse_flag(&meta, "primary_key")?;
10988 out.primary_key = true;
10989 return Ok(());
10990 }
10991 if meta.path.is_ident("fk") {
10992 let s: LitStr = meta.value()?.parse()?;
10993 out.fk = Some(s.value());
10994 return Ok(());
10995 }
10996 if meta.path.is_ident("o2o") {
10997 let s: LitStr = meta.value()?.parse()?;
10998 out.o2o = Some(s.value());
10999 return Ok(());
11000 }
11001 if meta.path.is_ident("on") {
11002 let s: LitStr = meta.value()?.parse()?;
11003 out.on = Some(s.value());
11004 return Ok(());
11005 }
11006 if meta.path.is_ident("on_delete") {
11007 let s: LitStr = meta.value()?.parse()?;
11008 let raw = s.value();
11009 let normalized = raw.trim().to_ascii_lowercase();
11010 match normalized.as_str() {
11013 "cascade" | "restrict" | "set_null" | "set_default" | "no_action" => {}
11014 _ => {
11015 return Err(syn::Error::new(
11016 s.span(),
11017 format!(
11018 "unknown on_delete action `{raw}`; expected one of \
11019 `cascade`, `restrict`, `set_null`, `set_default`, `no_action`"
11020 ),
11021 ));
11022 }
11023 }
11024 out.on_delete = Some(normalized);
11025 return Ok(());
11026 }
11027 if meta.path.is_ident("related_name") {
11028 let s: LitStr = meta.value()?.parse()?;
11029 let raw = s.value();
11030 if raw.trim().is_empty() {
11031 return Err(syn::Error::new(
11032 s.span(),
11033 "`related_name` must be a non-empty identifier",
11034 ));
11035 }
11036 let snake = raw
11041 .chars()
11042 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
11043 && !raw.starts_with(char::is_numeric);
11044 if !snake && raw != NO_REVERSE_ACCESSOR {
11045 return Err(syn::Error::new(
11046 s.span(),
11047 "`related_name` must be snake_case ASCII (lowercase letters, \
11048 digits, underscores; no leading digit), or \"+\" for none",
11049 ));
11050 }
11051 out.related_name = Some(raw);
11052 return Ok(());
11053 }
11054 if meta.path.is_ident("max_length") {
11055 let lit: syn::LitInt = meta.value()?.parse()?;
11056 out.max_length = Some(lit.base10_parse::<u32>()?);
11057 return Ok(());
11058 }
11059 if meta.path.is_ident("vector") {
11063 meta.parse_nested_meta(|inner| {
11064 if inner.path.is_ident("dims") {
11065 let lit: syn::LitInt = inner.value()?.parse()?;
11066 out.vector_dims = Some(lit.base10_parse::<u32>()?);
11067 return Ok(());
11068 }
11069 Err(inner.error("unknown `vector` attribute (supported: `dims`)"))
11070 })?;
11071 return Ok(());
11072 }
11073 if meta.path.is_ident("geometry") {
11076 meta.parse_nested_meta(|inner| {
11077 if inner.path.is_ident("srid") {
11078 let lit: syn::LitInt = inner.value()?.parse()?;
11079 out.geometry_srid = Some(lit.base10_parse::<u32>()?);
11080 return Ok(());
11081 }
11082 Err(inner.error("unknown `geometry` attribute (supported: `srid`)"))
11083 })?;
11084 return Ok(());
11085 }
11086 if meta.path.is_ident("min") {
11087 out.min = Some(parse_signed_i64(&meta)?);
11088 return Ok(());
11089 }
11090 if meta.path.is_ident("max") {
11091 out.max = Some(parse_signed_i64(&meta)?);
11092 return Ok(());
11093 }
11094 if meta.path.is_ident("default") {
11095 let s: LitStr = meta.value()?.parse()?;
11096 out.default = Some(s.value());
11097 return Ok(());
11098 }
11099 if meta.path.is_ident("generated_as") {
11100 let s: LitStr = meta.value()?.parse()?;
11101 out.generated_as = Some(s.value());
11102 return Ok(());
11103 }
11104 if meta.path.is_ident("help_text") {
11105 let s: LitStr = meta.value()?.parse()?;
11106 out.help_text = Some(s.value());
11107 return Ok(());
11108 }
11109 if meta.path.is_ident("choices") {
11110 let s: LitStr = meta.value()?.parse()?;
11111 let raw = s.value();
11112 let mut pairs: Vec<(String, String)> = Vec::new();
11113 for chunk in raw.split(',') {
11114 let chunk = chunk.trim();
11115 if chunk.is_empty() {
11116 continue;
11117 }
11118 let (value, label) = match chunk.split_once(':') {
11119 Some((v, l)) => (v.trim().to_owned(), l.trim().to_owned()),
11120 None => (chunk.to_owned(), chunk.to_owned()),
11121 };
11122 if value.is_empty() {
11123 return Err(syn::Error::new(
11124 s.span(),
11125 "`choices` entry has empty value before `:`",
11126 ));
11127 }
11128 pairs.push((value, label));
11129 }
11130 if pairs.is_empty() {
11131 return Err(syn::Error::new(
11132 s.span(),
11133 "`choices = \"…\"` must contain at least one value",
11134 ));
11135 }
11136 out.choices = Some(pairs);
11137 return Ok(());
11138 }
11139 if meta.path.is_ident("db_comment") {
11140 let s: LitStr = meta.value()?.parse()?;
11141 out.db_comment = Some(s.value());
11142 return Ok(());
11143 }
11144 if meta.path.is_ident("verbose_name") {
11145 let s: LitStr = meta.value()?.parse()?;
11146 out.verbose_name = Some(s.value());
11147 return Ok(());
11148 }
11149 if meta.path.is_ident("secret") {
11150 out.secret = Some(if let Ok(v) = meta.value() {
11152 v.parse::<syn::LitBool>()?.value
11153 } else {
11154 true
11155 });
11156 return Ok(());
11157 }
11158 if meta.path.is_ident("editable") {
11159 if let Ok(v) = meta.value() {
11164 let lit: syn::LitBool = v.parse()?;
11165 out.editable = lit.value;
11166 } else {
11167 out.editable = true;
11168 }
11169 return Ok(());
11170 }
11171 if meta.path.is_ident("blank") {
11172 if let Ok(v) = meta.value() {
11176 let lit: syn::LitBool = v.parse()?;
11177 out.blank = lit.value;
11178 } else {
11179 out.blank = true;
11180 }
11181 return Ok(());
11182 }
11183 if meta.path.is_ident("citext") {
11184 if let Ok(v) = meta.value() {
11193 let lit: syn::LitBool = v.parse()?;
11194 out.case_insensitive = lit.value;
11195 } else {
11196 out.case_insensitive = true;
11197 }
11198 return Ok(());
11199 }
11200 if meta.path.is_ident("validators") {
11201 let s: LitStr = meta.value()?.parse()?;
11202 let raw = s.value();
11203 out.validators = raw
11204 .split(',')
11205 .map(str::trim)
11206 .filter(|s| !s.is_empty())
11207 .map(|name| {
11208 let mut ident = syn::parse_str::<syn::Ident>(name).map_err(|_| {
11209 syn::Error::new(s.span(), format!("unknown validator `{name}`"))
11210 })?;
11211 ident.set_span(s.span());
11212 Ok(ident)
11213 })
11214 .collect::<syn::Result<_>>()?;
11215 if out.validators.is_empty() {
11216 return Err(syn::Error::new(
11217 s.span(),
11218 "`validators = \"…\"` must list at least one name",
11219 ));
11220 }
11221 return Ok(());
11222 }
11223 if meta.path.is_ident("auto_uuid") {
11224 parse_flag(&meta, "auto_uuid")?;
11225 out.auto_uuid = true;
11226 out.primary_key = true;
11230 if out.default.is_none() {
11231 out.default = Some("gen_random_uuid()".into());
11232 }
11233 return Ok(());
11234 }
11235 if meta.path.is_ident("default_uuid_v7") {
11236 parse_flag(&meta, "default_uuid_v7")?;
11237 out.default_uuid_v7 = true;
11243 out.primary_key = true;
11244 return Ok(());
11245 }
11246 if meta.path.is_ident("auto_now_add") {
11247 parse_flag(&meta, "auto_now_add")?;
11248 out.auto_now_add = true;
11249 if out.default.is_none() {
11250 out.default = Some("now()".into());
11251 }
11252 return Ok(());
11253 }
11254 if meta.path.is_ident("auto_now") {
11255 parse_flag(&meta, "auto_now")?;
11256 out.auto_now = true;
11257 if out.default.is_none() {
11258 out.default = Some("now()".into());
11259 }
11260 return Ok(());
11261 }
11262 if meta.path.is_ident("soft_delete") {
11263 parse_flag(&meta, "soft_delete")?;
11264 out.soft_delete = true;
11265 return Ok(());
11266 }
11267 if meta.path.is_ident("unique") {
11268 parse_flag(&meta, "unique")?;
11269 out.unique = true;
11270 return Ok(());
11271 }
11272 if meta.path.is_ident("index") {
11273 parse_flag(&meta, "index")?;
11274 out.index = true;
11275 if meta.input.peek(syn::token::Paren) {
11277 meta.parse_nested_meta(|inner| {
11278 if inner.path.is_ident("unique") {
11279 out.index_unique = true;
11280 return Ok(());
11281 }
11282 if inner.path.is_ident("name") {
11283 let s: LitStr = inner.value()?.parse()?;
11284 out.index_name = Some(s.value());
11285 return Ok(());
11286 }
11287 if inner.path.is_ident("method") {
11288 let s: LitStr = inner.value()?.parse()?;
11289 let v = s.value();
11290 match v.as_str() {
11291 "btree" | "gin" | "gist" | "brin" | "spgist" | "hash" | "bloom" => {
11292 out.index_method = v;
11293 }
11294 other => {
11295 return Err(inner.error(format!(
11296 "unknown index method `{other}` (supported: btree, gin, gist, brin, spgist, hash, bloom)",
11297 )));
11298 }
11299 }
11300 return Ok(());
11301 }
11302 Err(inner.error(
11303 "unknown index sub-attribute (supported: `unique`, `name`, `method`)",
11304 ))
11305 })?;
11306 }
11307 return Ok(());
11308 }
11309 Err(unknown_key_error(&meta, "field", FIELD_ATTR_KEYS))
11310 })?;
11311 }
11312 Ok(out)
11313}
11314
11315fn parse_signed_i64(meta: &syn::meta::ParseNestedMeta<'_>) -> syn::Result<i64> {
11317 let expr: syn::Expr = meta.value()?.parse()?;
11318 match expr {
11319 syn::Expr::Lit(syn::ExprLit {
11320 lit: syn::Lit::Int(lit),
11321 ..
11322 }) => lit.base10_parse::<i64>(),
11323 syn::Expr::Unary(syn::ExprUnary {
11324 op: syn::UnOp::Neg(_),
11325 expr,
11326 ..
11327 }) => {
11328 if let syn::Expr::Lit(syn::ExprLit {
11329 lit: syn::Lit::Int(lit),
11330 ..
11331 }) = *expr
11332 {
11333 let v: i64 = lit.base10_parse()?;
11334 Ok(-v)
11335 } else {
11336 Err(syn::Error::new_spanned(expr, "expected integer literal"))
11337 }
11338 }
11339 other => Err(syn::Error::new_spanned(
11340 other,
11341 "expected integer literal (signed)",
11342 )),
11343 }
11344}
11345
11346struct FieldInfo<'a> {
11347 ident: &'a syn::Ident,
11348 column: String,
11349 primary_key: bool,
11350 auto: bool,
11354 value_ty: &'a Type,
11357 field_type_tokens: TokenStream2,
11359 schema: TokenStream2,
11360 from_row_init: TokenStream2,
11361 from_aliased_row_init: TokenStream2,
11367 from_row_init_my: TokenStream2,
11370 from_aliased_row_init_my: TokenStream2,
11371 uuid_column: bool,
11373 fk_inner: Option<Type>,
11377 fk_pk_kind: DetectedKind,
11383 nullable: bool,
11391 auto_now: bool,
11397 auto_now_add: bool,
11403 soft_delete: bool,
11408 generated_as: Option<String>,
11413 default_uuid_v7: bool,
11420 related_name: Option<String>,
11425}
11426
11427fn validate_table_name(name: &str, span: proc_macro2::Span) -> syn::Result<()> {
11441 validate_sql_identifier(name, "table", span)
11442}
11443
11444fn validate_sql_identifier(name: &str, kind: &str, span: proc_macro2::Span) -> syn::Result<()> {
11449 if name.is_empty() {
11450 return Err(syn::Error::new(
11451 span,
11452 format!("`{kind} = \"\"` is not a valid SQL identifier"),
11453 ));
11454 }
11455 let mut chars = name.chars();
11456 let first = chars.next().unwrap();
11457 if !(first.is_ascii_alphabetic() || first == '_') {
11458 return Err(syn::Error::new(
11459 span,
11460 format!("{kind} name `{name}` must start with a letter or underscore (got {first:?})"),
11461 ));
11462 }
11463 for c in chars {
11464 if !(c.is_ascii_alphanumeric() || c == '_') {
11465 return Err(syn::Error::new(
11466 span,
11467 format!(
11468 "{kind} name `{name}` contains invalid character {c:?} — \
11469 SQL identifiers must match `[a-zA-Z_][a-zA-Z0-9_]*`. \
11470 Hyphens in particular break FK / index name derivation \
11471 downstream; use underscores instead (e.g. `{}`)",
11472 name.replace(|x: char| !x.is_ascii_alphanumeric() && x != '_', "_"),
11473 ),
11474 ));
11475 }
11476 }
11477 Ok(())
11478}
11479
11480fn process_field<'a>(field: &'a syn::Field, table: &str) -> syn::Result<FieldInfo<'a>> {
11481 let root = rustango_root();
11482 let attrs = parse_field_attrs(field)?;
11483 let ident = field
11484 .ident
11485 .as_ref()
11486 .ok_or_else(|| syn::Error::new(field.span(), "tuple structs are not supported"))?;
11487 let name = ident_name(ident);
11488 let column = attrs.column.clone().unwrap_or_else(|| name.clone());
11489 let primary_key = attrs.primary_key;
11490 let DetectedType {
11491 kind,
11492 nullable,
11493 auto: detected_auto,
11494 fk_inner,
11495 } = detect_type(&field.ty)?;
11496 check_bound_compatibility(field, &attrs, kind)?;
11497 let auto = detected_auto;
11498 if attrs.auto_uuid {
11504 if kind != DetectedKind::Uuid {
11505 return Err(syn::Error::new_spanned(
11506 field,
11507 "`#[rustango(auto_uuid)]` requires the field type to be \
11508 `Auto<uuid::Uuid>`",
11509 ));
11510 }
11511 if !detected_auto {
11512 return Err(syn::Error::new_spanned(
11513 field,
11514 "`#[rustango(auto_uuid)]` requires the field type to be \
11515 wrapped in `Auto<...>` so the macro skips the column on \
11516 INSERT and the DB DEFAULT (`gen_random_uuid()`) fires",
11517 ));
11518 }
11519 }
11520 if attrs.default_uuid_v7 {
11521 if kind != DetectedKind::Uuid {
11522 return Err(syn::Error::new_spanned(
11523 field,
11524 "`#[rustango(default_uuid_v7)]` requires the field type to be \
11525 `Auto<uuid::Uuid>`",
11526 ));
11527 }
11528 if !detected_auto {
11529 return Err(syn::Error::new_spanned(
11530 field,
11531 "`#[rustango(default_uuid_v7)]` requires the field type to be \
11532 wrapped in `Auto<...>` so the macro can detect the \
11533 unset-vs-set state and fill a fresh UUIDv7 before INSERT",
11534 ));
11535 }
11536 if attrs.auto_uuid {
11537 return Err(syn::Error::new_spanned(
11538 field,
11539 "`#[rustango(default_uuid_v7)]` is mutually exclusive with \
11540 `#[rustango(auto_uuid)]` — the former generates the UUID \
11541 Rust-side, the latter relies on the DB's `gen_random_uuid()`. \
11542 Pick one.",
11543 ));
11544 }
11545 }
11546 if attrs.auto_now_add || attrs.auto_now {
11547 if kind != DetectedKind::DateTime {
11548 return Err(syn::Error::new_spanned(
11549 field,
11550 "`#[rustango(auto_now_add)]` / `#[rustango(auto_now)]` require \
11551 the field type to be `Auto<chrono::DateTime<chrono::Utc>>`",
11552 ));
11553 }
11554 if !detected_auto {
11555 return Err(syn::Error::new_spanned(
11556 field,
11557 "`#[rustango(auto_now_add)]` / `#[rustango(auto_now)]` require \
11558 the field type to be wrapped in `Auto<...>` so the macro skips \
11559 the column on INSERT and the DB DEFAULT (`now()`) fires",
11560 ));
11561 }
11562 }
11563 if attrs.soft_delete && !(kind == DetectedKind::DateTime && nullable) {
11564 return Err(syn::Error::new_spanned(
11565 field,
11566 "`#[rustango(soft_delete)]` requires the field type to be \
11567 `Option<chrono::DateTime<chrono::Utc>>`",
11568 ));
11569 }
11570 let is_mixin_auto =
11571 attrs.auto_uuid || attrs.default_uuid_v7 || attrs.auto_now_add || attrs.auto_now;
11572 if detected_auto && !primary_key && !is_mixin_auto {
11573 return Err(syn::Error::new_spanned(
11574 field,
11575 "`Auto<T>` is only valid on a `#[rustango(primary_key)]` field, \
11576 or on a field carrying one of `auto_uuid`, `auto_now_add`, or \
11577 `auto_now`",
11578 ));
11579 }
11580 if detected_auto && attrs.default.is_some() && !is_mixin_auto {
11581 return Err(syn::Error::new_spanned(
11582 field,
11583 "`#[rustango(default = \"…\")]` is redundant on an `Auto<T>` field — \
11584 SERIAL / BIGSERIAL already supplies a default sequence.",
11585 ));
11586 }
11587 if fk_inner.is_some() && primary_key {
11588 return Err(syn::Error::new_spanned(
11589 field,
11590 "`ForeignKey<T>` is not allowed on a primary-key field — \
11591 a row's PK is its own identity, not a reference to a parent.",
11592 ));
11593 }
11594 if attrs.generated_as.is_some() {
11595 if primary_key {
11596 return Err(syn::Error::new_spanned(
11597 field,
11598 "`#[rustango(generated_as = \"…\")]` is not allowed on a \
11599 primary-key field — a PK must be writable so the row \
11600 has an identity at INSERT time.",
11601 ));
11602 }
11603 if attrs.default.is_some() {
11604 return Err(syn::Error::new_spanned(
11605 field,
11606 "`#[rustango(generated_as = \"…\")]` cannot combine with \
11607 `default = \"…\"` — Postgres rejects DEFAULT on \
11608 generated columns. The expression IS the default.",
11609 ));
11610 }
11611 if detected_auto {
11612 return Err(syn::Error::new_spanned(
11613 field,
11614 "`#[rustango(generated_as = \"…\")]` is not allowed on \
11615 an `Auto<T>` field — generated columns are computed \
11616 by the DB, not server-assigned via a sequence. Use a \
11617 plain Rust type (e.g. `f64`).",
11618 ));
11619 }
11620 if fk_inner.is_some() {
11621 return Err(syn::Error::new_spanned(
11622 field,
11623 "`#[rustango(generated_as = \"…\")]` is not allowed on a \
11624 ForeignKey field.",
11625 ));
11626 }
11627 }
11628 let relation = relation_tokens(field, &attrs, fk_inner, table)?;
11629 let column_lit = column.as_str();
11630 let field_type_tokens = if kind == DetectedKind::Vector {
11635 let root = rustango_root();
11636 let dims = attrs.vector_dims.unwrap_or(0);
11637 quote!(#root::core::FieldType::Vector(#dims))
11638 } else if kind == DetectedKind::Geometry {
11639 let root = rustango_root();
11643 let srid = attrs.geometry_srid.unwrap_or(0);
11644 quote!(#root::core::FieldType::Geometry(#srid))
11645 } else {
11646 kind.variant_tokens()
11647 };
11648 let max_length = optional_u32(attrs.max_length);
11649 let min = optional_i64(attrs.min);
11650 let max = optional_i64(attrs.max);
11651 let default = optional_str(attrs.default.as_deref());
11652
11653 let unique = attrs.unique;
11654 let generated_as = optional_str(attrs.generated_as.as_deref());
11655 let help_text = optional_str(attrs.help_text.as_deref());
11656 let choices = optional_choices(attrs.choices.as_deref());
11657 let db_comment = optional_str(attrs.db_comment.as_deref());
11658 let verbose_name = optional_str(attrs.verbose_name.as_deref());
11659 let editable = attrs.editable;
11660 let secret = match attrs.secret {
11661 Some(v) => quote!(::core::option::Option::Some(#v)),
11662 None => quote!(::core::option::Option::None),
11663 };
11664 let blank = attrs.blank;
11665 let case_insensitive = attrs.case_insensitive;
11666 let validators = &attrs.validators;
11668 if attrs.on_delete.is_some() && attrs.fk.is_none() && attrs.o2o.is_none() && fk_inner.is_none()
11669 {
11670 return Err(syn::Error::new_spanned(
11671 field,
11672 "`#[rustango(on_delete = \"…\")]` requires a `ForeignKey<T>` field \
11673 or `fk = \"<table>\"` / `o2o = \"<table>\"` on the same field — it \
11674 has no meaning on a non-FK column.",
11675 ));
11676 }
11677 if attrs.on_delete.as_deref() == Some("set_null") && !nullable {
11678 return Err(syn::Error::new_spanned(
11679 field,
11680 "`on_delete = \"set_null\"` needs a nullable column: use \
11681 `Option<ForeignKey<T>>` or `Option<…>` (spelled out; a type \
11682 alias for `Option` is not seen).",
11683 ));
11684 }
11685 let fk_on_delete = match attrs.on_delete.as_deref() {
11686 None => quote!(::core::option::Option::None),
11687 Some(action) => {
11688 let variant = match action {
11689 "cascade" => quote!(Cascade),
11690 "restrict" => quote!(Restrict),
11691 "set_null" => quote!(SetNull),
11692 "set_default" => quote!(SetDefault),
11693 "no_action" => quote!(NoAction),
11694 other => unreachable!("on_delete `{other}` should have been rejected at parse"),
11696 };
11697 quote!(::core::option::Option::Some(
11698 #root::core::OnDeleteAction::#variant
11699 ))
11700 }
11701 };
11702 let auto_now_flag = attrs.auto_now;
11703 let cast = match cast_type(&field.ty) {
11704 Some(ty) => quote!(::core::option::Option::Some(<#ty>::FIELD_CAST)),
11705 None => quote!(::core::option::Option::None),
11706 };
11707 let sealed_guard = match (attrs.secret, cast_type(&field.ty)) {
11709 (Some(false), Some(ty)) => quote! {
11710 const _: () = ::core::assert!(
11711 !::core::matches!(<#ty>::FIELD_CAST, #root::core::FieldCast::Sealed),
11712 "`#[rustango(secret = false)]` on a sealed cast: it is always a credential",
11713 );
11714 },
11715 _ => quote!(),
11716 };
11717 let schema = quote! {{
11718 #sealed_guard
11719 #root::core::FieldSchema::from_parts(#root::core::FieldSchemaParts {
11720 name: #name,
11721 column: #column_lit,
11722 ty: #field_type_tokens,
11723 nullable: #nullable,
11724 primary_key: #primary_key,
11725 relation: #relation,
11726 max_length: #max_length,
11727 min: #min,
11728 max: #max,
11729 default: #default,
11730 auto: #auto,
11731 auto_now: #auto_now_flag,
11732 unique: #unique,
11733 generated_as: #generated_as,
11734 help_text: #help_text,
11735 choices: #choices,
11736 db_comment: #db_comment,
11737 verbose_name: #verbose_name,
11738 editable: #editable,
11739 blank: #blank,
11740 case_insensitive: #case_insensitive,
11741 fk_on_delete: #fk_on_delete,
11742 validators: &[ #( #root::core::__validator_names::#validators ),* ],
11743 cast: #cast,
11744 secret: #secret,
11745 })
11746 }};
11747
11748 let decimal_column = kind == DetectedKind::Decimal && !detected_auto && fk_inner.is_none();
11750 let (from_row_init, from_aliased_row_init) = if decimal_column {
11751 (
11752 quote! { #ident: #root::sql::try_get_flat(row, #column_lit)? },
11753 quote! {
11754 #ident: #root::sql::try_get_flat(
11755 row,
11756 #root::core::joins::joined_label(prefix, #column_lit).as_str(),
11757 )?
11758 },
11759 )
11760 } else {
11761 (
11762 quote! { #ident: #root::sql::sqlx::Row::try_get(row, #column_lit)? },
11763 quote! {
11764 #ident: #root::sql::sqlx::Row::try_get(
11765 row,
11766 #root::core::joins::joined_label(prefix, #column_lit).as_str(),
11767 )?
11768 },
11769 )
11770 };
11771 let uuid_column = kind == DetectedKind::Uuid && !detected_auto && fk_inner.is_none();
11774 let (from_row_init_my, from_aliased_row_init_my) = if uuid_column {
11775 (
11776 quote! { #ident: #root::sql::try_get_flat_my(row, #column_lit)? },
11777 quote! {
11778 #ident: #root::sql::try_get_flat_my(
11779 row,
11780 #root::core::joins::joined_label(prefix, #column_lit).as_str(),
11781 )?
11782 },
11783 )
11784 } else {
11785 (from_row_init.clone(), from_aliased_row_init.clone())
11786 };
11787
11788 Ok(FieldInfo {
11789 ident,
11790 column,
11791 primary_key,
11792 auto,
11793 value_ty: &field.ty,
11794 field_type_tokens,
11795 schema,
11796 from_row_init,
11797 from_aliased_row_init,
11798 from_row_init_my,
11799 from_aliased_row_init_my,
11800 uuid_column,
11801 fk_inner: fk_inner.cloned(),
11802 fk_pk_kind: kind,
11803 nullable,
11804 auto_now: attrs.auto_now,
11805 auto_now_add: attrs.auto_now_add,
11806 soft_delete: attrs.soft_delete,
11807 generated_as: attrs.generated_as.clone(),
11808 default_uuid_v7: attrs.default_uuid_v7,
11809 related_name: attrs.related_name.clone(),
11810 })
11811}
11812
11813fn check_bound_compatibility(
11814 field: &syn::Field,
11815 attrs: &FieldAttrs,
11816 kind: DetectedKind,
11817) -> syn::Result<()> {
11818 if attrs.max_length.is_some() && kind != DetectedKind::String {
11819 return Err(syn::Error::new_spanned(
11820 field,
11821 "`max_length` is only valid on `String` fields (or `Option<String>`)",
11822 ));
11823 }
11824 if attrs.case_insensitive && kind != DetectedKind::String {
11826 return Err(syn::Error::new_spanned(
11827 field,
11828 "`citext` is only valid on `String` fields (or `Option<String>`)",
11829 ));
11830 }
11831 if attrs.vector_dims.is_some() && kind != DetectedKind::Vector {
11832 return Err(syn::Error::new_spanned(
11833 field,
11834 "`vector(dims = …)` is only valid on a pgvector `Vector` field",
11835 ));
11836 }
11837 if attrs.geometry_srid.is_some() && kind != DetectedKind::Geometry {
11838 return Err(syn::Error::new_spanned(
11839 field,
11840 "`geometry(srid = …)` is only valid on a PostGIS `Point` field",
11841 ));
11842 }
11843 if attrs.choices.is_some() && kind != DetectedKind::String {
11844 return Err(syn::Error::new_spanned(
11845 field,
11846 "`choices` is only valid on `String` fields (or `Option<String>`) — \
11847 integer-valued enumerations should be modeled with a Rust enum and \
11848 custom (de)serializer for now",
11849 ));
11850 }
11851 if !attrs.validators.is_empty() && kind != DetectedKind::String {
11853 return Err(syn::Error::new_spanned(
11854 field,
11855 "`validators` is only valid on `String` fields (or `Option<String>`)",
11856 ));
11857 }
11858 if (attrs.min.is_some() || attrs.max.is_some()) && !kind.is_integer() {
11859 return Err(syn::Error::new_spanned(
11860 field,
11861 "`min` / `max` are only valid on integer fields (`i32`, `i64`, optionally Option-wrapped)",
11862 ));
11863 }
11864 if let (Some(min), Some(max)) = (attrs.min, attrs.max) {
11865 if min > max {
11866 return Err(syn::Error::new_spanned(
11867 field,
11868 format!("`min` ({min}) is greater than `max` ({max})"),
11869 ));
11870 }
11871 }
11872 if let Some(default) = &attrs.default {
11873 check_default_expr(default, kind).map_err(|msg| syn::Error::new_spanned(field, msg))?;
11874 }
11875 Ok(())
11876}
11877
11878const DEFAULT_KEYWORDS: &[&str] = &[
11881 "NULL",
11882 "TRUE",
11883 "FALSE",
11884 "CURRENT_TIMESTAMP",
11885 "CURRENT_DATE",
11886 "CURRENT_TIME",
11887 "LOCALTIMESTAMP",
11888 "LOCALTIME",
11889];
11890
11891fn check_default_expr(default: &str, kind: DetectedKind) -> Result<(), String> {
11895 let expr = default.trim();
11896 let is_word = expr.starts_with(|c: char| c.is_ascii_alphabetic() || c == '_')
11897 && expr
11898 .chars()
11899 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == ' ');
11900 let upper = expr.to_ascii_uppercase();
11901 if is_word && ["CURRENT_USER", "SESSION_USER", "USER"].contains(&upper.as_str()) {
11902 return Err(format!(
11903 "`default = \"{expr}\"` works on Postgres only: MySQL refuses it as a default \
11904 and SQLite stores the text `{expr}`; set the value in code instead"
11905 ));
11906 }
11907 if is_word && !DEFAULT_KEYWORDS.contains(&upper.as_str()) {
11908 return Err(format!(
11909 "`default = \"{expr}\"` is raw SQL, so `{expr}` is read as a column name; \
11910 for the text '{expr}' write `default = \"'{expr}'\"`"
11911 ));
11912 }
11913 let numeric = kind.is_integer()
11914 || matches!(
11915 kind,
11916 DetectedKind::F32 | DetectedKind::F64 | DetectedKind::Decimal
11917 );
11918 if numeric {
11919 let unquoted = expr
11920 .strip_prefix('\'')
11921 .and_then(|e| e.strip_suffix('\''))
11922 .unwrap_or(expr);
11923 let is_number = if kind.is_integer() {
11924 unquoted.parse::<i64>().is_ok()
11925 } else {
11926 unquoted.parse::<f64>().is_ok_and(f64::is_finite)
11927 };
11928 let is_literal = is_word || expr.is_empty() || expr.starts_with('\'');
11931 if is_literal && !is_number && upper != "NULL" {
11932 return Err(format!(
11933 "`default = \"{expr}\"` is not a number, but the field is numeric"
11934 ));
11935 }
11936 }
11937 Ok(())
11938}
11939
11940fn const_slice(ty: &TokenStream2, items: impl Iterator<Item = TokenStream2>) -> TokenStream2 {
11943 quote!({
11944 const ITEMS: &[#ty] = &[ #(#items),* ];
11945 ITEMS
11946 })
11947}
11948
11949fn optional_u32(value: Option<u32>) -> TokenStream2 {
11950 if let Some(v) = value {
11951 quote!(::core::option::Option::Some(#v))
11952 } else {
11953 quote!(::core::option::Option::None)
11954 }
11955}
11956
11957fn optional_i64(value: Option<i64>) -> TokenStream2 {
11958 if let Some(v) = value {
11959 quote!(::core::option::Option::Some(#v))
11960 } else {
11961 quote!(::core::option::Option::None)
11962 }
11963}
11964
11965fn optional_str(value: Option<&str>) -> TokenStream2 {
11966 if let Some(v) = value {
11967 quote!(::core::option::Option::Some(#v))
11968 } else {
11969 quote!(::core::option::Option::None)
11970 }
11971}
11972
11973fn optional_choices(pairs: Option<&[(String, String)]>) -> TokenStream2 {
11974 let Some(pairs) = pairs else {
11975 return quote!(::core::option::Option::None);
11976 };
11977 let entries = pairs.iter().map(|(v, l)| quote!((#v, #l)));
11978 quote!(::core::option::Option::Some(&[#(#entries),*]))
11979}
11980
11981fn relation_tokens(
11982 field: &syn::Field,
11983 attrs: &FieldAttrs,
11984 fk_inner: Option<&syn::Type>,
11985 table: &str,
11986) -> syn::Result<TokenStream2> {
11987 let root = rustango_root();
11988 if let Some(inner) = fk_inner {
11989 if attrs.fk.is_some() || attrs.o2o.is_some() {
11990 return Err(syn::Error::new_spanned(
11991 field,
11992 "`ForeignKey<T>` already declares the FK target via the type parameter — \
11993 remove the `fk = \"…\"` / `o2o = \"…\"` attribute.",
11994 ));
11995 }
11996 let on = match attrs.on.as_deref() {
11998 Some(on) => quote!(#on),
11999 None => quote! {
12000 match <#inner as #root::core::Model>::SCHEMA.pk_column() {
12001 ::core::option::Option::Some(c) => c,
12002 ::core::option::Option::None => "id",
12003 }
12004 },
12005 };
12006 return Ok(quote! {
12007 ::core::option::Option::Some(#root::core::Relation::fk(
12008 <#inner as #root::core::Model>::SCHEMA.table,
12009 #on,
12010 ))
12011 });
12012 }
12013 match (&attrs.fk, &attrs.o2o) {
12014 (Some(_), Some(_)) => Err(syn::Error::new_spanned(
12015 field,
12016 "`fk` and `o2o` are mutually exclusive",
12017 )),
12018 (Some(to), None) => {
12019 let on = attrs.on.as_deref().unwrap_or("id");
12020 let resolved = if to == "self" { table } else { to };
12026 Ok(quote! {
12027 ::core::option::Option::Some(#root::core::Relation::fk(#resolved, #on))
12028 })
12029 }
12030 (None, Some(to)) => {
12031 let on = attrs.on.as_deref().unwrap_or("id");
12032 let resolved = if to == "self" { table } else { to };
12033 Ok(quote! {
12034 ::core::option::Option::Some(#root::core::Relation::o2o(#resolved, #on))
12035 })
12036 }
12037 (None, None) => {
12038 if attrs.on.is_some() {
12039 return Err(syn::Error::new_spanned(
12040 field,
12041 "`on` requires `fk` or `o2o`",
12042 ));
12043 }
12044 Ok(quote!(::core::option::Option::None))
12045 }
12046 }
12047}
12048
12049#[derive(Clone, Copy, PartialEq, Eq)]
12053enum DetectedKind {
12054 I16,
12055 I32,
12056 I64,
12057 F32,
12058 F64,
12059 Bool,
12060 String,
12061 DateTime,
12062 Date,
12063 Time,
12064 Uuid,
12065 Json,
12066 Decimal,
12067 Binary,
12068 ArrayText,
12070 ArrayInt,
12072 ArrayBigInt,
12074 RangeInt,
12076 RangeBigInt,
12078 RangeNumeric,
12080 RangeDate,
12082 RangeDateTime,
12084 HStore,
12086 Vector,
12090 Geometry,
12094}
12095
12096impl DetectedKind {
12097 fn variant_tokens(self) -> TokenStream2 {
12098 let root = rustango_root();
12099 match self {
12100 Self::I16 => quote!(#root::core::FieldType::I16),
12101 Self::I32 => quote!(#root::core::FieldType::I32),
12102 Self::I64 => quote!(#root::core::FieldType::I64),
12103 Self::F32 => quote!(#root::core::FieldType::F32),
12104 Self::F64 => quote!(#root::core::FieldType::F64),
12105 Self::Bool => quote!(#root::core::FieldType::Bool),
12106 Self::String => quote!(#root::core::FieldType::String),
12107 Self::DateTime => quote!(#root::core::FieldType::DateTime),
12108 Self::Date => quote!(#root::core::FieldType::Date),
12109 Self::Time => quote!(#root::core::FieldType::Time),
12110 Self::Uuid => quote!(#root::core::FieldType::Uuid),
12111 Self::Json => quote!(#root::core::FieldType::Json),
12112 Self::Decimal => quote!(#root::core::FieldType::Decimal),
12113 Self::Binary => quote!(#root::core::FieldType::Binary),
12114 Self::ArrayText => {
12115 quote!(#root::core::FieldType::Array(#root::core::ArrayElem::Text))
12116 }
12117 Self::ArrayInt => {
12118 quote!(#root::core::FieldType::Array(#root::core::ArrayElem::Int))
12119 }
12120 Self::ArrayBigInt => {
12121 quote!(#root::core::FieldType::Array(#root::core::ArrayElem::BigInt))
12122 }
12123 Self::RangeInt => {
12124 quote!(#root::core::FieldType::Range(#root::core::RangeElem::Int))
12125 }
12126 Self::RangeBigInt => {
12127 quote!(#root::core::FieldType::Range(#root::core::RangeElem::BigInt))
12128 }
12129 Self::RangeNumeric => {
12130 quote!(#root::core::FieldType::Range(#root::core::RangeElem::Numeric))
12131 }
12132 Self::RangeDate => {
12133 quote!(#root::core::FieldType::Range(#root::core::RangeElem::Date))
12134 }
12135 Self::RangeDateTime => {
12136 quote!(#root::core::FieldType::Range(#root::core::RangeElem::DateTime))
12137 }
12138 Self::HStore => quote!(#root::core::FieldType::HStore),
12139 Self::Vector => quote!(#root::core::FieldType::Vector(0)),
12142 Self::Geometry => quote!(#root::core::FieldType::Geometry(0)),
12145 }
12146 }
12147
12148 fn is_integer(self) -> bool {
12149 matches!(self, Self::I16 | Self::I32 | Self::I64)
12150 }
12151}
12152
12153#[derive(Clone, Copy)]
12159struct DetectedType<'a> {
12160 kind: DetectedKind,
12161 nullable: bool,
12162 auto: bool,
12163 fk_inner: Option<&'a syn::Type>,
12164}
12165
12166fn auto_inner_type(ty: &syn::Type) -> Option<&syn::Type> {
12171 let Type::Path(TypePath { path, qself: None }) = ty else {
12172 return None;
12173 };
12174 let last = path.segments.last()?;
12175 if last.ident != "Auto" {
12176 return None;
12177 }
12178 let syn::PathArguments::AngleBracketed(args) = &last.arguments else {
12179 return None;
12180 };
12181 args.args.iter().find_map(|a| match a {
12182 syn::GenericArgument::Type(t) => Some(t),
12183 _ => None,
12184 })
12185}
12186
12187fn cast_type(ty: &syn::Type) -> Option<&syn::Type> {
12189 let Type::Path(TypePath { path, qself: None }) = ty else {
12190 return None;
12191 };
12192 let last = path.segments.last()?;
12193 if last.ident == "Cast" {
12194 return Some(ty);
12195 }
12196 if last.ident != "Option" {
12197 return None;
12198 }
12199 let syn::PathArguments::AngleBracketed(args) = &last.arguments else {
12200 return None;
12201 };
12202 match args.args.first()? {
12203 syn::GenericArgument::Type(inner) => cast_type(inner).filter(|_| args.args.len() == 1),
12204 _ => None,
12205 }
12206}
12207
12208fn detect_type(ty: &syn::Type) -> syn::Result<DetectedType<'_>> {
12209 let Type::Path(TypePath { path, qself: None }) = ty else {
12210 return Err(syn::Error::new_spanned(ty, "unsupported field type"));
12211 };
12212 let last = path
12213 .segments
12214 .last()
12215 .ok_or_else(|| syn::Error::new_spanned(ty, "empty type path"))?;
12216
12217 if last.ident == "Option" {
12218 let inner = generic_inner(ty, &last.arguments, "Option")?;
12219 let inner_det = detect_type(inner)?;
12220 if inner_det.nullable {
12221 return Err(syn::Error::new_spanned(
12222 ty,
12223 "nested Option is not supported",
12224 ));
12225 }
12226 if inner_det.auto {
12227 return Err(syn::Error::new_spanned(
12228 ty,
12229 "`Option<Auto<T>>` is not supported — Auto fields are server-assigned and cannot be NULL",
12230 ));
12231 }
12232 return Ok(DetectedType {
12233 nullable: true,
12234 ..inner_det
12235 });
12236 }
12237
12238 if last.ident == "Auto" {
12239 let inner = generic_inner(ty, &last.arguments, "Auto")?;
12240 let inner_det = detect_type(inner)?;
12241 if inner_det.auto {
12242 return Err(syn::Error::new_spanned(ty, "nested Auto is not supported"));
12243 }
12244 if inner_det.nullable {
12245 return Err(syn::Error::new_spanned(
12246 ty,
12247 "`Auto<Option<T>>` is not supported — Auto fields are server-assigned and cannot be NULL",
12248 ));
12249 }
12250 if inner_det.fk_inner.is_some() {
12251 return Err(syn::Error::new_spanned(
12252 ty,
12253 "`Auto<ForeignKey<T>>` is not supported — Auto is for server-assigned PKs, ForeignKey is for parent references",
12254 ));
12255 }
12256 if !matches!(
12257 inner_det.kind,
12258 DetectedKind::I32 | DetectedKind::I64 | DetectedKind::Uuid | DetectedKind::DateTime
12259 ) {
12260 return Err(syn::Error::new_spanned(
12261 ty,
12262 "`Auto<T>` only supports integers (`i32` → SERIAL, `i64` → BIGSERIAL), \
12263 `uuid::Uuid` (DEFAULT gen_random_uuid()), or `chrono::DateTime<chrono::Utc>` \
12264 (DEFAULT now())",
12265 ));
12266 }
12267 return Ok(DetectedType {
12268 auto: true,
12269 ..inner_det
12270 });
12271 }
12272
12273 if last.ident == "ForeignKey" {
12274 let (inner, key_ty) = generic_pair(ty, &last.arguments, "ForeignKey")?;
12275 let kind = match key_ty {
12283 Some(k) => detect_type(k)?.kind,
12284 None => DetectedKind::I64,
12285 };
12286 return Ok(DetectedType {
12287 kind,
12288 nullable: false,
12289 auto: false,
12290 fk_inner: Some(inner),
12291 });
12292 }
12293
12294 let kind = match last.ident.to_string().as_str() {
12295 "i16" => DetectedKind::I16,
12296 "i32" => DetectedKind::I32,
12297 "i64" => DetectedKind::I64,
12298 "f32" => DetectedKind::F32,
12299 "f64" => DetectedKind::F64,
12300 "bool" => DetectedKind::Bool,
12301 "String" => DetectedKind::String,
12302 "DateTime" => DetectedKind::DateTime,
12303 "NaiveDate" => DetectedKind::Date,
12304 "NaiveTime" => DetectedKind::Time,
12305 "Uuid" => DetectedKind::Uuid,
12306 "Value" => DetectedKind::Json,
12307 "Decimal" => DetectedKind::Decimal,
12308 "Vec" => {
12312 let (inner, _) = generic_pair(ty, &last.arguments, "Vec")?;
12313 if let Type::Path(TypePath { path, qself: None }) = inner {
12314 if let Some(seg) = path.segments.last() {
12315 if seg.ident == "u8" && seg.arguments.is_empty() {
12316 return Ok(DetectedType {
12317 kind: DetectedKind::Binary,
12318 nullable: false,
12319 auto: false,
12320 fk_inner: None,
12321 });
12322 }
12323 }
12324 }
12325 return Err(syn::Error::new_spanned(
12326 ty,
12327 "unsupported `Vec<T>` field — only `Vec<u8>` (→ Binary) is supported; \
12328 for a PostgreSQL array column use `Array<String>` / `Array<i32>` / `Array<i64>`",
12329 ));
12330 }
12331 "Array" => {
12334 let (inner, _) = generic_pair(ty, &last.arguments, "Array")?;
12335 let elem = match inner {
12336 Type::Path(TypePath { path, qself: None }) => {
12337 path.segments.last().map(|s| s.ident.to_string())
12338 }
12339 _ => None,
12340 };
12341 let kind = match elem.as_deref() {
12342 Some("String") => DetectedKind::ArrayText,
12343 Some("i32") => DetectedKind::ArrayInt,
12344 Some("i64") => DetectedKind::ArrayBigInt,
12345 _ => {
12346 return Err(syn::Error::new_spanned(
12347 ty,
12348 "unsupported `Array<T>` element — only `Array<String>` (→ text[]), \
12349 `Array<i32>` (→ integer[]), and `Array<i64>` (→ bigint[]) are supported (#341)",
12350 ));
12351 }
12352 };
12353 return Ok(DetectedType {
12354 kind,
12355 nullable: false,
12356 auto: false,
12357 fk_inner: None,
12358 });
12359 }
12360 "Range" => {
12364 let (inner, _) = generic_pair(ty, &last.arguments, "Range")?;
12365 let elem = match inner {
12366 Type::Path(TypePath { path, qself: None }) => {
12367 path.segments.last().map(|s| s.ident.to_string())
12368 }
12369 _ => None,
12370 };
12371 let kind = match elem.as_deref() {
12372 Some("i32") => DetectedKind::RangeInt,
12373 Some("i64") => DetectedKind::RangeBigInt,
12374 Some("Decimal") => DetectedKind::RangeNumeric,
12375 Some("NaiveDate") => DetectedKind::RangeDate,
12376 Some("DateTime") => DetectedKind::RangeDateTime,
12377 _ => {
12378 return Err(syn::Error::new_spanned(
12379 ty,
12380 "unsupported `Range<T>` element — only `Range<i32>` (→ int4range), \
12381 `Range<i64>` (→ int8range), `Range<Decimal>` (→ numrange), \
12382 `Range<NaiveDate>` (→ daterange), and `Range<DateTime<Utc>>` \
12383 (→ tstzrange) are supported (#343)",
12384 ));
12385 }
12386 };
12387 return Ok(DetectedType {
12388 kind,
12389 nullable: false,
12390 auto: false,
12391 fk_inner: None,
12392 });
12393 }
12394 "Cast" => {
12399 return Ok(DetectedType {
12400 kind: DetectedKind::String,
12401 nullable: false,
12402 auto: false,
12403 fk_inner: None,
12404 });
12405 }
12406 "HStore" => {
12409 return Ok(DetectedType {
12410 kind: DetectedKind::HStore,
12411 nullable: false,
12412 auto: false,
12413 fk_inner: None,
12414 });
12415 }
12416 "Vector" => {
12419 return Ok(DetectedType {
12420 kind: DetectedKind::Vector,
12421 nullable: false,
12422 auto: false,
12423 fk_inner: None,
12424 });
12425 }
12426 "Point" => {
12429 return Ok(DetectedType {
12430 kind: DetectedKind::Geometry,
12431 nullable: false,
12432 auto: false,
12433 fk_inner: None,
12434 });
12435 }
12436 other => {
12437 return Err(syn::Error::new_spanned(
12438 ty,
12439 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)"),
12440 ));
12441 }
12442 };
12443 Ok(DetectedType {
12444 kind,
12445 nullable: false,
12446 auto: false,
12447 fk_inner: None,
12448 })
12449}
12450
12451fn generic_inner<'a>(
12452 ty: &'a Type,
12453 arguments: &'a PathArguments,
12454 wrapper: &str,
12455) -> syn::Result<&'a Type> {
12456 let PathArguments::AngleBracketed(args) = arguments else {
12457 return Err(syn::Error::new_spanned(
12458 ty,
12459 format!("{wrapper} requires a generic argument"),
12460 ));
12461 };
12462 args.args
12463 .iter()
12464 .find_map(|a| match a {
12465 GenericArgument::Type(t) => Some(t),
12466 _ => None,
12467 })
12468 .ok_or_else(|| {
12469 syn::Error::new_spanned(ty, format!("{wrapper}<T> requires a type argument"))
12470 })
12471}
12472
12473fn generic_pair<'a>(
12477 ty: &'a Type,
12478 arguments: &'a PathArguments,
12479 wrapper: &str,
12480) -> syn::Result<(&'a Type, Option<&'a Type>)> {
12481 let PathArguments::AngleBracketed(args) = arguments else {
12482 return Err(syn::Error::new_spanned(
12483 ty,
12484 format!("{wrapper} requires a generic argument"),
12485 ));
12486 };
12487 let mut types = args.args.iter().filter_map(|a| match a {
12488 GenericArgument::Type(t) => Some(t),
12489 _ => None,
12490 });
12491 let first = types.next().ok_or_else(|| {
12492 syn::Error::new_spanned(ty, format!("{wrapper}<T> requires a type argument"))
12493 })?;
12494 let second = types.next();
12495 Ok((first, second))
12496}
12497
12498fn to_snake_case(s: &str) -> String {
12499 let mut out = String::with_capacity(s.len() + 4);
12500 for (i, ch) in s.chars().enumerate() {
12501 if ch.is_ascii_uppercase() {
12502 if i > 0 {
12503 out.push('_');
12504 }
12505 out.push(ch.to_ascii_lowercase());
12506 } else {
12507 out.push(ch);
12508 }
12509 }
12510 out
12511}
12512
12513#[derive(Default)]
12519struct FormFieldAttrs {
12520 min: Option<i64>,
12521 max: Option<i64>,
12522 min_length: Option<u32>,
12523 max_length: Option<u32>,
12524 clean: Option<syn::Ident>,
12531}
12532
12533#[derive(Default)]
12536struct FormContainerAttrs {
12537 validate: Option<syn::Ident>,
12543}
12544
12545#[derive(Clone, Copy)]
12547enum FormFieldKind {
12548 String,
12549 I16,
12550 I32,
12551 I64,
12552 F32,
12553 F64,
12554 Bool,
12555}
12556
12557impl FormFieldKind {
12558 fn parse_method(self) -> &'static str {
12559 match self {
12560 Self::I16 => "i16",
12561 Self::I32 => "i32",
12562 Self::I64 => "i64",
12563 Self::F32 => "f32",
12564 Self::F64 => "f64",
12565 Self::String | Self::Bool => "",
12568 }
12569 }
12570}
12571
12572fn form_local(name: &str) -> syn::Ident {
12574 syn::Ident::new(name, proc_macro2::Span::mixed_site())
12575}
12576
12577fn expand_form(input: &DeriveInput) -> syn::Result<TokenStream2> {
12578 let root = rustango_root();
12579 let (errs, cleaned, data_param) = (
12580 form_local("__errors"),
12581 form_local("__cleaned"),
12582 form_local("data"),
12583 );
12584 let struct_name = &input.ident;
12585
12586 let Data::Struct(data) = &input.data else {
12587 return Err(syn::Error::new_spanned(
12588 struct_name,
12589 "Form can only be derived on structs",
12590 ));
12591 };
12592 let Fields::Named(named) = &data.fields else {
12593 return Err(syn::Error::new_spanned(
12594 struct_name,
12595 "Form requires a struct with named fields",
12596 ));
12597 };
12598
12599 let container = parse_form_container_attrs(input)?;
12601 let post_field_clean: Vec<TokenStream2> = Vec::new();
12602 let _ = post_field_clean;
12603
12604 let mut field_blocks: Vec<TokenStream2> = Vec::with_capacity(named.named.len());
12605 let mut field_idents: Vec<&syn::Ident> = Vec::with_capacity(named.named.len());
12606
12607 for field in &named.named {
12608 let ident = field
12609 .ident
12610 .as_ref()
12611 .ok_or_else(|| syn::Error::new(field.span(), "tuple structs are not supported"))?;
12612 let attrs = parse_form_field_attrs(field)?;
12613 let (kind, nullable) = detect_form_field(&field.ty, field.span())?;
12614 let is_string = matches!(kind, FormFieldKind::String);
12615 let is_number = !is_string && !matches!(kind, FormFieldKind::Bool);
12616 if !is_string && (attrs.min_length.is_some() || attrs.max_length.is_some()) {
12617 return Err(syn::Error::new(
12618 field.span(),
12619 "`min_length` / `max_length` need a String field",
12620 ));
12621 }
12622 if !is_number && (attrs.min.is_some() || attrs.max.is_some()) {
12623 return Err(syn::Error::new(
12624 field.span(),
12625 "`min` / `max` need a numeric field",
12626 ));
12627 }
12628
12629 let name_lit = ident_name(ident);
12630 let parse_block = render_form_field_parse(ident, &name_lit, kind, nullable, &attrs);
12631 let clean_block = if let Some(clean_fn) = &attrs.clean {
12637 quote! {
12638 if #errs.fields().get(#name_lit).is_none() {
12639 match Self::#clean_fn(&#ident) {
12640 ::core::result::Result::Ok(#cleaned) => { #ident = #cleaned; }
12641 ::core::result::Result::Err(__msg) => {
12642 #errs.add(#name_lit, __msg);
12643 }
12644 }
12645 }
12646 }
12647 } else {
12648 quote! {}
12649 };
12650 field_blocks.push(quote! {
12651 #parse_block
12652 #clean_block
12653 });
12654 field_idents.push(ident);
12655 }
12656
12657 let cross_field_call = if let Some(validate_fn) = &container.validate {
12662 quote! {
12663 if #errs.is_empty() {
12664 let __candidate = Self { #( #field_idents ),* };
12665 if let ::core::result::Result::Err(__other) = Self::#validate_fn(&__candidate) {
12666 #errs.merge(__other);
12667 }
12668 if !#errs.is_empty() {
12669 return ::core::result::Result::Err(#errs);
12670 }
12671 return ::core::result::Result::Ok(__candidate);
12672 }
12673 }
12674 } else {
12675 quote! {}
12676 };
12677
12678 Ok(quote! {
12679 impl #root::forms::Form for #struct_name {
12680 fn parse(
12681 #data_param: &::std::collections::HashMap<::std::string::String, ::std::string::String>,
12682 ) -> ::core::result::Result<Self, #root::forms::FormErrors> {
12683 let mut #errs = #root::forms::FormErrors::default();
12684 #( #field_blocks )*
12685 #cross_field_call
12686 if !#errs.is_empty() {
12687 return ::core::result::Result::Err(#errs);
12688 }
12689 ::core::result::Result::Ok(Self {
12690 #( #field_idents ),*
12691 })
12692 }
12693 }
12694 })
12695}
12696
12697fn parse_form_container_attrs(input: &DeriveInput) -> syn::Result<FormContainerAttrs> {
12698 let mut out = FormContainerAttrs::default();
12699 for attr in &input.attrs {
12700 if !attr.path().is_ident("form") {
12701 continue;
12702 }
12703 attr.parse_nested_meta(|meta| {
12704 if meta.path.is_ident("validate") {
12705 let s: LitStr = meta.value()?.parse()?;
12706 out.validate = Some(syn::Ident::new(&s.value(), s.span()));
12707 return Ok(());
12708 }
12709 Err(meta.error("unknown form container attribute (supported: `validate`)"))
12710 })?;
12711 }
12712 Ok(out)
12713}
12714
12715fn parse_form_field_attrs(field: &syn::Field) -> syn::Result<FormFieldAttrs> {
12716 let mut out = FormFieldAttrs::default();
12717 for attr in &field.attrs {
12718 if !attr.path().is_ident("form") {
12719 continue;
12720 }
12721 attr.parse_nested_meta(|meta| {
12722 if meta.path.is_ident("min") {
12723 let lit: syn::LitInt = meta.value()?.parse()?;
12724 out.min = Some(lit.base10_parse::<i64>()?);
12725 return Ok(());
12726 }
12727 if meta.path.is_ident("max") {
12728 let lit: syn::LitInt = meta.value()?.parse()?;
12729 out.max = Some(lit.base10_parse::<i64>()?);
12730 return Ok(());
12731 }
12732 if meta.path.is_ident("min_length") {
12733 let lit: syn::LitInt = meta.value()?.parse()?;
12734 out.min_length = Some(lit.base10_parse::<u32>()?);
12735 return Ok(());
12736 }
12737 if meta.path.is_ident("max_length") {
12738 let lit: syn::LitInt = meta.value()?.parse()?;
12739 out.max_length = Some(lit.base10_parse::<u32>()?);
12740 return Ok(());
12741 }
12742 if meta.path.is_ident("clean") {
12743 let s: LitStr = meta.value()?.parse()?;
12744 out.clean = Some(syn::Ident::new(&s.value(), s.span()));
12745 return Ok(());
12746 }
12747 Err(meta.error(
12748 "unknown form field attribute (supported: `min`, `max`, `min_length`, `max_length`, `clean`)",
12749 ))
12750 })?;
12751 }
12752 Ok(out)
12753}
12754
12755fn detect_form_field(ty: &Type, span: proc_macro2::Span) -> syn::Result<(FormFieldKind, bool)> {
12756 let Type::Path(TypePath { path, qself: None }) = ty else {
12757 return Err(syn::Error::new(
12758 span,
12759 "Form field must be a simple typed path (e.g. `String`, `i32`, `Option<String>`)",
12760 ));
12761 };
12762 let last = path
12763 .segments
12764 .last()
12765 .ok_or_else(|| syn::Error::new(span, "empty type path"))?;
12766
12767 if last.ident == "Option" {
12768 let inner = generic_inner(ty, &last.arguments, "Option")?;
12769 let (kind, nested) = detect_form_field(inner, span)?;
12770 if nested {
12771 return Err(syn::Error::new(
12772 span,
12773 "nested Option in Form fields is not supported",
12774 ));
12775 }
12776 return Ok((kind, true));
12777 }
12778
12779 let kind = match last.ident.to_string().as_str() {
12780 "String" => FormFieldKind::String,
12781 "i16" => FormFieldKind::I16,
12782 "i32" => FormFieldKind::I32,
12783 "i64" => FormFieldKind::I64,
12784 "f32" => FormFieldKind::F32,
12785 "f64" => FormFieldKind::F64,
12786 "bool" => FormFieldKind::Bool,
12787 other => {
12788 return Err(syn::Error::new(
12789 span,
12790 format!(
12791 "Form field type `{other}` is not supported in v0.8 — use String / \
12792 i16 / i32 / i64 / f32 / f64 / bool, optionally wrapped in Option<…>"
12793 ),
12794 ));
12795 }
12796 };
12797 Ok((kind, false))
12798}
12799
12800#[allow(clippy::too_many_lines)]
12801fn render_form_field_parse(
12802 ident: &syn::Ident,
12803 name_lit: &str,
12804 kind: FormFieldKind,
12805 nullable: bool,
12806 attrs: &FormFieldAttrs,
12807) -> TokenStream2 {
12808 let (errs, data_param) = (form_local("__errors"), form_local("data"));
12809 let lookup = quote! {
12811 let __raw: ::core::option::Option<&::std::string::String> = #data_param.get(#name_lit);
12812 };
12813
12814 let root = rustango_root();
12815 let checkbox = quote! {
12817 match #root::forms::checkbox(__s) {
12818 ::core::option::Option::Some(__b) => __b,
12819 ::core::option::Option::None => {
12820 #errs.add(#name_lit, "Enter true or false.");
12821 false
12822 }
12823 }
12824 };
12825 let parsed_value = match kind {
12826 FormFieldKind::Bool if nullable => quote! {
12828 let __v: ::core::option::Option<bool> = match __raw {
12829 ::core::option::Option::None => ::core::option::Option::None,
12830 ::core::option::Option::Some(__s) if __s.is_empty() => {
12831 ::core::option::Option::None
12832 }
12833 ::core::option::Option::Some(__s) => ::core::option::Option::Some(#checkbox),
12834 };
12835 },
12836 FormFieldKind::Bool => quote! {
12837 let __v: bool = match __raw {
12838 ::core::option::Option::None => false,
12839 ::core::option::Option::Some(__s) => #checkbox,
12840 };
12841 },
12842 FormFieldKind::String => {
12843 if nullable {
12844 quote! {
12845 let __v: ::core::option::Option<::std::string::String> = match __raw {
12846 ::core::option::Option::None => ::core::option::Option::None,
12847 ::core::option::Option::Some(__s) if __s.is_empty() => {
12848 ::core::option::Option::None
12849 }
12850 ::core::option::Option::Some(__s) => {
12851 ::core::option::Option::Some(::core::clone::Clone::clone(__s))
12852 }
12853 };
12854 }
12855 } else {
12856 quote! {
12857 let __v: ::std::string::String = match __raw {
12858 ::core::option::Option::Some(__s) if !__s.is_empty() => {
12859 ::core::clone::Clone::clone(__s)
12860 }
12861 _ => {
12862 #errs.add(#name_lit, "This field is required.");
12863 ::std::string::String::new()
12864 }
12865 };
12866 }
12867 }
12868 }
12869 FormFieldKind::I16
12870 | FormFieldKind::I32
12871 | FormFieldKind::I64
12872 | FormFieldKind::F32
12873 | FormFieldKind::F64 => {
12874 let parse_ty = syn::Ident::new(kind.parse_method(), proc_macro2::Span::call_site());
12875 let ty_lit = kind.parse_method();
12876 let default_val = match kind {
12877 FormFieldKind::I16 => quote! { 0i16 },
12878 FormFieldKind::I32 => quote! { 0i32 },
12879 FormFieldKind::I64 => quote! { 0i64 },
12880 FormFieldKind::F32 => quote! { 0f32 },
12881 FormFieldKind::F64 => quote! { 0f64 },
12882 _ => quote! { Default::default() },
12883 };
12884 let finite = if matches!(kind, FormFieldKind::F32 | FormFieldKind::F64) {
12886 quote! {
12887 .and_then(|__n| if __n.is_finite() {
12888 ::core::result::Result::Ok(__n)
12889 } else {
12890 ::core::result::Result::Err(::std::string::String::from("not a finite number"))
12891 })
12892 }
12893 } else {
12894 quote! {}
12895 };
12896 if nullable {
12897 quote! {
12898 let __v: ::core::option::Option<#parse_ty> = match __raw {
12899 ::core::option::Option::None => ::core::option::Option::None,
12900 ::core::option::Option::Some(__s) if __s.is_empty() => {
12901 ::core::option::Option::None
12902 }
12903 ::core::option::Option::Some(__s) => {
12904 match __s.parse::<#parse_ty>().map_err(|__e| ::std::string::ToString::to_string(&__e)) #finite {
12905 ::core::result::Result::Ok(__n) => {
12906 ::core::option::Option::Some(__n)
12907 }
12908 ::core::result::Result::Err(__e) => {
12909 #errs.add(
12910 #name_lit,
12911 ::std::format!("Enter a valid {} value: {}", #ty_lit, __e),
12912 );
12913 ::core::option::Option::None
12914 }
12915 }
12916 }
12917 };
12918 }
12919 } else {
12920 quote! {
12921 let __v: #parse_ty = match __raw {
12922 ::core::option::Option::Some(__s) if !__s.is_empty() => {
12923 match __s.parse::<#parse_ty>().map_err(|__e| ::std::string::ToString::to_string(&__e)) #finite {
12924 ::core::result::Result::Ok(__n) => __n,
12925 ::core::result::Result::Err(__e) => {
12926 #errs.add(
12927 #name_lit,
12928 ::std::format!("Enter a valid {} value: {}", #ty_lit, __e),
12929 );
12930 #default_val
12931 }
12932 }
12933 }
12934 _ => {
12935 #errs.add(#name_lit, "This field is required.");
12936 #default_val
12937 }
12938 };
12939 }
12940 }
12941 }
12942 };
12943
12944 let validators = render_form_validators(name_lit, kind, nullable, attrs);
12945
12946 quote! {
12947 let mut #ident = {
12951 #lookup
12952 #parsed_value
12953 #validators
12954 __v
12955 };
12956 }
12957}
12958
12959fn render_form_validators(
12960 name_lit: &str,
12961 kind: FormFieldKind,
12962 nullable: bool,
12963 attrs: &FormFieldAttrs,
12964) -> TokenStream2 {
12965 let errs = form_local("__errors");
12966 let mut checks: Vec<TokenStream2> = Vec::new();
12967
12968 let val_ref = if nullable {
12969 quote! { __v.as_ref() }
12970 } else {
12971 quote! { ::core::option::Option::Some(&__v) }
12972 };
12973
12974 let is_string = matches!(kind, FormFieldKind::String);
12975 let is_numeric = matches!(
12976 kind,
12977 FormFieldKind::I16
12978 | FormFieldKind::I32
12979 | FormFieldKind::I64
12980 | FormFieldKind::F32
12981 | FormFieldKind::F64
12982 );
12983
12984 if is_string {
12985 if let Some(min_len) = attrs.min_length {
12986 let min_len_usize = min_len as usize;
12987 checks.push(quote! {
12988 if let ::core::option::Option::Some(__s) = #val_ref {
12989 if __s.chars().count() < #min_len_usize {
12990 #errs.add(
12991 #name_lit,
12992 ::std::format!("Ensure this value has at least {} characters.", #min_len_usize),
12993 );
12994 }
12995 }
12996 });
12997 }
12998 if let Some(max_len) = attrs.max_length {
12999 let max_len_usize = max_len as usize;
13000 checks.push(quote! {
13001 if let ::core::option::Option::Some(__s) = #val_ref {
13002 if __s.chars().count() > #max_len_usize {
13003 #errs.add(
13004 #name_lit,
13005 ::std::format!("Ensure this value has at most {} characters.", #max_len_usize),
13006 );
13007 }
13008 }
13009 });
13010 }
13011 }
13012
13013 if is_numeric {
13014 if let Some(min) = attrs.min {
13015 checks.push(quote! {
13016 if let ::core::option::Option::Some(__n) = #val_ref {
13017 if (*__n as f64) < (#min as f64) {
13018 #errs.add(
13019 #name_lit,
13020 ::std::format!("Ensure this value is greater than or equal to {}.", #min),
13021 );
13022 }
13023 }
13024 });
13025 }
13026 if let Some(max) = attrs.max {
13027 checks.push(quote! {
13028 if let ::core::option::Option::Some(__n) = #val_ref {
13029 if (*__n as f64) > (#max as f64) {
13030 #errs.add(
13031 #name_lit,
13032 ::std::format!("Ensure this value is less than or equal to {}.", #max),
13033 );
13034 }
13035 }
13036 });
13037 }
13038 }
13039
13040 quote! { #( #checks )* }
13041}
13042
13043struct ViewSetAttrs {
13048 model: syn::Path,
13049 filter_backends: Vec<syn::Expr>,
13051 fields: Vec<String>,
13052 filter_fields: Vec<String>,
13053 search_fields: Vec<String>,
13054 ordering: Vec<(String, bool)>,
13056 page_size: Option<usize>,
13057 read_only: bool,
13058 allow_anonymous: bool,
13059 perms: ViewSetPermsAttrs,
13060 serializer: Option<syn::Path>,
13065}
13066
13067#[derive(Default)]
13068struct ViewSetPermsAttrs {
13069 list: Vec<String>,
13070 retrieve: Vec<String>,
13071 create: Vec<String>,
13072 update: Vec<String>,
13073 destroy: Vec<String>,
13074}
13075
13076fn expand_viewset(input: &DeriveInput) -> syn::Result<TokenStream2> {
13077 let root = rustango_root();
13078 let struct_name = &input.ident;
13079
13080 match &input.data {
13082 Data::Struct(s) => match &s.fields {
13083 Fields::Unit | Fields::Named(_) => {}
13084 Fields::Unnamed(_) => {
13085 return Err(syn::Error::new_spanned(
13086 struct_name,
13087 "ViewSet can only be derived on a unit struct or an empty named struct",
13088 ));
13089 }
13090 },
13091 _ => {
13092 return Err(syn::Error::new_spanned(
13093 struct_name,
13094 "ViewSet can only be derived on a struct",
13095 ));
13096 }
13097 }
13098
13099 let attrs = parse_viewset_attrs(input)?;
13100 let model_path = &attrs.model;
13101
13102 let field_lits = attrs.fields.iter().map(|f| f.as_str());
13103
13104 let filter_fields_call = if attrs.filter_fields.is_empty() {
13105 quote!()
13106 } else {
13107 let lits = attrs.filter_fields.iter().map(|f| f.as_str());
13108 quote!(.filter_fields(&[ #(#lits),* ]))
13109 };
13110
13111 let search_fields_call = if attrs.search_fields.is_empty() {
13112 quote!()
13113 } else {
13114 let lits = attrs.search_fields.iter().map(|f| f.as_str());
13115 quote!(.search_fields(&[ #(#lits),* ]))
13116 };
13117
13118 let ordering_call = if attrs.ordering.is_empty() {
13119 quote!()
13120 } else {
13121 let pairs = attrs.ordering.iter().map(|(f, desc)| {
13122 let f = f.as_str();
13123 quote!((#f, #desc))
13124 });
13125 quote!(.ordering(&[ #(#pairs),* ]))
13126 };
13127
13128 let filter_backends = &attrs.filter_backends;
13129 let filter_backend_calls = quote!( #( .filter_backend(#filter_backends) )* );
13130
13131 let page_size_call = if let Some(n) = attrs.page_size {
13132 quote!(.page_size(#n))
13133 } else {
13134 quote!()
13135 };
13136
13137 let read_only_call = if attrs.read_only {
13138 quote!(.read_only())
13139 } else {
13140 quote!()
13141 };
13142 let allow_anonymous_call = if attrs.allow_anonymous {
13143 quote!(.allow_anonymous())
13144 } else {
13145 quote!()
13146 };
13147
13148 let serializer_call = if let Some(ref ser) = attrs.serializer {
13152 quote!(.serializer::<#ser>())
13153 } else {
13154 quote!()
13155 };
13156
13157 let perms = &attrs.perms;
13158 let perms_call = if perms.list.is_empty()
13159 && perms.retrieve.is_empty()
13160 && perms.create.is_empty()
13161 && perms.update.is_empty()
13162 && perms.destroy.is_empty()
13163 {
13164 quote!()
13165 } else {
13166 let list_lits = perms.list.iter().map(|s| s.as_str());
13167 let retrieve_lits = perms.retrieve.iter().map(|s| s.as_str());
13168 let create_lits = perms.create.iter().map(|s| s.as_str());
13169 let update_lits = perms.update.iter().map(|s| s.as_str());
13170 let destroy_lits = perms.destroy.iter().map(|s| s.as_str());
13171 quote! {
13172 .permissions(#root::viewset::ViewSetPerms {
13173 list: ::std::vec![ #(#list_lits.to_owned()),* ],
13174 retrieve: ::std::vec![ #(#retrieve_lits.to_owned()),* ],
13175 create: ::std::vec![ #(#create_lits.to_owned()),* ],
13176 update: ::std::vec![ #(#update_lits.to_owned()),* ],
13177 destroy: ::std::vec![ #(#destroy_lits.to_owned()),* ],
13178 })
13179 }
13180 };
13181
13182 Ok(quote! {
13183 impl #struct_name {
13184 pub fn router(
13193 prefix: &str,
13194 pool: impl ::core::convert::Into<#root::sql::Pool>,
13195 ) -> #root::__axum::Router {
13196 #root::viewset::ViewSet::for_model(
13197 <#model_path as #root::core::Model>::SCHEMA,
13198 &[ #(#field_lits),* ],
13199 )
13200 #filter_fields_call
13201 #filter_backend_calls
13202 #search_fields_call
13203 #ordering_call
13204 #page_size_call
13205 #perms_call
13206 #read_only_call
13207 #allow_anonymous_call
13208 #serializer_call
13209 .router(prefix, pool.into())
13210 }
13211 }
13212 })
13213}
13214
13215fn parse_viewset_attrs(input: &DeriveInput) -> syn::Result<ViewSetAttrs> {
13216 let mut model: Option<syn::Path> = None;
13217 let mut fields: Option<Vec<String>> = None;
13218 let mut filter_fields: Vec<String> = Vec::new();
13219 let mut search_fields: Vec<String> = Vec::new();
13220 let mut ordering: Vec<(String, bool)> = Vec::new();
13221 let mut page_size: Option<usize> = None;
13222 let mut read_only = false;
13223 let mut allow_anonymous = false;
13224 let mut perms = ViewSetPermsAttrs::default();
13225 let mut serializer: Option<syn::Path> = None;
13226 let mut filter_backends: Vec<syn::Expr> = Vec::new();
13227
13228 for attr in &input.attrs {
13229 if !attr.path().is_ident("viewset") {
13230 continue;
13231 }
13232 attr.parse_nested_meta(|meta| {
13233 if meta.path.is_ident("model") {
13234 let path: syn::Path = meta.value()?.parse()?;
13235 model = Some(path);
13236 return Ok(());
13237 }
13238 if meta.path.is_ident("serializer") {
13239 let path: syn::Path = meta.value()?.parse()?;
13240 serializer = Some(path);
13241 return Ok(());
13242 }
13243 if meta.path.is_ident("fields") {
13244 let s: LitStr = meta.value()?.parse()?;
13245 fields = Some(split_field_list(&s.value()));
13246 return Ok(());
13247 }
13248 if meta.path.is_ident("filter_backend") {
13249 filter_backends.push(meta.value()?.parse()?);
13250 return Ok(());
13251 }
13252 if meta.path.is_ident("filter_fields") {
13253 let s: LitStr = meta.value()?.parse()?;
13254 filter_fields = split_field_list(&s.value());
13255 return Ok(());
13256 }
13257 if meta.path.is_ident("search_fields") {
13258 let s: LitStr = meta.value()?.parse()?;
13259 search_fields = split_field_list(&s.value());
13260 return Ok(());
13261 }
13262 if meta.path.is_ident("ordering") {
13263 let s: LitStr = meta.value()?.parse()?;
13264 ordering = parse_ordering_list(&s.value());
13265 return Ok(());
13266 }
13267 if meta.path.is_ident("page_size") {
13268 let lit: syn::LitInt = meta.value()?.parse()?;
13269 page_size = Some(lit.base10_parse::<usize>()?);
13270 return Ok(());
13271 }
13272 if meta.path.is_ident("read_only") {
13273 read_only = true;
13274 return Ok(());
13275 }
13276 if meta.path.is_ident("allow_anonymous") {
13277 allow_anonymous = true;
13278 return Ok(());
13279 }
13280 if meta.path.is_ident("permissions") {
13281 meta.parse_nested_meta(|inner| {
13282 let parse_codenames = |inner: &syn::meta::ParseNestedMeta| -> syn::Result<Vec<String>> {
13283 let s: LitStr = inner.value()?.parse()?;
13284 Ok(split_field_list(&s.value()))
13285 };
13286 if inner.path.is_ident("list") {
13287 perms.list = parse_codenames(&inner)?;
13288 } else if inner.path.is_ident("retrieve") {
13289 perms.retrieve = parse_codenames(&inner)?;
13290 } else if inner.path.is_ident("create") {
13291 perms.create = parse_codenames(&inner)?;
13292 } else if inner.path.is_ident("update") {
13293 perms.update = parse_codenames(&inner)?;
13294 } else if inner.path.is_ident("destroy") {
13295 perms.destroy = parse_codenames(&inner)?;
13296 } else {
13297 return Err(inner.error(
13298 "unknown permissions key (supported: list, retrieve, create, update, destroy)",
13299 ));
13300 }
13301 Ok(())
13302 })?;
13303 return Ok(());
13304 }
13305 Err(meta.error(
13306 "unknown viewset attribute (supported: model, fields, filter_fields, \
13307 filter_backend, search_fields, ordering, page_size, read_only, allow_anonymous, serializer, \
13308 permissions(...))",
13309 ))
13310 })?;
13311 }
13312
13313 let model = model.ok_or_else(|| {
13314 syn::Error::new_spanned(&input.ident, "`#[viewset(model = SomeModel)]` is required")
13315 })?;
13316 let fields = fields.ok_or_else(|| {
13318 syn::Error::new_spanned(
13319 &input.ident,
13320 "`#[viewset(fields = \"id, title\")]` is required: the fields the API renders and writes",
13321 )
13322 })?;
13323
13324 Ok(ViewSetAttrs {
13325 model,
13326 filter_backends,
13327 fields,
13328 filter_fields,
13329 search_fields,
13330 ordering,
13331 page_size,
13332 read_only,
13333 allow_anonymous,
13334 perms,
13335 serializer,
13336 })
13337}
13338
13339struct SerializerContainerAttrs {
13342 model: syn::Path,
13343 cross_validate: Option<syn::Ident>,
13351}
13352
13353#[derive(Default)]
13354struct SerializerFieldAttrs {
13355 read_only: bool,
13356 write_only: bool,
13357 source: Option<String>,
13358 skip: bool,
13359 method: Option<String>,
13363 validate: Option<String>,
13368 nested: bool,
13378 nested_strict: bool,
13383 many: Option<syn::Type>,
13392 slug: Option<String>,
13403 max_length: Option<u64>,
13407 min_length: Option<u64>,
13410 min: Option<i64>,
13413 max: Option<i64>,
13416}
13417
13418fn parse_serializer_container_attrs(input: &DeriveInput) -> syn::Result<SerializerContainerAttrs> {
13419 let mut model: Option<syn::Path> = None;
13420 let mut cross_validate: Option<syn::Ident> = None;
13421 for attr in &input.attrs {
13422 if !attr.path().is_ident("serializer") {
13423 continue;
13424 }
13425 attr.parse_nested_meta(|meta| {
13426 if meta.path.is_ident("model") {
13427 let _eq: syn::Token![=] = meta.input.parse()?;
13428 model = Some(meta.input.parse()?);
13429 return Ok(());
13430 }
13431 if meta.path.is_ident("validate") {
13432 let s: LitStr = meta.value()?.parse()?;
13437 cross_validate = Some(syn::Ident::new(&s.value(), s.span()));
13438 return Ok(());
13439 }
13440 Err(meta.error(
13441 "unknown serializer container attribute \
13442 (supported: `model`, `validate`)",
13443 ))
13444 })?;
13445 }
13446 let model = model.ok_or_else(|| {
13447 syn::Error::new_spanned(
13448 &input.ident,
13449 "`#[serializer(model = SomeModel)]` is required",
13450 )
13451 })?;
13452 Ok(SerializerContainerAttrs {
13453 model,
13454 cross_validate,
13455 })
13456}
13457
13458#[derive(Default)]
13461struct SerdeWireAttrs {
13462 rename_ser: Option<String>,
13463 rename_de: Option<String>,
13464 skip_ser: bool,
13465 skip_de: bool,
13466 skip_ser_if: Option<syn::ExprPath>,
13467}
13468
13469#[derive(Clone, Copy)]
13471enum SerdeRenameRule {
13472 Lower,
13473 Upper,
13474 Pascal,
13475 Camel,
13476 ScreamingSnake,
13477 Kebab,
13478 ScreamingKebab,
13479}
13480
13481impl SerdeRenameRule {
13482 fn parse(s: &LitStr) -> syn::Result<Self> {
13483 Ok(match s.value().as_str() {
13484 "lowercase" | "snake_case" => Self::Lower,
13485 "UPPERCASE" => Self::Upper,
13486 "PascalCase" => Self::Pascal,
13487 "camelCase" => Self::Camel,
13488 "SCREAMING_SNAKE_CASE" => Self::ScreamingSnake,
13489 "kebab-case" => Self::Kebab,
13490 "SCREAMING-KEBAB-CASE" => Self::ScreamingKebab,
13491 _ => {
13492 return Err(syn::Error::new_spanned(
13493 s,
13494 "unknown serde `rename_all` rule",
13495 ))
13496 }
13497 })
13498 }
13499
13500 fn apply(self, field: &str) -> String {
13502 match self {
13503 Self::Lower => field.to_owned(),
13504 Self::Upper | Self::ScreamingSnake => field.to_ascii_uppercase(),
13505 Self::Pascal | Self::Camel => {
13506 let mut out = String::new();
13507 let mut cap = matches!(self, Self::Pascal);
13508 for ch in field.chars() {
13509 if ch == '_' {
13510 cap = !out.is_empty() || matches!(self, Self::Pascal);
13511 } else if cap {
13512 out.push(ch.to_ascii_uppercase());
13513 cap = false;
13514 } else {
13515 out.push(ch);
13516 }
13517 }
13518 out
13519 }
13520 Self::Kebab => field.replace('_', "-"),
13521 Self::ScreamingKebab => field.to_ascii_uppercase().replace('_', "-"),
13522 }
13523 }
13524}
13525
13526fn skip_serde_meta_value(meta: &syn::meta::ParseNestedMeta) -> syn::Result<()> {
13528 if meta.input.peek(syn::Token![=]) {
13529 let _: syn::Expr = meta.value()?.parse()?;
13530 } else if meta.input.peek(syn::token::Paren) {
13531 let _: proc_macro2::Group = meta.input.parse()?;
13532 }
13533 Ok(())
13534}
13535
13536fn parse_serde_rename(
13538 meta: &syn::meta::ParseNestedMeta,
13539 mut set: impl FnMut(bool, LitStr) -> syn::Result<()>,
13540) -> syn::Result<()> {
13541 if meta.input.peek(syn::Token![=]) {
13542 let s: LitStr = meta.value()?.parse()?;
13543 set(true, s.clone())?;
13544 return set(false, s);
13545 }
13546 meta.parse_nested_meta(|inner| {
13547 let is_ser = inner.path.is_ident("serialize");
13548 if !is_ser && !inner.path.is_ident("deserialize") {
13549 return Err(inner.error("expected `serialize` or `deserialize`"));
13550 }
13551 set(is_ser, inner.value()?.parse()?)
13552 })
13553}
13554
13555fn parse_serde_container_rules(
13557 input: &DeriveInput,
13558) -> syn::Result<(Option<SerdeRenameRule>, Option<SerdeRenameRule>)> {
13559 let (mut ser, mut de) = (None, None);
13560 for attr in input.attrs.iter().filter(|a| a.path().is_ident("serde")) {
13561 attr.parse_nested_meta(|meta| {
13562 if meta.path.is_ident("rename_all") {
13563 return parse_serde_rename(&meta, |is_ser, s| {
13564 let rule = Some(SerdeRenameRule::parse(&s)?);
13565 if is_ser {
13566 ser = rule;
13567 } else {
13568 de = rule;
13569 }
13570 Ok(())
13571 });
13572 }
13573 skip_serde_meta_value(&meta)
13574 })?;
13575 }
13576 Ok((ser, de))
13577}
13578
13579fn parse_serde_field_attrs(field: &syn::Field) -> syn::Result<SerdeWireAttrs> {
13580 let mut out = SerdeWireAttrs::default();
13581 for attr in field.attrs.iter().filter(|a| a.path().is_ident("serde")) {
13582 attr.parse_nested_meta(|meta| {
13583 if meta.path.is_ident("rename") {
13584 return parse_serde_rename(&meta, |is_ser, s| {
13585 if is_ser {
13586 out.rename_ser = Some(s.value());
13587 } else {
13588 out.rename_de = Some(s.value());
13589 }
13590 Ok(())
13591 });
13592 }
13593 if meta.path.is_ident("skip") {
13594 out.skip_ser = true;
13595 out.skip_de = true;
13596 return Ok(());
13597 }
13598 if meta.path.is_ident("skip_serializing") {
13599 out.skip_ser = true;
13600 return Ok(());
13601 }
13602 if meta.path.is_ident("skip_deserializing") {
13603 out.skip_de = true;
13604 return Ok(());
13605 }
13606 if meta.path.is_ident("skip_serializing_if") {
13607 let s: LitStr = meta.value()?.parse()?;
13608 out.skip_ser_if = Some(s.parse()?);
13609 return Ok(());
13610 }
13611 skip_serde_meta_value(&meta)
13612 })?;
13613 }
13614 Ok(out)
13615}
13616
13617fn parse_serializer_field_attrs(field: &syn::Field) -> syn::Result<SerializerFieldAttrs> {
13618 let mut out = SerializerFieldAttrs::default();
13619 for attr in &field.attrs {
13620 if !attr.path().is_ident("serializer") {
13621 continue;
13622 }
13623 attr.parse_nested_meta(|meta| {
13624 if meta.path.is_ident("read_only") {
13625 out.read_only = true;
13626 return Ok(());
13627 }
13628 if meta.path.is_ident("write_only") {
13629 out.write_only = true;
13630 return Ok(());
13631 }
13632 if meta.path.is_ident("skip") {
13633 out.skip = true;
13634 return Ok(());
13635 }
13636 if meta.path.is_ident("source") {
13637 let s: LitStr = meta.value()?.parse()?;
13638 out.source = Some(s.value());
13639 return Ok(());
13640 }
13641 if meta.path.is_ident("method") {
13642 let s: LitStr = meta.value()?.parse()?;
13643 out.method = Some(s.value());
13644 return Ok(());
13645 }
13646 if meta.path.is_ident("validate") {
13647 let s: LitStr = meta.value()?.parse()?;
13648 out.validate = Some(s.value());
13649 return Ok(());
13650 }
13651 if meta.path.is_ident("many") {
13652 let _eq: syn::Token![=] = meta.input.parse()?;
13653 out.many = Some(meta.input.parse()?);
13654 return Ok(());
13655 }
13656 if meta.path.is_ident("nested") {
13657 out.nested = true;
13658 if meta.input.peek(syn::token::Paren) {
13661 meta.parse_nested_meta(|inner| {
13662 if inner.path.is_ident("strict") {
13663 out.nested_strict = true;
13664 return Ok(());
13665 }
13666 Err(inner.error("unknown nested sub-attribute (supported: `strict`)"))
13667 })?;
13668 }
13669 return Ok(());
13670 }
13671 if meta.path.is_ident("slug") {
13672 let s: LitStr = meta.value()?.parse()?;
13673 out.slug = Some(s.value());
13674 return Ok(());
13675 }
13676 if meta.path.is_ident("max_length") {
13677 let lit: syn::LitInt = meta.value()?.parse()?;
13678 out.max_length = Some(lit.base10_parse::<u64>()?);
13679 return Ok(());
13680 }
13681 if meta.path.is_ident("min_length") {
13682 let lit: syn::LitInt = meta.value()?.parse()?;
13683 out.min_length = Some(lit.base10_parse::<u64>()?);
13684 return Ok(());
13685 }
13686 if meta.path.is_ident("min") {
13687 let lit: syn::LitInt = meta.value()?.parse()?;
13688 out.min = Some(lit.base10_parse::<i64>()?);
13689 return Ok(());
13690 }
13691 if meta.path.is_ident("max") {
13692 let lit: syn::LitInt = meta.value()?.parse()?;
13693 out.max = Some(lit.base10_parse::<i64>()?);
13694 return Ok(());
13695 }
13696 Err(meta.error(
13697 "unknown serializer field attribute (supported: \
13698 `read_only`, `write_only`, `source`, `skip`, `method`, \
13699 `validate`, `nested`, `many`, `slug`, `max_length`, \
13700 `min_length`, `min`, `max`)",
13701 ))
13702 })?;
13703 }
13704 if out.read_only && out.write_only {
13706 return Err(syn::Error::new_spanned(
13707 field,
13708 "a field cannot be both `read_only` and `write_only`",
13709 ));
13710 }
13711 if out.method.is_some() && out.source.is_some() {
13712 return Err(syn::Error::new_spanned(
13713 field,
13714 "`method` and `source` are mutually exclusive — `method` computes \
13715 the value from a method, `source` reads it from a different model field",
13716 ));
13717 }
13718 if out.slug.is_some() && (out.method.is_some() || out.nested || out.many.is_some()) {
13719 return Err(syn::Error::new_spanned(
13720 field,
13721 "`slug` is mutually exclusive with `method`, `nested`, and `many` \
13722 — pick one strategy for populating the field",
13723 ));
13724 }
13725 Ok(out)
13726}
13727
13728fn expand_serializer(input: &DeriveInput) -> syn::Result<TokenStream2> {
13729 let root = rustango_root();
13730 let struct_name = &input.ident;
13731 let struct_name_lit = struct_name.to_string();
13732
13733 let Data::Struct(data) = &input.data else {
13734 return Err(syn::Error::new_spanned(
13735 struct_name,
13736 "Serializer can only be derived on structs",
13737 ));
13738 };
13739 let Fields::Named(named) = &data.fields else {
13740 return Err(syn::Error::new_spanned(
13741 struct_name,
13742 "Serializer requires a struct with named fields",
13743 ));
13744 };
13745
13746 let container = parse_serializer_container_attrs(input)?;
13747 let model_path = &container.model;
13748
13749 #[allow(dead_code)]
13753 struct FieldInfo {
13754 ident: syn::Ident,
13755 ty: syn::Type,
13756 attrs: SerializerFieldAttrs,
13757 serde: SerdeWireAttrs,
13758 ser_name: String,
13760 de_name: String,
13761 }
13762 let (ser_rule, de_rule) = parse_serde_container_rules(input)?;
13763 let mut fields_info: Vec<FieldInfo> = Vec::new();
13764 for field in &named.named {
13765 let ident = field.ident.clone().expect("named field has ident");
13766 let attrs = parse_serializer_field_attrs(field)?;
13767 let serde = parse_serde_field_attrs(field)?;
13768 let name = ident_name(&ident);
13769 let wire = |rename: &Option<String>, rule: Option<SerdeRenameRule>| {
13770 rename
13771 .clone()
13772 .unwrap_or_else(|| rule.map_or_else(|| name.clone(), |r| r.apply(&name)))
13773 };
13774 fields_info.push(FieldInfo {
13775 ser_name: wire(&serde.rename_ser, ser_rule),
13776 de_name: wire(&serde.rename_de, de_rule),
13777 ident,
13778 ty: field.ty.clone(),
13779 attrs,
13780 serde,
13781 });
13782 }
13783
13784 let from_model_fields = fields_info.iter().map(|fi| {
13786 let ident = &fi.ident;
13787 if let Some(_inner) = &fi.attrs.many {
13788 quote! { #ident: ::std::vec::Vec::new() }
13792 } else if let Some(method) = &fi.attrs.method {
13793 let method_ident = syn::Ident::new(method, ident.span());
13797 quote! { #ident: Self::#method_ident(model) }
13798 } else if let Some(slug_field) = &fi.attrs.slug {
13799 let src_name = fi
13808 .attrs
13809 .source
13810 .as_deref()
13811 .unwrap_or(&ident_name(&fi.ident))
13812 .to_owned();
13813 let src_ident = ident_from_name(&src_name, ident.span());
13814 let slug_ident = syn::Ident::new(slug_field, ident.span());
13815 quote! {
13816 #ident: #root::serializer::LoadedRelation::render(
13817 &model.#src_ident,
13818 |__loaded| ::core::clone::Clone::clone(&__loaded.#slug_ident),
13819 ::core::default::Default::default,
13820 )
13821 }
13822 } else if fi.attrs.nested {
13823 let src_name = fi.attrs.source.as_deref().unwrap_or(&ident_name(&fi.ident)).to_owned();
13841 let src_ident = ident_from_name(&src_name, ident.span());
13842 let unloaded = if fi.attrs.nested_strict {
13843 let panic_msg = format!(
13844 "nested(strict) serializer for `{ident}` requires `model.{src_name}` to be loaded — \
13845 call .get(&pool).await? or .select_related(\"{src_name}\") on the model first",
13846 );
13847 quote! { || ::core::panic!("{}", #panic_msg) }
13848 } else {
13849 quote! { ::core::default::Default::default }
13850 };
13851 quote! {
13852 #ident: #root::serializer::LoadedRelation::render(
13853 &model.#src_ident,
13854 |__loaded| #root::serializer::ModelSerializer::from_model(__loaded),
13855 #unloaded,
13856 )
13857 }
13858 } else if fi.attrs.write_only || fi.attrs.skip {
13859 quote! { #ident: ::core::default::Default::default() }
13861 } else if let Some(src) = &fi.attrs.source {
13862 let src_ident = ident_from_name(src, ident.span());
13863 quote! { #ident: ::core::clone::Clone::clone(&model.#src_ident) }
13864 } else {
13865 quote! { #ident: ::core::clone::Clone::clone(&model.#ident) }
13866 }
13867 });
13868
13869 let is_writable = |fi: &&FieldInfo| {
13872 !fi.attrs.read_only
13873 && !fi.attrs.skip
13874 && !fi.serde.skip_de
13875 && fi.attrs.method.is_none()
13876 && !fi.attrs.nested
13877 && fi.attrs.many.is_none()
13878 && fi.attrs.slug.is_none()
13879 };
13880
13881 let opt_usize = |v: Option<u64>| match v {
13889 Some(n) => {
13890 let n = n as usize;
13891 quote!(::core::option::Option::Some(#n))
13892 }
13893 None => quote!(::core::option::Option::None),
13894 };
13895 #[cfg(feature = "openapi")]
13896 let opt_u64 = |v: Option<u64>| match v {
13897 Some(n) => quote!(::core::option::Option::Some(#n)),
13898 None => quote!(::core::option::Option::None),
13899 };
13900 let opt_i64 = |v: Option<i64>| match v {
13901 Some(n) => quote!(::core::option::Option::Some(#n)),
13902 None => quote!(::core::option::Option::None),
13903 };
13904 let constraint_blocks: Vec<_> = fields_info
13905 .iter()
13906 .filter(is_writable)
13907 .map(|fi| {
13908 let ident = &fi.ident;
13909 let fname = &fi.de_name;
13910 let mname = fi
13911 .attrs
13912 .source
13913 .clone()
13914 .unwrap_or_else(|| ident_name(&fi.ident));
13915 let attr_max_len = opt_usize(fi.attrs.max_length);
13916 let attr_min_len = opt_usize(fi.attrs.min_length);
13917 let attr_min = opt_i64(fi.attrs.min);
13918 let attr_max = opt_i64(fi.attrs.max);
13919 let ty = &fi.ty;
13921 let len_check = (fi.attrs.max_length.is_some() || fi.attrs.min_length.is_some())
13922 .then(|| quote! { #root::forms::validators::assert_length_bounded::<#ty>(); });
13923 let num_check = (fi.attrs.min.is_some() || fi.attrs.max.is_some())
13924 .then(|| quote! { #root::forms::validators::assert_number_bounded::<#ty>(); });
13925 quote! {
13926 #len_check
13927 #num_check
13928 if __sent(#fname) {
13929 let __sf = <#model_path as #root::core::Model>::SCHEMA.field(#mname);
13930 let __max_length: ::core::option::Option<usize> = #attr_max_len
13931 .or_else(|| __sf.and_then(|__f| __f.max_length).map(|__n| __n as usize));
13932 let __min_length: ::core::option::Option<usize> = #attr_min_len;
13933 let __min: ::core::option::Option<i64> =
13934 #attr_min.or_else(|| __sf.and_then(|__f| __f.min));
13935 let __max: ::core::option::Option<i64> =
13936 #attr_max.or_else(|| __sf.and_then(|__f| __f.max));
13937 let __choices = __sf.and_then(|__f| __f.choices);
13938 let __v = #root::__serde_json::to_value(&self.#ident)
13939 .unwrap_or(#root::__serde_json::Value::Null);
13940 #root::forms::validators::check_value(
13941 #fname, &__v, __max_length, __min_length, __min, __max, __choices,
13942 &mut __errors,
13943 );
13944 }
13945 }
13946 })
13947 .collect();
13948 let has_constraints = !constraint_blocks.is_empty();
13949
13950 let validator_calls: Vec<_> = fields_info
13954 .iter()
13955 .filter_map(|fi| {
13956 let ident = &fi.ident;
13957 let name_lit = &fi.de_name;
13958 let method = fi.attrs.validate.as_ref()?;
13959 let method_ident = syn::Ident::new(method, ident.span());
13960 Some(quote! {
13961 if __sent(#name_lit) {
13962 if let ::core::result::Result::Err(__e) = Self::#method_ident(&self.#ident) {
13963 __errors.add(#name_lit.to_owned(), __e);
13964 }
13965 }
13966 })
13967 })
13968 .collect();
13969 let cross_validate_call = container.cross_validate.as_ref().map(|method_ident| {
13976 quote! {
13977 if let ::core::result::Result::Err(__cross) = self.#method_ident() {
13980 __errors.merge(__cross);
13981 }
13982 }
13983 });
13984 let has_validators = !validator_calls.is_empty() || container.cross_validate.is_some();
13985 let has_run_validations = has_validators || has_constraints;
13988 let checks = quote! {
13992 let mut __errors = #root::forms::FormErrors::default();
13993 #( #constraint_blocks )*
13994 #( #validator_calls )*
13995 #cross_validate_call
13996 if __errors.is_empty() {
13997 ::core::result::Result::Ok(())
13998 } else {
13999 ::core::result::Result::Err(__errors)
14000 }
14001 };
14002 let validate_body = quote! {
14003 let __sent = |_: &str| true;
14004 #checks
14005 };
14006 let validate_patch_body = quote! {
14008 let __sent = |__n: &str| __obj.is_some_and(|__o| __o.contains_key(__n));
14009 #checks
14010 };
14011 let validate_method = if has_validators {
14017 quote! {
14018 impl #struct_name {
14019 pub fn validate(&self) -> ::core::result::Result<(), #root::forms::FormErrors> {
14025 #validate_body
14026 }
14027 }
14028 }
14029 } else {
14030 quote! {}
14031 };
14032 let trait_validate_override = if has_run_validations {
14037 quote! {
14038 fn validate(&self) -> ::core::result::Result<(), #root::forms::FormErrors> {
14039 #validate_body
14040 }
14041 }
14042 } else {
14043 quote! {}
14044 };
14045
14046 let many_setters: Vec<_> = fields_info
14050 .iter()
14051 .filter_map(|fi| {
14052 let many_ty = fi.attrs.many.as_ref()?;
14053 let ident = &fi.ident;
14054 let setter = syn::Ident::new(&format!("set_{}", ident_name(ident)), ident.span());
14055 Some(quote! {
14056 pub fn #setter(
14061 &mut self,
14062 models: &[<#many_ty as #root::serializer::ModelSerializer>::Model],
14063 ) -> &mut Self {
14064 self.#ident = models.iter()
14065 .map(<#many_ty as #root::serializer::ModelSerializer>::from_model)
14066 .collect();
14067 self
14068 }
14069 })
14070 })
14071 .collect();
14072 let many_setters_impl = if many_setters.is_empty() {
14073 quote! {}
14074 } else {
14075 quote! {
14076 impl #struct_name {
14077 #( #many_setters )*
14078 }
14079 }
14080 };
14081
14082 let output_fields: Vec<_> = fields_info
14084 .iter()
14085 .filter(|fi| !fi.attrs.write_only && !fi.serde.skip_ser)
14086 .collect();
14087 let output_field_count = output_fields.iter().map(|fi| {
14089 let ident = &fi.ident;
14090 if let Some(pred) = &fi.serde.skip_ser_if {
14091 quote! { + if #pred(&self.#ident) { 0 } else { 1 } }
14092 } else {
14093 quote! { + 1 }
14094 }
14095 });
14096 let output_field_count = quote! { 0usize #( #output_field_count )* };
14097 let serialize_fields = output_fields.iter().map(|fi| {
14098 let ident = &fi.ident;
14099 let name_lit = &fi.ser_name;
14100 if let Some(pred) = &fi.serde.skip_ser_if {
14101 quote! {
14102 if #pred(&self.#ident) {
14103 __state.skip_field(#name_lit)?;
14104 } else {
14105 __state.serialize_field(#name_lit, &self.#ident)?;
14106 }
14107 }
14108 } else {
14109 quote! { __state.serialize_field(#name_lit, &self.#ident)?; }
14110 }
14111 });
14112
14113 let writable_lits: Vec<_> = fields_info
14128 .iter()
14129 .filter(is_writable)
14130 .map(|fi| fi.de_name.clone())
14131 .collect();
14132
14133 let writable_source_lits: Vec<String> = fields_info
14138 .iter()
14139 .filter(is_writable)
14140 .map(|fi| {
14141 fi.attrs
14142 .source
14143 .clone()
14144 .unwrap_or_else(|| ident_name(&fi.ident))
14145 })
14146 .collect();
14147
14148 let readable: Vec<&FieldInfo> = fields_info
14152 .iter()
14153 .filter(|fi| {
14154 !fi.attrs.write_only
14155 && !fi.serde.skip_ser
14156 && !fi.attrs.skip
14157 && fi.attrs.method.is_none()
14158 && !fi.attrs.nested
14159 && fi.attrs.many.is_none()
14160 && fi.attrs.slug.is_none()
14161 })
14162 .collect();
14163 let readable_source_lits: Vec<String> = readable
14164 .iter()
14165 .map(|fi| {
14166 fi.attrs
14167 .source
14168 .clone()
14169 .unwrap_or_else(|| ident_name(&fi.ident))
14170 })
14171 .collect();
14172 let readable_lits: Vec<String> = readable.iter().map(|fi| fi.ser_name.clone()).collect();
14174
14175 let from_writable_json_inits: Vec<_> = fields_info
14182 .iter()
14183 .map(|fi| {
14184 let ident = &fi.ident;
14185 let fname = &fi.de_name;
14186 let ty = &fi.ty;
14187 if is_writable(&fi) {
14188 quote! {
14189 #ident: match __obj.and_then(|__o| __o.get(#fname)) {
14190 ::core::option::Option::Some(__v) => {
14191 match #root::__serde_json::from_value::<#ty>(
14192 ::core::clone::Clone::clone(__v),
14193 ) {
14194 ::core::result::Result::Ok(__x) => __x,
14195 ::core::result::Result::Err(__e) => {
14196 __errors.add(#fname.to_owned(), __e.to_string());
14197 ::core::default::Default::default()
14198 }
14199 }
14200 }
14201 ::core::option::Option::None => ::core::default::Default::default(),
14202 }
14203 }
14204 } else {
14205 quote! { #ident: ::core::default::Default::default() }
14206 }
14207 })
14208 .collect();
14209 let patch_overlays: Vec<_> = fields_info
14211 .iter()
14212 .filter(is_writable)
14213 .map(|fi| {
14214 let ident = &fi.ident;
14215 let fname = &fi.de_name;
14216 let ty = &fi.ty;
14217 quote! {
14218 if let ::core::option::Option::Some(__v) = __obj.and_then(|__o| __o.get(#fname)) {
14219 match #root::__serde_json::from_value::<#ty>(::core::clone::Clone::clone(__v)) {
14220 ::core::result::Result::Ok(__x) => __merged.#ident = __x,
14221 ::core::result::Result::Err(__e) => {
14222 __errors.add(#fname.to_owned(), __e.to_string());
14223 }
14224 }
14225 }
14226 }
14227 })
14228 .collect();
14229 let patch_validate_call = if has_run_validations {
14230 quote! { __merged.__rustango_validate_patch(__obj) }
14231 } else {
14232 quote! { let _ = &__merged; ::core::result::Result::Ok(()) }
14233 };
14234 let patch_validate_helper = if has_run_validations {
14235 quote! {
14236 impl #struct_name {
14237 #[doc(hidden)]
14238 fn __rustango_validate_patch(
14239 &self,
14240 __obj: ::core::option::Option<&#root::__serde_json::Map<::std::string::String, #root::__serde_json::Value>>,
14241 ) -> ::core::result::Result<(), #root::forms::FormErrors> {
14242 #validate_patch_body
14243 }
14244 }
14245 }
14246 } else {
14247 quote! {}
14248 };
14249
14250 let openapi_impl = {
14254 #[cfg(feature = "openapi")]
14255 {
14256 let property_calls = output_fields.iter().map(|fi| {
14257 let name_lit = &fi.ser_name;
14258 let ty = &fi.ty;
14259 let nullable_call = if is_option(ty) {
14260 quote! { .nullable() }
14261 } else {
14262 quote! {}
14263 };
14264 let limits_call = is_writable(fi).then(|| {
14266 let mname = fi
14267 .attrs
14268 .source
14269 .clone()
14270 .unwrap_or_else(|| ident_name(&fi.ident));
14271 let (a, b, c, d) = (
14272 opt_u64(fi.attrs.max_length),
14273 opt_u64(fi.attrs.min_length),
14274 opt_i64(fi.attrs.min),
14275 opt_i64(fi.attrs.max),
14276 );
14277 quote! {
14278 .field_limits(
14279 <#model_path as #root::core::Model>::SCHEMA.field(#mname),
14280 #a, #b, #c, #d,
14281 )
14282 }
14283 });
14284 quote! {
14285 .property(
14286 #name_lit,
14287 <#ty as #root::openapi::OpenApiSchema>::openapi_schema()
14288 #limits_call
14289 #nullable_call,
14290 )
14291 }
14292 });
14293 let required_lits: Vec<_> = output_fields
14294 .iter()
14295 .filter(|fi| !is_option(&fi.ty) && fi.serde.skip_ser_if.is_none())
14296 .map(|fi| fi.ser_name.clone())
14297 .collect();
14298 quote! {
14299 impl #root::openapi::OpenApiSchema for #struct_name {
14300 fn openapi_schema() -> #root::openapi::Schema {
14301 #root::openapi::Schema::object()
14302 #( #property_calls )*
14303 .required([ #( #required_lits ),* ])
14304 }
14305 }
14306 }
14307 }
14308 #[cfg(not(feature = "openapi"))]
14309 {
14310 quote! {}
14311 }
14312 };
14313
14314 let related_tables = fields_info.iter().filter_map(|fi| {
14316 let name = &fi.ser_name;
14317 if let Some(inner) = &fi.attrs.many {
14318 return Some(quote! {
14319 (#name, <<#inner as #root::serializer::ModelSerializer>::Model
14320 as #root::core::Model>::SCHEMA.table)
14321 });
14322 }
14323 if !(fi.attrs.nested || fi.attrs.slug.is_some()) {
14324 return None;
14325 }
14326 let src_name = fi
14327 .attrs
14328 .source
14329 .as_deref()
14330 .unwrap_or(&ident_name(&fi.ident))
14331 .to_owned();
14332 let src_ident = ident_from_name(&src_name, fi.ident.span());
14333 Some(quote! {
14334 (#name, #root::serializer::relation_table(|__m: &#model_path| &__m.#src_ident))
14335 })
14336 });
14337
14338 Ok(quote! {
14339 impl #root::serializer::ModelSerializer for #struct_name {
14340 type Model = #model_path;
14341
14342 fn related_tables() -> ::std::vec::Vec<(&'static str, &'static str)> {
14343 ::std::vec![ #( #related_tables ),* ]
14344 }
14345
14346 fn from_model(model: &Self::Model) -> Self {
14347 Self {
14348 #( #from_model_fields ),*
14349 }
14350 }
14351
14352 fn writable_fields() -> &'static [&'static str] {
14353 &[ #( #writable_lits ),* ]
14354 }
14355
14356 fn writable_source_fields() -> &'static [&'static str] {
14357 &[ #( #writable_source_lits ),* ]
14358 }
14359
14360 fn readable_source_fields() -> &'static [&'static str] {
14361 &[ #( #readable_source_lits ),* ]
14362 }
14363
14364 fn readable_fields() -> &'static [&'static str] {
14365 &[ #( #readable_lits ),* ]
14366 }
14367
14368 fn from_writable_json(
14369 __body: &#root::__serde_json::Value,
14370 ) -> ::core::result::Result<Self, #root::forms::FormErrors> {
14371 let mut __errors = #root::forms::FormErrors::default();
14372 let __obj = __body.as_object();
14373 let __out = Self {
14374 #( #from_writable_json_inits ),*
14375 };
14376 if __errors.is_empty() {
14377 ::core::result::Result::Ok(__out)
14378 } else {
14379 ::core::result::Result::Err(__errors)
14380 }
14381 }
14382
14383 fn validate_patch(
14384 __stored: &Self::Model,
14385 __body: &#root::__serde_json::Value,
14386 ) -> ::core::result::Result<(), #root::forms::FormErrors> {
14387 let mut __errors = #root::forms::FormErrors::default();
14388 let __obj = __body.as_object();
14389 #[allow(unused_mut)]
14390 let mut __merged = <Self as #root::serializer::ModelSerializer>::from_model(__stored);
14391 #( #patch_overlays )*
14392 if !__errors.is_empty() {
14393 return ::core::result::Result::Err(__errors);
14394 }
14395 #patch_validate_call
14396 }
14397
14398 #trait_validate_override
14399 }
14400
14401 #patch_validate_helper
14402
14403 impl #root::__serde::Serialize for #struct_name {
14404 fn serialize<S>(&self, serializer: S)
14405 -> ::core::result::Result<S::Ok, S::Error>
14406 where
14407 S: #root::__serde::Serializer,
14408 {
14409 use #root::__serde::ser::SerializeStruct;
14410 let mut __state = serializer.serialize_struct(
14411 #struct_name_lit,
14412 #output_field_count,
14413 )?;
14414 #( #serialize_fields )*
14415 __state.end()
14416 }
14417 }
14418
14419 #openapi_impl
14420
14421 #validate_method
14422
14423 #many_setters_impl
14424 })
14425}
14426
14427#[cfg_attr(not(feature = "openapi"), allow(dead_code))]
14431fn is_option(ty: &syn::Type) -> bool {
14432 if let syn::Type::Path(p) = ty {
14433 if let Some(last) = p.path.segments.last() {
14434 return last.ident == "Option";
14435 }
14436 }
14437 false
14438}
14439
14440#[cfg(test)]
14441mod main_attr_tests {
14442 use super::*;
14443
14444 fn expand(args: &str) -> String {
14445 let body = quote! { async fn main() { run().await } };
14446 expand_main(args.parse().unwrap(), body)
14447 .expect("expansion failed")
14448 .to_string()
14449 }
14450
14451 #[test]
14455 fn logging_false_emits_no_subscriber() {
14456 let out = expand("logging = false");
14457 assert!(
14458 !out.contains("logging :: Setup"),
14459 "`logging = false` must not install a subscriber:\n{out}",
14460 );
14461 assert!(
14462 out.contains("block_on"),
14463 "the runtime must still be built:\n{out}",
14464 );
14465 }
14466
14467 #[test]
14468 fn default_still_installs_a_subscriber() {
14469 let out = expand("");
14473 assert!(
14474 out.contains("logging :: Setup"),
14475 "the default entrypoint must still install one:\n{out}",
14476 );
14477 assert!(
14480 !out.contains("args ()"),
14481 "the entrypoint must not route logs by argv:\n{out}",
14482 );
14483 }
14484
14485 #[test]
14486 fn logging_false_composes_with_flavor() {
14487 let out = expand("flavor = \"current_thread\", logging = false");
14488 assert!(!out.contains("logging :: Setup"), "{out}");
14489 assert!(out.contains("new_current_thread"), "{out}");
14490 }
14491
14492 #[test]
14494 fn body_runs_on_a_big_stack_thread() {
14495 let out = expand("");
14496 assert!(out.contains("run_on_main_thread"), "{out}");
14497 assert!(out.contains("MAIN_STACK_SIZE"), "{out}");
14498 let out = expand("stack_size = 16777216, logging = false");
14499 assert!(out.contains("16777216usize"), "{out}");
14500 assert!(!out.contains("MAIN_STACK_SIZE"), "{out}");
14501 assert!(
14502 out.contains("thread_stack_size"),
14503 "workers get it too:\n{out}"
14504 );
14505 }
14506
14507 #[test]
14509 fn a_tiny_stack_size_is_refused() {
14510 let body = quote! { async fn main() {} };
14511 let err = expand_main("stack_size = 4096".parse().unwrap(), body)
14512 .expect_err("4 KiB")
14513 .to_string();
14514 assert!(err.contains("at least"), "{err}");
14515 }
14516
14517 #[test]
14519 fn lint_attributes_reach_the_body() {
14520 let body = quote! {
14521 #[allow(unused_variables)]
14522 #[doc = "entry"]
14523 async fn main() { let x = 1; }
14524 };
14525 let out = expand_main(quote!(), body).unwrap().to_string();
14526 assert_eq!(out.matches("allow (unused_variables)").count(), 2, "{out}");
14527 assert_eq!(out.matches("doc = \"entry\"").count(), 1, "{out}");
14528 }
14529
14530 #[test]
14531 fn logging_true_is_the_default_shape() {
14532 assert!(
14533 parse_main_args(&"logging = true".parse().unwrap())
14534 .unwrap()
14535 .logging
14536 );
14537 assert!(
14538 !parse_main_args(&"logging=false".parse().unwrap())
14539 .unwrap()
14540 .logging
14541 );
14542 }
14543}
14544
14545#[cfg(test)]
14546mod main_arg_tests {
14547 use super::*;
14548
14549 fn expand(args: &str) -> syn::Result<TokenStream2> {
14550 let body = quote! { async fn main() { run().await } };
14551 expand_main(args.parse().unwrap(), body)
14552 }
14553
14554 #[test]
14556 fn unknown_or_misspelt_args_are_errors() {
14557 for args in [
14558 "logging = flase",
14559 "multi_thread",
14560 "workers = 4",
14561 r#"flavor = "curent_thread""#,
14562 "worker_threads = 4",
14563 ] {
14564 let err = expand(args).expect_err(args).to_string();
14565 assert!(err.contains("#[rustango::main]"), "{args}: {err}");
14566 }
14567 }
14568
14569 #[test]
14570 fn supported_args_expand() {
14571 for args in [
14572 "",
14573 r#"flavor = "current_thread""#,
14574 r#"flavor = "multi_thread", logging = false"#,
14575 "logging = true",
14576 ] {
14577 assert!(expand(args).is_ok(), "{args}");
14578 }
14579 }
14580}
14581
14582#[cfg(test)]
14583mod embed_migrations_tests {
14584 use super::*;
14585
14586 fn expand(path: &str) -> String {
14587 expand_embed_migrations(quote! { #path })
14588 .expect("expansion failed")
14589 .to_string()
14590 }
14591
14592 #[test]
14593 fn missing_dir_warns() {
14594 let out = expand("./no_such_migrations_dir");
14595 assert!(out.contains("deprecated"), "missing dir must warn:\n{out}");
14596 assert!(out.contains("no_such_migrations_dir"), "{out}");
14597 }
14598
14599 #[test]
14600 fn existing_dir_does_not_warn() {
14601 let out = expand("./src");
14602 assert!(!out.contains("deprecated"), "{out}");
14603 }
14604}
14605
14606#[cfg(test)]
14607mod field_attr_tests {
14608 use super::*;
14609
14610 fn error_for(field: syn::Field) -> Option<String> {
14611 let attrs = parse_field_attrs(&field).unwrap();
14612 let kind = detect_type(&field.ty).unwrap().kind;
14613 check_bound_compatibility(&field, &attrs, kind)
14614 .err()
14615 .map(|e| e.to_string())
14616 }
14617
14618 #[test]
14620 fn type_specific_attrs_reject_other_types() {
14621 let f: syn::FieldsNamed = syn::parse_quote!({
14622 #[rustango(citext)] a: i64,
14623 #[rustango(vector(dims = 3))] b: String,
14624 #[rustango(geometry(srid = 4326))] c: String,
14625 #[rustango(citext)] d: String,
14626 });
14627 let errs: Vec<_> = f.named.into_iter().map(error_for).collect();
14628 assert!(errs[0].as_deref().unwrap().contains("citext"), "{errs:?}");
14629 assert!(errs[1].as_deref().unwrap().contains("vector"), "{errs:?}");
14630 assert!(errs[2].as_deref().unwrap().contains("geometry"), "{errs:?}");
14631 assert_eq!(errs[3], None);
14632 }
14633
14634 #[test]
14636 fn manager_ext_is_refused() {
14637 let input: DeriveInput = syn::parse_quote! {
14638 #[rustango(table = "p", manager(ext = "PostManagerExt"))]
14639 struct Post { id: i64 }
14640 };
14641 let err = parse_container_attrs(&input).err().expect("refused");
14642 assert!(err.to_string().contains("#2132"), "{err}");
14643 }
14644
14645 #[test]
14647 fn set_null_needs_an_optional_foreign_key() {
14648 let input: DeriveInput = syn::parse_quote! {
14649 #[rustango(table = "c")]
14650 struct Child {
14651 #[rustango(primary_key)] id: i64,
14652 #[rustango(on_delete = "set_null")] parent: ForeignKey<Parent>,
14653 }
14654 };
14655 let err = expand(&input).err().expect("refused");
14656 assert!(err.to_string().contains("set_null"), "{err}");
14657 let input: DeriveInput = syn::parse_quote! {
14658 #[rustango(table = "c")]
14659 struct Child {
14660 #[rustango(primary_key)] id: i64,
14661 #[rustango(on_delete = "set_null")] parent: Option<ForeignKey<Parent>>,
14662 }
14663 };
14664 assert!(expand(&input).is_ok());
14665 }
14666
14667 #[test]
14669 fn set_null_needs_a_nullable_fk_column() {
14670 let input: DeriveInput = syn::parse_quote! {
14671 #[rustango(table = "c")]
14672 struct Child {
14673 #[rustango(primary_key)] id: i64,
14674 #[rustango(fk = "p", on_delete = "set_null")] parent_id: i64,
14675 #[rustango(o2o = "q", on_delete = "set_null")] other_id: Option<i64>,
14676 }
14677 };
14678 let err = expand(&input).err().expect("refused");
14679 assert!(err.to_string().contains("set_null"), "{err}");
14680 let input: DeriveInput = syn::parse_quote! {
14681 #[rustango(table = "c")]
14682 struct Child {
14683 #[rustango(primary_key)] id: i64,
14684 #[rustango(fk = "p", on_delete = "set_null")] parent_id: std::option::Option<i64>,
14685 #[rustango(o2o = "q", on_delete = "set_null")] other_id: ::core::option::Option<i64>,
14686 #[rustango(on_delete = "set_null")] parent: core::option::Option<ForeignKey<Parent>>,
14687 }
14688 };
14689 assert!(expand(&input).is_ok());
14690 }
14691
14692 #[test]
14695 fn unknown_index_columns_are_errors() {
14696 let input: DeriveInput = syn::parse_quote! {
14697 #[rustango(
14698 table = "w",
14699 unique_together = "a, nope",
14700 index_together = "a, b_typo",
14701 index("missing"),
14702 get_latest_by = "-when_typo"
14703 )]
14704 struct W { #[rustango(primary_key)] id: i64, a: i64, b: i64 }
14705 };
14706 let err = expand(&input).expect_err("must not compile");
14707 let all: Vec<String> = err.into_iter().map(|e| e.to_string()).collect();
14708 for name in ["nope", "b_typo", "missing", "when_typo"] {
14709 assert!(
14710 all.iter().any(|m| m.contains(&format!("`{name}`"))),
14711 "{name}: {all:?}"
14712 );
14713 }
14714 }
14715
14716 #[test]
14719 fn known_index_columns_compile() {
14720 let input: DeriveInput = syn::parse_quote! {
14721 #[rustango(
14722 table = "w",
14723 unique_together = "a, b_col",
14724 index("lower(a)"),
14725 get_latest_by = "-b"
14726 )]
14727 struct W {
14728 #[rustango(primary_key)] id: i64,
14729 a: i64,
14730 #[rustango(column = "b_col")] b: i64,
14731 }
14732 };
14733 expand(&input).expect("compiles");
14734 }
14735
14736 #[test]
14738 fn reverse_accessor_clash_names_related_name() {
14739 let input: DeriveInput = syn::parse_quote! {
14740 #[rustango(table = "m")]
14741 struct Message {
14742 #[rustango(primary_key)] id: i64,
14743 sender: ForeignKey<User>,
14744 recipient: ForeignKey<User>,
14745 }
14746 };
14747 let err = expand(&input).err().expect("refused");
14748 assert!(err.to_string().contains("related_name"), "{err}");
14749 }
14750
14751 #[test]
14754 fn related_name_plus_skips_the_reverse_accessor() {
14755 let input: DeriveInput = syn::parse_quote! {
14756 #[rustango(table = "m")]
14757 struct Message {
14758 #[rustango(primary_key)] id: i64,
14759 sender: ForeignKey<User>,
14760 #[rustango(related_name = "+")] recipient: ForeignKey<User>,
14761 #[rustango(related_name = "+")] owner: ForeignKey<other::Account>,
14762 }
14763 };
14764 let out = expand(&input).expect("derives").to_string();
14765 assert_eq!(out.matches("message_set_pool").count(), 1, "{out}");
14766 assert!(!out.contains("impl other :: Account {"), "{out}");
14767 }
14768
14769 fn field_error(src: &str) -> Option<String> {
14770 let f: syn::FieldsNamed = syn::parse_str(&format!("{{ {src} }}")).unwrap();
14771 let field = f.named.into_iter().next().unwrap();
14772 match parse_field_attrs(&field) {
14773 Err(e) => Some(e.to_string()),
14774 Ok(_) => error_for(field),
14775 }
14776 }
14777
14778 #[test]
14780 fn flag_attrs_accept_eq_true() {
14781 for key in [
14782 "primary_key",
14783 "unique",
14784 "soft_delete",
14785 "index",
14786 "auto_now",
14787 "auto_now_add",
14788 ] {
14789 let f: syn::FieldsNamed =
14790 syn::parse_str(&format!("{{ #[rustango({key} = true)] a: i64 }}")).unwrap();
14791 let field = f.named.into_iter().next().unwrap();
14792 assert!(parse_field_attrs(&field).is_ok(), "{key} = true");
14793 let err = field_error(&format!("#[rustango({key} = false)] a: i64")).unwrap();
14794 assert!(err.contains(&format!("`{key} = false`")), "{err}");
14795 }
14796 let f: syn::Field =
14797 syn::parse_quote!(#[rustango(primary_key = true, unique = true)] a: i64);
14798 let attrs = parse_field_attrs(&f).unwrap();
14799 assert!(attrs.primary_key && attrs.unique);
14800 }
14801
14802 #[test]
14804 fn unknown_key_is_named_with_a_suggestion() {
14805 let err = field_error("#[rustango(max_lenght = 5)] a: String").unwrap();
14806 assert!(err.contains("`max_lenght`"), "{err}");
14807 assert!(err.contains("did you mean `max_length`"), "{err}");
14808 assert!(err.contains("Supported: column"), "{err}");
14809 let input: DeriveInput = syn::parse_quote! {
14810 #[rustango(tabel = "p")]
14811 struct P { id: i64 }
14812 };
14813 let err = parse_container_attrs(&input).err().unwrap().to_string();
14814 assert!(
14815 err.contains("`tabel`") && err.contains("did you mean `table`"),
14816 "{err}"
14817 );
14818 let err = field_error("#[rustango(zzzzzz)] a: i64").unwrap();
14819 assert!(!err.contains("did you mean"), "{err}");
14820 }
14821
14822 #[test]
14824 fn listed_keys_are_all_known() {
14825 for key in FIELD_ATTR_KEYS {
14826 let err = field_error(&format!("#[rustango({key})] a: i64")).unwrap_or_default();
14827 assert!(!err.contains("unknown rustango field"), "{key}: {err}");
14828 }
14829 for key in CONTAINER_ATTR_KEYS {
14830 let input: DeriveInput =
14831 syn::parse_str(&format!("#[rustango({key})] struct P {{ id: i64 }}")).unwrap();
14832 let err = parse_container_attrs(&input)
14833 .err()
14834 .map(|e| e.to_string())
14835 .unwrap_or_default();
14836 assert!(!err.contains("unknown rustango container"), "{key}: {err}");
14837 }
14838 }
14839
14840 #[test]
14842 fn default_shapes_only_sqlite_takes_are_refused() {
14843 for bad in [
14844 r#"#[rustango(default = "draft")] s: String"#,
14845 r#"#[rustango(default = "draft")] s: Option<String>"#,
14846 r#"#[rustango(default = "abc")] n: i32"#,
14847 r#"#[rustango(default = "'abc'")] n: i64"#,
14848 r#"#[rustango(default = "true")] n: i32"#,
14849 r#"#[rustango(default = "")] n: i32"#,
14850 r#"#[rustango(default = "'x'")] n: f64"#,
14851 ] {
14852 assert!(field_error(bad).is_some(), "{bad} must be refused");
14853 }
14854 let err = field_error(r#"#[rustango(default = "draft")] s: String"#).unwrap();
14855 assert!(err.contains(r#"default = "'draft'""#), "{err}");
14856 let err = field_error(r#"#[rustango(default = "current_user")] s: String"#).unwrap();
14858 assert!(
14859 err.contains("Postgres only") && !err.contains("column"),
14860 "{err}"
14861 );
14862 for good in [
14863 r#"#[rustango(default = "'draft'")] s: String"#,
14864 r#"#[rustango(default = "")] s: String"#,
14865 r#"#[rustango(default = "now()")] s: String"#,
14866 r#"#[rustango(default = "CURRENT_TIMESTAMP")] s: String"#,
14867 r#"#[rustango(default = "NULL")] s: Option<String>"#,
14868 r#"#[rustango(default = "0")] n: i32"#,
14869 r#"#[rustango(default = "-1")] n: i64"#,
14870 r#"#[rustango(default = "'5'")] n: i64"#,
14871 r#"#[rustango(default = "(1 + 2)")] n: i64"#,
14872 r#"#[rustango(default = "1.5")] n: f64"#,
14873 r#"#[rustango(default = "true")] b: bool"#,
14874 ] {
14875 assert_eq!(field_error(good), None, "{good}");
14876 }
14877 }
14878
14879 #[test]
14881 fn raw_idents_derive_plain_names() {
14882 let ident: syn::Ident = syn::parse_quote!(r#type);
14883 assert_eq!(column_type_ident(&ident).to_string(), "type_col");
14884 assert_eq!(ident_from_name("type", ident.span()).to_string(), "r#type");
14885 }
14886}
14887
14888#[cfg(test)]
14889mod model_attr_name_tests {
14890 use super::*;
14891
14892 fn container_error(item: proc_macro2::TokenStream) -> Option<String> {
14893 let input: DeriveInput = syn::parse2(item).unwrap();
14894 parse_container_attrs(&input).err().map(|e| e.to_string())
14895 }
14896
14897 fn field_error(field: proc_macro2::TokenStream) -> Option<String> {
14898 let f: syn::FieldsNamed = syn::parse2(quote!({ #field })).unwrap();
14899 let field = f.named.into_iter().next().unwrap();
14900 parse_field_attrs(&field).err().map(|e| e.to_string())
14901 }
14902
14903 #[test]
14905 fn rgo_is_read_like_rustango() {
14906 let long: DeriveInput = syn::parse_quote! {
14907 #[rustango(table = "t", display = "name")] struct S;
14908 };
14909 let short: DeriveInput = syn::parse_quote! {
14910 #[rgo(table = "t", display = "name")] struct S;
14911 };
14912 let (long, short) = (
14913 parse_container_attrs(&long).unwrap(),
14914 parse_container_attrs(&short).unwrap(),
14915 );
14916 assert_eq!(long.table, short.table);
14917 assert_eq!(short.table.as_deref(), Some("t"));
14918 assert_eq!(long.display.map(|d| d.0), short.display.map(|d| d.0));
14919 }
14920
14921 #[test]
14922 fn mixing_names_on_one_item_or_field_is_refused() {
14923 let err = container_error(quote! {
14924 #[rustango(table = "t")] #[rgo(display = "name")] struct S;
14925 })
14926 .expect("a mix on an item must fail");
14927 assert!(
14928 err.contains("`#[rustango(...)]` and `#[rgo(...)]`"),
14929 "{err}"
14930 );
14931 let err = field_error(quote! { #[rgo(primary_key)] #[rustango(column = "c")] a: i64 })
14932 .expect("a mix on a field must fail");
14933 assert!(
14934 err.contains("`#[rgo(...)]` and `#[rustango(...)]`"),
14935 "{err}"
14936 );
14937 assert_eq!(
14939 container_error(quote! { #[rgo(table = "t")] #[rgo(display = "name")] struct S; }),
14940 None
14941 );
14942 assert_eq!(
14943 field_error(quote! { #[rustango(primary_key)] #[rustango(column = "c")] a: i64 }),
14944 None
14945 );
14946 }
14947
14948 #[test]
14950 fn hints_use_the_written_name() {
14951 let err = container_error(quote! { #[rgo(table = "t", unique_together = "a")] struct S; })
14952 .expect("one column must fail");
14953 assert!(err.contains("#[rgo(unique)]"), "{err}");
14954 let err =
14955 container_error(quote! { #[rustango(table = "t", index_together = "a")] struct S; })
14956 .expect("one column must fail");
14957 assert!(err.contains("#[rustango(index)]"), "{err}");
14958 }
14959}
14960
14961fn decode_text(bytes: Vec<u8>) -> Result<String, String> {
14965 fn utf16(bytes: &[u8], unit: fn([u8; 2]) -> u16) -> Result<String, String> {
14966 if bytes.len() % 2 != 0 {
14967 return Err("odd-length UTF-16".into());
14968 }
14969 let units = bytes.chunks_exact(2).map(|c| unit([c[0], c[1]]));
14970 char::decode_utf16(units)
14971 .collect::<Result<String, _>>()
14972 .map_err(|e| e.to_string())
14973 }
14974 match bytes.as_slice() {
14975 [0xFF, 0xFE, rest @ ..] => utf16(rest, u16::from_le_bytes),
14976 [0xFE, 0xFF, rest @ ..] => utf16(rest, u16::from_be_bytes),
14977 _ => {
14978 let mut bytes = bytes;
14979 if bytes.starts_with(&[0xEF, 0xBB, 0xBF]) {
14980 bytes.drain(..3);
14981 }
14982 String::from_utf8(bytes).map_err(|e| e.to_string())
14983 }
14984 }
14985}
14986
14987#[cfg(test)]
14988mod decode_text_tests {
14989 use super::decode_text;
14990
14991 #[test]
14992 fn a_bom_or_utf16_migration_decodes() {
14993 let text = r#"{"name": "0001_é"}"#;
14994 let mut bom = vec![0xEF, 0xBB, 0xBF];
14995 bom.extend(text.bytes());
14996 let mut le = vec![0xFF, 0xFE];
14997 le.extend(text.encode_utf16().flat_map(u16::to_le_bytes));
14998 let mut be = vec![0xFE, 0xFF];
14999 be.extend(text.encode_utf16().flat_map(u16::to_be_bytes));
15000 for bytes in [text.as_bytes().to_vec(), bom, le, be] {
15001 assert_eq!(decode_text(bytes).unwrap(), text);
15002 }
15003 assert!(decode_text(vec![0xFF, 0xFE, 0x41]).is_err());
15004 }
15005}