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))]
100pub fn derive_viewset(input: TokenStream) -> TokenStream {
101 let input = parse_macro_input!(input as DeriveInput);
102 expand_viewset(&input)
103 .unwrap_or_else(syn::Error::into_compile_error)
104 .into()
105}
106
107#[proc_macro_derive(Form, attributes(form))]
135pub fn derive_form(input: TokenStream) -> TokenStream {
136 let input = parse_macro_input!(input as DeriveInput);
137 expand_form(&input)
138 .unwrap_or_else(syn::Error::into_compile_error)
139 .into()
140}
141
142#[proc_macro_derive(Serializer, attributes(serializer, serde))]
167pub fn derive_serializer(input: TokenStream) -> TokenStream {
168 let input = parse_macro_input!(input as DeriveInput);
169 expand_serializer(&input)
170 .unwrap_or_else(syn::Error::into_compile_error)
171 .into()
172}
173
174#[proc_macro]
213pub fn embed_migrations(input: TokenStream) -> TokenStream {
214 expand_embed_migrations(input.into())
215 .unwrap_or_else(syn::Error::into_compile_error)
216 .into()
217}
218
219#[allow(non_snake_case)]
269#[proc_macro]
270pub fn Q(input: TokenStream) -> TokenStream {
271 expand_q(input.into())
272 .unwrap_or_else(syn::Error::into_compile_error)
273 .into()
274}
275
276#[proc_macro_attribute]
322pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
323 expand_main(args.into(), item.into())
324 .unwrap_or_else(syn::Error::into_compile_error)
325 .into()
326}
327
328fn expand_main(args: TokenStream2, item: TokenStream2) -> syn::Result<TokenStream2> {
329 let mut input: syn::ItemFn = syn::parse2(item)?;
330 if input.sig.asyncness.is_none() {
331 return Err(syn::Error::new(
332 input.sig.ident.span(),
333 "`#[rustango::main]` must wrap an `async fn`",
334 ));
335 }
336
337 let root = rustango_root();
347 let MainArgs {
348 flavor,
349 logging,
350 stack_size,
351 } = parse_main_args(&args)?;
352 let stack_size = match stack_size {
353 Some(n) => quote!(#n),
354 None => quote!(#root::__private_runtime::MAIN_STACK_SIZE),
355 };
356 let builder_call = match flavor {
357 Flavor::CurrentThread => quote! {
358 #root::__private_runtime::tokio::runtime::Builder::new_current_thread()
359 },
360 Flavor::MultiThread => quote! {
362 #root::__private_runtime::tokio::runtime::Builder::new_multi_thread()
363 .thread_stack_size(#stack_size)
364 },
365 };
366
367 let mut inner = input.clone();
370 inner.sig.ident = syn::Ident::new("__rustango_main_body", input.sig.ident.span());
371 inner.attrs.retain(|a| {
373 ["allow", "expect", "deny", "warn", "forbid"]
374 .iter()
375 .any(|l| a.path().is_ident(l))
376 });
377 inner.vis = syn::Visibility::Inherited;
378 input.sig.asyncness = None;
379 input.sig.output = syn::parse2(quote!(-> ::std::process::ExitCode))?;
380
381 let logging_prologue = if logging {
385 quote! {
386 let _ = #root::logging::Setup::new()
391 .with_default_env_filter(#root::__private_runtime::main_default_filter())
392 .with_format(#root::__private_runtime::main_format())
393 .install();
394 }
395 } else {
396 quote! {}
397 };
398
399 input.block = syn::parse2(quote! {{
400 { #logging_prologue }
401 #inner
402 #root::__private_runtime::run_on_main_thread(#stack_size, || {
403 let __rt = #builder_call
404 .enable_all()
405 .build()
406 .expect("failed to build tokio runtime");
407 ::std::process::Termination::report(__rt.block_on(__rustango_main_body()))
408 })
409 }})?;
410
411 Ok(quote! {
412 #input
413 })
414}
415
416enum Flavor {
417 MultiThread,
418 CurrentThread,
419}
420
421struct MainArgs {
423 flavor: Flavor,
424 logging: bool,
425 stack_size: Option<usize>,
427}
428
429const MIN_STACK_SIZE: usize = 64 * 1024;
431
432const MAIN_ARGS_HELP: &str =
433 "supported: `flavor = \"current_thread\" | \"multi_thread\"`, `logging = true | false`, `stack_size = <bytes>`";
434
435fn parse_main_args(args: &TokenStream2) -> syn::Result<MainArgs> {
437 use syn::parse::Parser as _;
438 let metas = syn::punctuated::Punctuated::<syn::MetaNameValue, syn::Token![,]>::parse_terminated
439 .parse2(args.clone())
440 .map_err(|e| {
441 syn::Error::new(
442 e.span(),
443 format!("#[rustango::main]: {e}; {MAIN_ARGS_HELP}"),
444 )
445 })?;
446 let mut out = MainArgs {
447 flavor: Flavor::MultiThread,
448 logging: true,
449 stack_size: None,
450 };
451 for meta in &metas {
452 let lit = match &meta.value {
453 syn::Expr::Lit(l) => Some(&l.lit),
454 _ => None,
455 };
456 let name = meta.path.get_ident().map(ToString::to_string);
457 match (name.as_deref(), lit) {
458 (Some("flavor"), Some(syn::Lit::Str(s))) if s.value() == "current_thread" => {
459 out.flavor = Flavor::CurrentThread;
460 }
461 (Some("flavor"), Some(syn::Lit::Str(s))) if s.value() == "multi_thread" => {
462 out.flavor = Flavor::MultiThread;
463 }
464 (Some("logging"), Some(syn::Lit::Bool(b))) => out.logging = b.value,
465 (Some("stack_size"), Some(syn::Lit::Int(n))) => {
466 let bytes = n.base10_parse::<usize>()?;
467 if bytes < MIN_STACK_SIZE {
468 return Err(syn::Error::new(
469 n.span(),
470 format!("#[rustango::main]: `stack_size` must be at least {MIN_STACK_SIZE} bytes"),
471 ));
472 }
473 out.stack_size = Some(bytes);
474 }
475 _ => {
476 return Err(syn::Error::new(
477 meta.span(),
478 format!("#[rustango::main]: unsupported argument; {MAIN_ARGS_HELP}"),
479 ))
480 }
481 }
482 }
483 Ok(out)
484}
485
486struct QInput {
488 base_path: syn::Path,
489 field: syn::Ident,
490 value: syn::Expr,
491}
492
493impl syn::parse::Parse for QInput {
494 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
495 let base_path: syn::Path = input.parse()?;
496 input.parse::<syn::Token![.]>()?;
497 let field: syn::Ident = input.parse()?;
498 input.parse::<syn::Token![=]>()?;
499 let value: syn::Expr = input.parse()?;
500 Ok(QInput {
501 base_path,
502 field,
503 value,
504 })
505 }
506}
507
508fn expand_q(input: TokenStream2) -> syn::Result<TokenStream2> {
509 let q: QInput = syn::parse2(input)?;
510 let root = rustango_root();
511 let field_str = ident_name(&q.field);
512 let field_span = q.field.span();
513 let (base, suffix) = match field_str.find("__") {
514 Some(idx) => (&field_str[..idx], &field_str[idx + 2..]),
515 None => (field_str.as_str(), ""),
516 };
517 if base.is_empty() {
518 return Err(syn::Error::new(
519 field_span,
520 "Q!(): field name is empty before `__` suffix",
521 ));
522 }
523 let base_ident = ident_from_name(base, field_span);
524 let value = &q.value;
525 let path = &q.base_path;
526
527 let expanded = match suffix {
532 "" | "exact" => quote! {
533 #root::core::Column::eq(#path::#base_ident, #value)
534 },
535 "ne" => quote! {
536 #root::core::Column::ne(#path::#base_ident, #value)
537 },
538 "gt" => quote! {
539 #root::core::Column::gt(#path::#base_ident, #value)
540 },
541 "gte" => quote! {
542 #root::core::Column::gte(#path::#base_ident, #value)
543 },
544 "lt" => quote! {
545 #root::core::Column::lt(#path::#base_ident, #value)
546 },
547 "lte" => quote! {
548 #root::core::Column::lte(#path::#base_ident, #value)
549 },
550 "iexact" => quote! {
555 #root::core::Column::iexact(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
556 },
557 "contains" => quote! {
558 #root::core::Column::contains(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
559 },
560 "icontains" => quote! {
561 #root::core::Column::icontains(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
562 },
563 "startswith" => quote! {
564 #root::core::Column::startswith(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
565 },
566 "istartswith" => quote! {
567 #root::core::Column::istartswith(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
568 },
569 "endswith" => quote! {
570 #root::core::Column::endswith(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
571 },
572 "iendswith" => quote! {
573 #root::core::Column::iendswith(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
574 },
575 "in" => quote! {
576 #root::core::Column::is_in(#path::#base_ident, #value)
577 },
578 "not_in" => quote! {
579 #root::core::Column::not_in(#path::#base_ident, #value)
580 },
581 "isnull" => {
582 let b = match value {
585 syn::Expr::Lit(syn::ExprLit {
586 lit: syn::Lit::Bool(b),
587 ..
588 }) => b.value(),
589 _ => {
590 return Err(syn::Error::new_spanned(
591 value,
592 "Q!(): `__isnull` requires a `true` or `false` literal",
593 ));
594 }
595 };
596 if b {
597 quote! { #root::core::Column::is_null(#path::#base_ident) }
598 } else {
599 quote! { #root::core::Column::is_not_null(#path::#base_ident) }
600 }
601 }
602 "between" => {
603 let tuple = match value {
605 syn::Expr::Tuple(t) if t.elems.len() == 2 => t,
606 _ => {
607 return Err(syn::Error::new_spanned(
608 value,
609 "Q!(): `__between` requires a tuple literal `(lo, hi)`",
610 ));
611 }
612 };
613 let lo = &tuple.elems[0];
614 let hi = &tuple.elems[1];
615 quote! { #root::core::Column::between(#path::#base_ident, #lo, #hi) }
616 }
617 "regex" => quote! {
618 #root::core::Column::regex(#path::#base_ident, #value)
619 },
620 "iregex" => quote! {
621 #root::core::Column::iregex(#path::#base_ident, #value)
622 },
623 _ => {
624 return Err(syn::Error::new(
625 field_span,
626 format!(
627 "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",
628 suffix
629 ),
630 ));
631 }
632 };
633 Ok(expanded)
634}
635
636fn expand_embed_migrations(input: TokenStream2) -> syn::Result<TokenStream2> {
637 let path_str = if input.is_empty() {
639 "./migrations".to_string()
640 } else {
641 let lit: LitStr = syn::parse2(input)?;
642 lit.value()
643 };
644
645 let manifest = std::env::var("CARGO_MANIFEST_DIR").map_err(|_| {
646 syn::Error::new(
647 proc_macro2::Span::call_site(),
648 "embed_migrations! must be invoked during a Cargo build (CARGO_MANIFEST_DIR not set)",
649 )
650 })?;
651 let abs = std::path::Path::new(&manifest).join(&path_str);
652
653 let mut entries: Vec<(String, std::path::PathBuf)> = Vec::new();
654 if abs.is_dir() {
655 let read = std::fs::read_dir(&abs).map_err(|e| {
656 syn::Error::new(
657 proc_macro2::Span::call_site(),
658 format!("embed_migrations!: cannot read {}: {e}", abs.display()),
659 )
660 })?;
661 for entry in read.flatten() {
662 let path = entry.path();
663 if !path.is_file() {
664 continue;
665 }
666 if path.extension().and_then(|s| s.to_str()) != Some("json") {
667 continue;
668 }
669 let Some(stem) = path.file_stem().and_then(|s| s.to_str()) else {
670 continue;
671 };
672 entries.push((stem.to_owned(), path));
673 }
674 }
675 entries.sort_by(|a, b| a.0.cmp(&b.0));
676
677 let mut chain_names: Vec<String> = Vec::with_capacity(entries.len());
690 let mut prev_refs: Vec<(String, Option<String>)> = Vec::with_capacity(entries.len());
691 let mut texts: Vec<String> = Vec::with_capacity(entries.len());
692 for (stem, path) in &entries {
693 let raw = std::fs::read(path)
694 .map_err(|e| e.to_string())
695 .and_then(decode_text)
696 .map_err(|e| {
697 syn::Error::new(
698 proc_macro2::Span::call_site(),
699 format!(
700 "embed_migrations!: cannot read {} for chain validation: {e}",
701 path.display()
702 ),
703 )
704 })?;
705 let json: serde_json::Value = serde_json::from_str(&raw).map_err(|e| {
706 syn::Error::new(
707 proc_macro2::Span::call_site(),
708 format!(
709 "embed_migrations!: {} is not valid JSON: {e}",
710 path.display()
711 ),
712 )
713 })?;
714 let name = json
715 .get("name")
716 .and_then(|v| v.as_str())
717 .ok_or_else(|| {
718 syn::Error::new(
719 proc_macro2::Span::call_site(),
720 format!(
721 "embed_migrations!: {} is missing the `name` field",
722 path.display()
723 ),
724 )
725 })?
726 .to_owned();
727 if name != *stem {
728 return Err(syn::Error::new(
729 proc_macro2::Span::call_site(),
730 format!(
731 "embed_migrations!: file stem `{stem}` does not match the migration's \
732 `name` field `{name}` — rename the file or fix the JSON",
733 ),
734 ));
735 }
736 let prev = json.get("prev").and_then(|v| v.as_str()).map(str::to_owned);
737 chain_names.push(name.clone());
738 prev_refs.push((name, prev));
739 texts.push(raw);
740 }
741
742 let name_set: std::collections::HashSet<&str> =
743 chain_names.iter().map(String::as_str).collect();
744 for (name, prev) in &prev_refs {
745 if let Some(p) = prev {
746 if !name_set.contains(p.as_str()) {
747 return Err(syn::Error::new(
748 proc_macro2::Span::call_site(),
749 format!(
750 "embed_migrations!: broken migration chain — `{name}` declares \
751 prev=`{p}` but no migration with that name exists in {}",
752 abs.display()
753 ),
754 ));
755 }
756 }
757 }
758
759 let pairs: Vec<TokenStream2> = entries
762 .iter()
763 .zip(&texts)
764 .map(|((name, path), text)| {
765 let path_lit = path.display().to_string();
766 quote! { (#name, {
767 const _: &[u8] = ::core::include_bytes!(#path_lit);
768 #text
769 }) }
770 })
771 .collect();
772
773 let missing_warning = if abs.is_dir() {
776 quote! {}
777 } else {
778 let note = format!(
779 "embed_migrations!: directory {} does not exist; no migrations are embedded",
780 abs.display()
781 );
782 quote! {
783 #[deprecated(note = #note)]
784 const __RUSTANGO_MIGRATIONS_DIR_MISSING: () = ();
785 #[allow(clippy::let_unit_value)]
786 let _ = __RUSTANGO_MIGRATIONS_DIR_MISSING;
787 }
788 };
789
790 Ok(quote! {
791 {
792 #missing_warning
793 const __RUSTANGO_EMBEDDED: &[(&'static str, &'static str)] = &[#(#pairs),*];
794 __RUSTANGO_EMBEDDED
795 }
796 })
797}
798
799fn expand(input: &DeriveInput) -> syn::Result<TokenStream2> {
800 let root = rustango_root();
801 let struct_name = &input.ident;
802
803 let Data::Struct(data) = &input.data else {
804 return Err(syn::Error::new_spanned(
805 struct_name,
806 "Model can only be derived on structs",
807 ));
808 };
809 let Fields::Named(named) = &data.fields else {
810 return Err(syn::Error::new_spanned(
811 struct_name,
812 "Model requires a struct with named fields",
813 ));
814 };
815
816 let mut container = parse_container_attrs(input)?;
817 let table = container
818 .table
819 .unwrap_or_else(|| to_snake_case(&struct_name.to_string()));
820 let model_name = struct_name.to_string();
821
822 let collected = collect_fields(named, &table)?;
823 let explicit_pks = container
825 .reverse_has_relations
826 .iter()
827 .map(|r| (&r.self_pk_column, r.self_pk_span))
828 .chain(
829 container
830 .generic_has_relations
831 .iter()
832 .map(|r| (&r.self_pk_column, r.self_pk_span)),
833 );
834 for (column, span) in explicit_pks {
835 if let (Some(column), Some(span)) = (column, span) {
836 if !collected.column_entries.iter().any(|c| &c.column == column) {
837 return Err(syn::Error::new(
838 span,
839 format!("`self_pk_column = \"{column}\"` names no column of `{model_name}`"),
840 ));
841 }
842 }
843 }
844 if let Some((_, pk_column)) = &collected.primary_key {
846 for rel in &mut container.reverse_has_relations {
847 rel.self_pk_column.get_or_insert_with(|| pk_column.clone());
848 }
849 for rel in &mut container.generic_has_relations {
850 rel.self_pk_column.get_or_insert_with(|| pk_column.clone());
851 }
852 }
853
854 if let Some((ref display, span)) = container.display {
856 if !collected.field_names.iter().any(|n| n == display) {
857 return Err(syn::Error::new(
858 span,
859 format!("`display = \"{display}\"` does not match any field on this struct"),
860 ));
861 }
862 }
863 let display = container.display.map(|(name, _)| name);
864 let app_label = container.app.clone();
865
866 if let Some(admin) = &container.admin {
876 for (label, list) in [
877 ("search_fields", &admin.search_fields),
878 ("readonly_fields", &admin.readonly_fields),
879 ("list_filter", &admin.list_filter),
880 ] {
881 if let Some((names, span)) = list {
882 for name in names {
883 if !collected.field_names.iter().any(|n| n == name) {
884 return Err(syn::Error::new(
885 *span,
886 format!(
887 "`{label} = \"{name}\"`: \"{name}\" is not a declared field on this struct"
888 ),
889 ));
890 }
891 }
892 }
893 }
894 if let Some((pairs, span)) = &admin.ordering {
895 for (name, _) in pairs {
896 if !collected.field_names.iter().any(|n| n == name) {
897 return Err(syn::Error::new(
898 *span,
899 format!(
900 "`ordering = \"{name}\"`: \"{name}\" is not a declared field on this struct"
901 ),
902 ));
903 }
904 }
905 }
906 if let Some((groups, span)) = &admin.fieldsets {
907 for (_, fields) in groups {
908 for name in fields {
909 if !collected.field_names.iter().any(|n| n == name) {
910 return Err(syn::Error::new(
911 *span,
912 format!(
913 "`fieldsets`: \"{name}\" is not a declared field on this struct"
914 ),
915 ));
916 }
917 }
918 }
919 }
920 }
921 if let Some(audit) = &container.audit {
922 if let Some((names, span)) = &audit.track {
923 for name in names {
924 if !collected.field_names.iter().any(|n| n == name) {
925 return Err(syn::Error::new(
926 *span,
927 format!(
928 "`audit(track = \"{name}\")`: \"{name}\" is not a declared field on this struct"
929 ),
930 ));
931 }
932 }
933 }
934 }
935
936 for (col, _desc, span) in &container.default_order {
940 if !collected.field_names.iter().any(|n| n == col) {
941 return Err(syn::Error::new(
942 *span,
943 format!(
944 "`default_order = \"...\"`: \"{col}\" is not a declared field on this struct"
945 ),
946 ));
947 }
948 }
949
950 check_known_columns(&container.indexes, &container.get_latest_by, &collected)?;
951
952 let audit_track_names: Option<Vec<String>> = container.audit.as_ref().map(|audit| {
955 audit
956 .track
957 .as_ref()
958 .map(|(names, _)| names.clone())
959 .unwrap_or_default()
960 });
961
962 let upsert_unique: Option<Vec<String>> = container
965 .indexes
966 .iter()
967 .find(|i| i.unique && i.where_clause.is_none() && !i.columns.is_empty())
968 .map(|i| i.columns.clone());
969
970 let mut all_indexes: Vec<IndexAttr> = container.indexes;
972 for field in &named.named {
973 let ident = field.ident.as_ref().expect("named");
974 let col = ident_name(ident);
976 if let Ok(fa) = parse_field_attrs(field) {
978 if fa.index {
979 let col_name = fa.column.clone().unwrap_or_else(|| col.clone());
980 let auto_name = capped_index_name(if fa.index_unique {
981 format!("{table}_{col_name}_uq_idx")
982 } else {
983 format!("{table}_{col_name}_idx")
984 });
985 all_indexes.push(IndexAttr {
986 span: field.span(),
987 name: fa.index_name.or(Some(auto_name)),
988 columns: vec![col_name],
989 unique: fa.index_unique,
990 method: fa.index_method,
991 where_clause: None,
992 include: Vec::new(),
993 });
994 }
995 }
996 }
997 for field in &named.named {
999 let (Ok(fa), Ok(det)) = (parse_field_attrs(field), detect_type(&field.ty)) else {
1000 continue;
1001 };
1002 if det.kind != DetectedKind::String || fa.max_length.is_some() {
1003 continue;
1004 }
1005 let col = fa
1006 .column
1007 .clone()
1008 .unwrap_or_else(|| ident_name(field.ident.as_ref().expect("named")));
1009 let relation = det.fk_inner.is_some() || fa.fk.is_some() || fa.o2o.is_some();
1011 if fa.primary_key
1012 || fa.unique
1013 || relation
1014 || all_indexes.iter().any(|i| i.columns.contains(&col))
1015 {
1016 return Err(syn::Error::new_spanned(
1017 field,
1018 "a primary-key, unique, indexed or foreign-key `String` needs \
1019 `max_length`: MySQL cannot index an unbounded (LONGTEXT) column",
1020 ));
1021 }
1022 }
1023
1024 let model_impl = model_impl_tokens(
1025 struct_name,
1026 &model_name,
1027 &table,
1028 display.as_deref(),
1029 app_label.as_deref(),
1030 container.admin.as_ref(),
1031 &container.default_order,
1032 &collected.field_schemas,
1033 collected.soft_delete_column.as_deref(),
1034 container.permissions,
1035 audit_track_names.as_deref(),
1036 &container.m2m,
1037 &all_indexes,
1038 &container.checks,
1039 &container.excludes,
1040 &container.composite_fks,
1041 &container.generic_fks,
1042 container.scope.as_deref(),
1043 container.access.as_deref(),
1044 container.is_view,
1045 container.verbose_name.as_deref(),
1046 container.verbose_name_plural.as_deref(),
1047 container.managed,
1048 container.base_manager_name.as_deref(),
1049 container.order_with_respect_to.as_deref(),
1050 container.proxy,
1051 &container.required_db_features,
1052 container.required_db_vendor.as_deref(),
1053 container.default_related_name.as_deref(),
1054 container.db_table_comment.as_deref(),
1055 container
1056 .get_latest_by
1057 .as_ref()
1058 .map(|(c, d, _)| (c.as_str(), *d)),
1059 &container.extra_permissions,
1060 &container.default_permissions,
1061 &container.global_scopes,
1062 &container.reverse_has_relations,
1063 &container.generic_has_relations,
1064 );
1065 let module_ident = column_module_ident(struct_name);
1066 let column_consts = column_const_tokens(&module_ident, &collected.column_entries);
1067 let audited_fields: Option<Vec<&ColumnEntry>> = container.audit.as_ref().map(|audit| {
1068 let track_set: Option<std::collections::HashSet<&str>> = audit
1069 .track
1070 .as_ref()
1071 .map(|(names, _)| names.iter().map(String::as_str).collect());
1072 collected
1073 .column_entries
1074 .iter()
1075 .filter(|c| {
1076 track_set
1077 .as_ref()
1078 .map_or(true, |s| s.contains(c.name.as_str()))
1079 })
1080 .collect()
1081 });
1082 let inherent_impl = inherent_impl_tokens(
1083 struct_name,
1084 &collected,
1085 collected.primary_key.as_ref(),
1086 &column_consts,
1087 audited_fields.as_deref(),
1088 upsert_unique.as_deref(),
1089 &container.manager_fns,
1090 );
1091 let column_module = column_module_tokens(&module_ident, struct_name, &collected.column_entries);
1092 let from_row_impl = from_row_impl_tokens(
1093 struct_name,
1094 &collected.from_row_inits,
1095 &collected.from_row_inits_my,
1096 );
1097 let reverse_helpers = reverse_helper_tokens(
1098 struct_name,
1099 &collected.fk_relations,
1100 container.default_related_name.as_deref(),
1101 )?;
1102 let m2m_accessors = m2m_accessor_tokens(struct_name, &container.m2m);
1103 let generic_m2m_accessors = generic_m2m_accessor_tokens(struct_name, &container.generic_m2m);
1104 let through_accessors = through_accessor_tokens(struct_name, &container.through_relations);
1106 let reverse_has_accessors = reverse_has_accessor_tokens(
1108 struct_name,
1109 &container.reverse_has_relations,
1110 &collected.column_entries,
1111 );
1112 let generic_fk_accessors = generic_fk_accessor_tokens(
1113 struct_name,
1114 &container.generic_fks,
1115 &collected.column_entries,
1116 );
1117
1118 Ok(quote! {
1119 #model_impl
1120 #inherent_impl
1121 #from_row_impl
1122 #column_module
1123 #reverse_helpers
1124 #m2m_accessors
1125 #generic_m2m_accessors
1126 #through_accessors
1127 #reverse_has_accessors
1128 #generic_fk_accessors
1129
1130 #root::core::inventory::submit! {
1131 #root::core::ModelEntry::new(
1136 <#struct_name as #root::core::Model>::SCHEMA,
1137 ::core::module_path!(),
1138 )
1139 .with_audited(
1140 <#struct_name as #root::core::Model>::__rustango_audited_update,
1141 <#struct_name as #root::core::Model>::__rustango_audited_delete,
1142 )
1143 .with_audited_create(<#struct_name as #root::core::Model>::__rustango_audited_create)
1144 .with_audited_update_record(
1145 <#struct_name as #root::core::Model>::__rustango_audited_update_record,
1146 )
1147 .with_audited_cascade(<#struct_name as #root::core::Model>::__rustango_audited_cascade)
1148 }
1149 })
1150}
1151
1152fn check_known_columns(
1155 indexes: &[IndexAttr],
1156 get_latest_by: &Option<(String, bool, proc_macro2::Span)>,
1157 collected: &CollectedFields,
1158) -> syn::Result<()> {
1159 let is_column = |c: &str| collected.column_entries.iter().any(|e| e.column == c);
1160 let plain = |c: &str| c.chars().all(|ch| ch.is_ascii_alphanumeric() || ch == '_');
1162 let mut found: Vec<(proc_macro2::Span, String)> = Vec::new();
1163 for idx in indexes {
1164 for col in idx.columns.iter().filter(|c| plain(c) && !is_column(c)) {
1165 found.push((
1166 idx.span,
1167 format!("index column `{col}` is not a column of this model; its DDL will fail"),
1168 ));
1169 }
1170 }
1171 if let Some((col, _, span)) = get_latest_by {
1172 if !is_column(col) && !collected.field_names.iter().any(|n| n == col) {
1173 found.push((
1174 *span,
1175 format!("`get_latest_by`: `{col}` is not a field of this model"),
1176 ));
1177 }
1178 }
1179 let mut errors = found
1180 .into_iter()
1181 .map(|(span, note)| syn::Error::new(span, note));
1182 let Some(mut first) = errors.next() else {
1183 return Ok(());
1184 };
1185 first.extend(errors);
1186 Err(first)
1187}
1188
1189fn join_guard_tokens(rel: &FkRelation, receiver: &TokenStream2) -> TokenStream2 {
1192 let root = rustango_root();
1193 let parent_ty = &rel.parent_type;
1194 let field_ident = &rel.field_ident;
1195 let null_fk = if rel.nullable {
1196 quote! {
1197 if #receiver.#field_ident.is_none() {
1198 return ::core::result::Result::Ok(false);
1199 }
1200 }
1201 } else {
1202 quote! {}
1203 };
1204 quote! {
1205 #null_fk
1206 #root::sql::__rustango_require_join(
1207 row, <#parent_ty as #root::core::Model>::SCHEMA, alias,
1208 )?;
1209 }
1210}
1211
1212fn loaded_reuse_tokens(
1216 rel: &FkRelation,
1217 receiver: &TokenStream2,
1218 load: &TokenStream2,
1219) -> TokenStream2 {
1220 let root = rustango_root();
1221 let field_ident = &rel.field_ident;
1222 let pattern = if rel.nullable {
1223 quote!(::core::option::Option::Some(#root::sql::ForeignKey::Loaded { value: __loaded, .. }))
1224 } else {
1225 quote!(#root::sql::ForeignKey::Loaded { value: __loaded, .. })
1226 };
1227 quote! {
1228 if let #pattern = &mut #receiver.#field_ident {
1229 if let ::core::option::Option::Some(__r) = __rest {
1230 let _ = #load(&mut **__loaded, row, __r, &__next_alias)?;
1231 }
1232 return ::core::result::Result::Ok(true);
1233 }
1234 }
1235}
1236
1237fn loaded_assign_tokens(rel: &FkRelation, receiver: &TokenStream2) -> TokenStream2 {
1240 let root = rustango_root();
1241 let field_ident = &rel.field_ident;
1242 if rel.nullable {
1243 quote! {
1244 if let ::core::option::Option::Some(__fk) = &mut #receiver.#field_ident {
1245 let _pk = ::core::clone::Clone::clone(__fk.pk_ref());
1246 *__fk = #root::sql::ForeignKey::loaded(_pk, _parent);
1247 }
1248 }
1249 } else {
1250 quote! {
1251 let _pk = ::core::clone::Clone::clone(#receiver.#field_ident.pk_ref());
1252 #receiver.#field_ident = #root::sql::ForeignKey::loaded(_pk, _parent);
1253 }
1254 }
1255}
1256
1257fn load_related_impl_tokens(struct_name: &syn::Ident, fk_relations: &[FkRelation]) -> TokenStream2 {
1268 let root = rustango_root();
1269 let arms = fk_relations.iter().map(|rel| {
1270 let parent_ty = &rel.parent_type;
1271 let field_ident = &rel.field_ident;
1272 let fk_name = ident_name(field_ident);
1273 let fk_col = fk_name.as_str();
1274 let guard = join_guard_tokens(rel, "e!(self));
1275 let reuse = loaded_reuse_tokens(
1276 rel,
1277 "e!(self),
1278 "e!(#root::sql::LoadRelated::__rustango_load_related),
1279 );
1280 let assign = loaded_assign_tokens(rel, "e!(self));
1281 quote! {
1282 #fk_col => {
1283 #guard
1284 #reuse
1285 let mut _parent: #parent_ty = <#parent_ty>::__rustango_from_aliased_row(row, alias)?;
1286 if let ::core::option::Option::Some(__r) = __rest {
1292 let _ = #root::sql::LoadRelated::__rustango_load_related(
1293 &mut _parent, row, __r, &__next_alias,
1294 )?;
1295 }
1296 #assign
1297 ::core::result::Result::Ok(true)
1298 }
1299 }
1300 });
1301 quote! {
1302 #[cfg(feature = "postgres")]
1303 impl #root::sql::LoadRelated for #struct_name {
1304 #[allow(unused_variables)]
1305 fn __rustango_load_related(
1306 &mut self,
1307 row: &#root::sql::sqlx::postgres::PgRow,
1308 field_name: &str,
1309 alias: &str,
1310 ) -> ::core::result::Result<bool, #root::sql::sqlx::Error> {
1311 let (__base, __rest): (&str, ::core::option::Option<&str>) =
1317 match field_name.split_once("__") {
1318 ::core::option::Option::Some((b, r)) => (b, ::core::option::Option::Some(r)),
1319 ::core::option::Option::None => (field_name, ::core::option::Option::None),
1320 };
1321 let __next_alias: ::std::string::String = match __rest {
1322 ::core::option::Option::Some(__r) => {
1323 let __rb = __r.split_once("__").map(|(b, _)| b).unwrap_or(__r);
1324 ::std::format!("{}__{}", alias, __rb)
1325 }
1326 ::core::option::Option::None => ::std::string::String::new(),
1327 };
1328 match __base {
1329 #( #arms )*
1330 _ => ::core::result::Result::Ok(false),
1331 }
1332 }
1333 }
1334 }
1335}
1336
1337fn load_related_impl_my_tokens(
1345 struct_name: &syn::Ident,
1346 fk_relations: &[FkRelation],
1347) -> TokenStream2 {
1348 let root = rustango_root();
1349 let arms = fk_relations.iter().map(|rel| {
1350 let parent_ty = &rel.parent_type;
1351 let field_ident = &rel.field_ident;
1352 let fk_name = ident_name(field_ident);
1353 let fk_col = fk_name.as_str();
1354 let guard = join_guard_tokens(rel, "e!(__self));
1355 let reuse = loaded_reuse_tokens(
1356 rel,
1357 "e!(__self),
1358 "e!(#root::sql::LoadRelatedMy::__rustango_load_related_my),
1359 );
1360 let assign = loaded_assign_tokens(rel, "e!(__self));
1361 quote! {
1366 #fk_col => {
1367 #guard
1368 #reuse
1369 let mut _parent: #parent_ty =
1370 <#parent_ty>::__rustango_from_aliased_my_row(row, alias)?;
1371 if let ::core::option::Option::Some(__r) = __rest {
1374 let _ = #root::sql::LoadRelatedMy::__rustango_load_related_my(
1375 &mut _parent, row, __r, &__next_alias,
1376 )?;
1377 }
1378 #assign
1379 ::core::result::Result::Ok(true)
1380 }
1381 }
1382 });
1383 quote! {
1384 #root::__impl_my_load_related!(#struct_name, |__self, row, field_name, alias, __rest, __next_alias| {
1385 #( #arms )*
1386 });
1387 }
1388}
1389
1390fn load_related_impl_sqlite_tokens(
1394 struct_name: &syn::Ident,
1395 fk_relations: &[FkRelation],
1396) -> TokenStream2 {
1397 let root = rustango_root();
1398 let arms = fk_relations.iter().map(|rel| {
1399 let parent_ty = &rel.parent_type;
1400 let field_ident = &rel.field_ident;
1401 let fk_name = ident_name(field_ident);
1402 let fk_col = fk_name.as_str();
1403 let guard = join_guard_tokens(rel, "e!(__self));
1404 let reuse = loaded_reuse_tokens(
1405 rel,
1406 "e!(__self),
1407 "e!(#root::sql::LoadRelatedSqlite::__rustango_load_related_sqlite),
1408 );
1409 let assign = loaded_assign_tokens(rel, "e!(__self));
1410 quote! {
1411 #fk_col => {
1412 #guard
1413 #reuse
1414 let mut _parent: #parent_ty =
1415 <#parent_ty>::__rustango_from_aliased_sqlite_row(row, alias)?;
1416 if let ::core::option::Option::Some(__r) = __rest {
1419 let _ = #root::sql::LoadRelatedSqlite::__rustango_load_related_sqlite(
1420 &mut _parent, row, __r, &__next_alias,
1421 )?;
1422 }
1423 #assign
1424 ::core::result::Result::Ok(true)
1425 }
1426 }
1427 });
1428 quote! {
1429 #root::__impl_sqlite_load_related!(#struct_name, |__self, row, field_name, alias, __rest, __next_alias| {
1430 #( #arms )*
1431 });
1432 }
1433}
1434
1435fn fk_pk_access_impl_tokens(struct_name: &syn::Ident, fk_relations: &[FkRelation]) -> TokenStream2 {
1443 let root = rustango_root();
1444 let arms = fk_relations.iter().map(|rel| {
1445 let field_ident = &rel.field_ident;
1446 let fk_name = ident_name(field_ident);
1447 let fk_col = fk_name.as_str();
1448 if rel.pk_kind == DetectedKind::I64 {
1449 if rel.nullable {
1455 quote! {
1456 #fk_col => self.#field_ident
1457 .as_ref()
1458 .map(|fk| #root::sql::ForeignKey::pk(fk)),
1459 }
1460 } else {
1461 quote! {
1462 #fk_col => ::core::option::Option::Some(self.#field_ident.pk()),
1463 }
1464 }
1465 } else {
1466 quote! {
1474 #fk_col => ::core::option::Option::None,
1475 }
1476 }
1477 });
1478 let value_arms = fk_relations.iter().map(|rel| {
1484 let field_ident = &rel.field_ident;
1485 let fk_name = ident_name(field_ident);
1486 let fk_col = fk_name.as_str();
1487 if rel.nullable {
1488 quote! {
1489 #fk_col => self.#field_ident
1490 .as_ref()
1491 .map(|fk| ::core::convert::Into::<#root::core::SqlValue>::into(
1492 #root::sql::ForeignKey::pk(fk)
1493 )),
1494 }
1495 } else {
1496 quote! {
1497 #fk_col => ::core::option::Option::Some(
1498 ::core::convert::Into::<#root::core::SqlValue>::into(
1499 self.#field_ident.pk()
1500 )
1501 ),
1502 }
1503 }
1504 });
1505 quote! {
1506 impl #root::sql::FkPkAccess for #struct_name {
1507 #[allow(unused_variables)]
1508 fn __rustango_fk_pk(&self, field_name: &str) -> ::core::option::Option<i64> {
1509 match field_name {
1510 #( #arms )*
1511 _ => ::core::option::Option::None,
1512 }
1513 }
1514 #[allow(unused_variables)]
1515 fn __rustango_fk_pk_value(
1516 &self,
1517 field_name: &str,
1518 ) -> ::core::option::Option<#root::core::SqlValue> {
1519 match field_name {
1520 #( #value_arms )*
1521 _ => ::core::option::Option::None,
1522 }
1523 }
1524 }
1525 }
1526}
1527
1528const NO_REVERSE_ACCESSOR: &str = "+";
1530
1531fn reverse_helper_tokens(
1539 child_ident: &syn::Ident,
1540 fk_relations: &[FkRelation],
1541 default_related_name: Option<&str>,
1542) -> syn::Result<TokenStream2> {
1543 let root = rustango_root();
1544 let fk_relations: Vec<&FkRelation> = fk_relations
1546 .iter()
1547 .filter(|rel| rel.related_name.as_deref() != Some(NO_REVERSE_ACCESSOR))
1548 .collect();
1549 if fk_relations.is_empty() {
1550 return Ok(TokenStream2::new());
1551 }
1552 let default_pg_suffix = default_related_name
1566 .map(str::to_owned)
1567 .unwrap_or_else(|| format!("{}_set", to_snake_case(&ident_name(child_ident))));
1568 let name_of = |rel: &FkRelation| {
1569 rel.related_name
1570 .clone()
1571 .unwrap_or_else(|| default_pg_suffix.clone())
1572 };
1573 for (i, rel) in fk_relations.iter().enumerate() {
1575 let parent_ty = &rel.parent_type;
1576 let parent = quote!(#parent_ty).to_string();
1577 let name = name_of(rel);
1578 if let Some(first) = fk_relations[..i].iter().find(|r| {
1579 let other = &r.parent_type;
1580 quote!(#other).to_string() == parent && name_of(r) == name
1581 }) {
1582 return Err(syn::Error::new_spanned(
1583 &rel.field_ident,
1584 format!(
1585 "`{}` and `{}` both point at `{parent}` and would both add `{parent}::{name}`; \
1586 give one `#[rustango(related_name = \"...\")]`, or `related_name = \"+\"` for none",
1587 first.field_ident, rel.field_ident
1588 ),
1589 ));
1590 }
1591 }
1592 let impls = fk_relations.iter().map(|rel| {
1593 let pg_suffix = name_of(rel);
1594 let pool_suffix = format!("{}_pool", pg_suffix);
1595 let pg_method_ident = syn::Ident::new(&pg_suffix, child_ident.span());
1596 let pool_method_ident = syn::Ident::new(&pool_suffix, child_ident.span());
1597 let parent_ty = &rel.parent_type;
1598 let fk_name = ident_name(&rel.field_ident);
1599 let fk_col = fk_name.as_str();
1600 let doc = format!(
1601 "Fetch every `{child_ident}` whose `{fk_col}` foreign key points at this row. \
1602 Single SQL query — `SELECT … FROM <{child_ident} table> WHERE {fk_col} = $1` — \
1603 generated from the FK declaration on `{child_ident}::{fk_col}`. Composes with \
1604 further `{child_ident}::objects()` filters via direct queryset use."
1605 );
1606 quote! {
1607 impl #parent_ty {
1608 #[doc = #doc]
1609 pub async fn #pg_method_ident<'_c, _E: #root::sql::SqlExecutor<'_c>>(
1617 &self,
1618 _executor: _E,
1619 ) -> ::core::result::Result<
1620 ::std::vec::Vec<#child_ident>,
1621 #root::sql::ExecError,
1622 > {
1623 let _key = #root::sql::__reverse_fk_key::<Self, #child_ident>(self, #fk_col)?;
1624 #root::query::QuerySet::<#child_ident>::new()
1625 .filter_op(#fk_col, #root::core::Op::Eq, _key)
1626 .fetch_on(_executor)
1627 .await
1628 }
1629
1630 #[deprecated(since = "0.62.0", note = "drop the `_pool` suffix")]
1635 pub async fn #pool_method_ident(
1636 &self,
1637 pool: &#root::sql::Pool,
1638 ) -> ::core::result::Result<
1639 ::std::vec::Vec<#child_ident>,
1640 #root::sql::ExecError,
1641 > {
1642 self.#pg_method_ident(pool).await
1643 }
1644 }
1645 }
1646 });
1647 Ok(quote! { #( #impls )* })
1648}
1649
1650fn generic_fk_accessor_tokens(
1664 struct_name: &syn::Ident,
1665 generic_fks: &[GenericFkAttr],
1666 column_entries: &[ColumnEntry],
1667) -> TokenStream2 {
1668 let root = rustango_root();
1669 if generic_fks.is_empty() {
1670 return TokenStream2::new();
1671 }
1672 let methods = generic_fks.iter().filter_map(|gfk| {
1673 let ct_ident = column_entries
1679 .iter()
1680 .find(|c| c.column == gfk.ct_column)
1681 .map(|c| c.ident.clone())?;
1682 let pk_ident = column_entries
1683 .iter()
1684 .find(|c| c.column == gfk.pk_column)
1685 .map(|c| c.ident.clone())?;
1686
1687 let accessor_ident =
1688 syn::Ident::new(&format!("{}_pool", gfk.name), struct_name.span());
1689 let setter_ident =
1690 syn::Ident::new(&format!("set_{}_for", gfk.name), struct_name.span());
1691 let name_literal = gfk.name.as_str();
1692
1693 Some(quote! {
1694 #[doc = concat!(
1695 "Resolve the polymorphic `",
1696 #name_literal,
1697 "` relation. Reads `self.",
1698 stringify!(#ct_ident),
1699 "` + `self.",
1700 stringify!(#pk_ident),
1701 "`, looks up the matching `ContentType`, and fetches the target row as a JSON map.\n\n",
1702 "Returns `Ok(None)` when the ContentType is stale / unseeded or the target row was deleted. Emitted by `#[rustango(generic_fk(name = \"",
1703 #name_literal,
1704 "\", ...))]`."
1705 )]
1706 pub async fn #accessor_ident(
1707 &self,
1708 pool: &#root::sql::Pool,
1709 ) -> ::core::result::Result<
1710 ::core::option::Option<#root::__serde_json::Value>,
1711 #root::sql::ExecError,
1712 > {
1713 let gfk = #root::contenttypes::GenericForeignKey::new(
1714 self.#ct_ident as i64,
1715 self.#pk_ident as i64,
1716 );
1717 gfk.get_object(pool).await
1718 }
1719
1720 #[doc = concat!(
1721 "Set the polymorphic `",
1722 #name_literal,
1723 "` target. Looks up the `ContentType` for `T` via the cached registry, then assigns both `self.",
1724 stringify!(#ct_ident),
1725 "` and `self.",
1726 stringify!(#pk_ident),
1727 "`.\n\nFollow with `self.insert(pool)` or `self.update(pool)` to persist. Emitted by `#[rustango(generic_fk(name = \"",
1728 #name_literal,
1729 "\", ...))]`."
1730 )]
1731 pub async fn #setter_ident<T: #root::core::Model>(
1732 &mut self,
1733 pool: &#root::sql::Pool,
1734 target_pk: i64,
1735 ) -> ::core::result::Result<(), #root::sql::ExecError> {
1736 let gfk = #root::contenttypes::GenericForeignKey::for_target::<T>(
1737 pool,
1738 target_pk,
1739 ).await?;
1740 self.#ct_ident = gfk.content_type_id as _;
1741 self.#pk_ident = gfk.object_pk as _;
1742 ::core::result::Result::Ok(())
1743 }
1744 })
1745 });
1746 quote! {
1747 impl #struct_name {
1748 #( #methods )*
1749 }
1750 }
1751}
1752
1753fn m2m_accessor_tokens(struct_name: &syn::Ident, m2m_relations: &[M2MAttr]) -> TokenStream2 {
1754 let root = rustango_root();
1755 if m2m_relations.is_empty() {
1756 return TokenStream2::new();
1757 }
1758 let methods = m2m_relations.iter().map(|rel| {
1759 let method_name = format!("{}_m2m", rel.name);
1760 let method_ident = syn::Ident::new(&method_name, struct_name.span());
1761 let through = rel.through.as_str();
1762 let src_col = rel.src.as_str();
1763 let dst_col = rel.dst.as_str();
1764 quote! {
1765 pub fn #method_ident(&self) -> #root::sql::M2MManager {
1766 #root::sql::M2MManager {
1767 src_pk: self.__rustango_pk_value(),
1768 through: #through,
1769 src_col: #src_col,
1770 dst_col: #dst_col,
1771 }
1772 }
1773 }
1774 });
1775 quote! {
1776 impl #struct_name {
1777 #( #methods )*
1778 }
1779 }
1780}
1781
1782fn generic_m2m_accessor_tokens(
1785 struct_name: &syn::Ident,
1786 relations: &[GenericM2MAttr],
1787) -> TokenStream2 {
1788 let root = rustango_root();
1789 if relations.is_empty() {
1790 return TokenStream2::new();
1791 }
1792 let methods = relations.iter().map(|rel| {
1793 let method_ident = syn::Ident::new(&format!("{}_m2m", rel.name), struct_name.span());
1794 let through = rel.through.as_str();
1795 let pk_col = rel.pk_column.as_str();
1796 let ct_col = rel.ct_column.as_str();
1797 let related_col = rel.related_column.as_str();
1798 quote! {
1799 pub fn #method_ident(&self) -> #root::sql::GenericM2MManager {
1800 #root::sql::GenericM2MManager {
1801 src_pk: self.__rustango_pk_value(),
1802 src_schema: <Self as #root::core::Model>::SCHEMA,
1803 through: #through,
1804 pk_col: #pk_col,
1805 ct_col: #ct_col,
1806 dst_col: #related_col,
1807 }
1808 }
1809 }
1810 });
1811 let entries = relations.iter().map(|rel| {
1813 let through = rel.through.as_str();
1814 let pk_col = rel.pk_column.as_str();
1815 let ct_col = rel.ct_column.as_str();
1816 quote! {
1817 #root::core::inventory::submit! {
1818 #root::core::GenericM2MEntry::new(
1819 <#struct_name as #root::core::Model>::SCHEMA,
1820 #through,
1821 #pk_col,
1822 #ct_col,
1823 )
1824 }
1825 }
1826 });
1827 quote! {
1828 impl #struct_name {
1829 #( #methods )*
1830 }
1831 #( #entries )*
1832 }
1833}
1834
1835fn reverse_has_accessor_tokens(
1857 struct_name: &syn::Ident,
1858 reverse_has_relations: &[ReverseHasAttr],
1859 columns: &[ColumnEntry],
1860) -> TokenStream2 {
1861 let root = rustango_root();
1862 if reverse_has_relations.is_empty() {
1863 return TokenStream2::new();
1864 }
1865 let methods = reverse_has_relations.iter().map(|rel| {
1866 let exists_name = format!("{}_exists_expr", rel.name);
1867 let not_exists_name = format!("{}_not_exists_expr", rel.name);
1868 let count_name = format!("{}_count", rel.name);
1869 let fetch_name = format!("{}_fetch", rel.name);
1870 let first_name = format!("{}_first", rel.name);
1871 let pluck_name = format!("{}_pluck", rel.name);
1872 let accessor_name = rel.name.as_str();
1873 let exists_ident = syn::Ident::new(&exists_name, struct_name.span());
1874 let not_exists_ident = syn::Ident::new(¬_exists_name, struct_name.span());
1875 let count_ident = syn::Ident::new(&count_name, struct_name.span());
1876 let fetch_ident = syn::Ident::new(&fetch_name, struct_name.span());
1877 let first_ident = syn::Ident::new(&first_name, struct_name.span());
1878 let pluck_ident = syn::Ident::new(&pluck_name, struct_name.span());
1879 let accessor_ident = syn::Ident::new(accessor_name, struct_name.span());
1880 let child = &rel.child;
1881 let child_fk_column = rel.child_fk_column.as_str();
1882 let self_pk_column = rel.self_pk_column();
1883 let self_value = columns
1885 .iter()
1886 .find(|c| c.column == self_pk_column)
1887 .map_or_else(
1888 || quote! { self.__rustango_pk_value() },
1889 |c| {
1890 let ident = &c.ident;
1891 quote! {
1892 ::core::convert::Into::<#root::core::SqlValue>::into(
1893 ::core::clone::Clone::clone(&self.#ident)
1894 )
1895 }
1896 },
1897 );
1898 let exists_doc = format!(
1899 "Eloquent `whereHas` analog — yields `EXISTS (SELECT 1 \
1900 FROM <{child}> WHERE {child_fk_column} = <outer>.{self_pk_column})`. \
1901 Drop into `QuerySet::<{struct_name}>::where_raw(...)` to \
1902 filter to {struct_name}s with at least one matching child.",
1903 );
1904 let not_exists_doc = format!(
1905 "Eloquent `whereDoesntHave` analog — yields `NOT EXISTS \
1906 (SELECT 1 FROM <{child}> WHERE {child_fk_column} = \
1907 <outer>.{self_pk_column})`. Drop into \
1908 `QuerySet::<{struct_name}>::where_raw(...)` to filter to \
1909 {struct_name}s with **no** matching child.",
1910 );
1911 let count_doc = format!(
1912 "Eloquent `$model->{name}->count()` analog — returns \
1913 the number of `{child}` rows whose `{child_fk_column}` \
1914 matches this `{struct_name}` instance's `{self_pk_column}`. \
1915 Issued as `SELECT COUNT(*) FROM <{child}> WHERE \
1916 {child_fk_column} = <self.{self_pk_column}>`.",
1917 name = rel.name,
1918 );
1919 let accessor_doc = format!(
1920 "Eloquent `$model->{name}` accessor — returns a \
1921 `QuerySet<{child}>` filtered to rows whose \
1922 `{child_fk_column}` matches this `{struct_name}` \
1923 instance's `{self_pk_column}`. **Chainable**: compose `.filter()` \
1924 / `.order_by()` / `.limit()` etc. on top, then call \
1925 `.fetch(&pool)` (the QuerySet trait method) when \
1926 done. For the simple \"fetch all\" hot path with no \
1927 further composition, prefer the bare-name \
1928 `{name}_fetch(&pool)` companion.",
1929 name = rel.name,
1930 );
1931 let fetch_doc = format!(
1932 "Eloquent `$model->{name}->get()` — bare-name hot-path \
1933 over `{name}(&self).fetch(&pool)`. Use this when \
1934 you don't need further `.filter()` / `.order_by()` \
1935 composition; falls back to the chainable accessor when \
1936 you do. Avoids the `_pool` suffix on the most common \
1937 call-site shape.",
1938 name = rel.name,
1939 );
1940 quote! {
1941 #[doc = #accessor_doc]
1942 pub fn #accessor_ident(&self) -> #root::query::QuerySet<#child> {
1943 #root::query::QuerySet::<#child>::new()
1944 .filter(#child_fk_column, #self_value)
1945 }
1946
1947 #[doc = #fetch_doc]
1948 pub async fn #fetch_ident(
1949 &self,
1950 pool: &#root::sql::Pool,
1951 ) -> ::core::result::Result<
1952 ::std::vec::Vec<#child>,
1953 #root::sql::ExecError,
1954 > {
1955 use #root::sql::FetcherPool as _;
1956 self.#accessor_ident().fetch(pool).await
1957 }
1958
1959 pub async fn #first_ident(
1966 &self,
1967 pool: &#root::sql::Pool,
1968 ) -> ::core::result::Result<
1969 ::core::option::Option<#child>,
1970 #root::sql::ExecError,
1971 > {
1972 self.#accessor_ident().first(pool).await
1973 }
1974
1975 pub async fn #pluck_ident<U>(
1980 &self,
1981 col: &'static str,
1982 pool: &#root::sql::Pool,
1983 ) -> ::core::result::Result<
1984 ::std::vec::Vec<U>,
1985 #root::sql::ExecError,
1986 >
1987 where
1988 U: #root::sql::FlatScalar,
1989 {
1990 self.#accessor_ident()
1991 .values_list_flat(col)
1992 .fetch::<U>(pool)
1993 .await
1994 }
1995
1996 #[doc = #exists_doc]
1997 pub fn #exists_ident() -> #root::core::WhereExpr {
1998 use #root::core::{Expr, Model as _, Op, SelectQuery, WhereExpr};
1999 let child_schema =
2000 <#child as #root::core::Model>::SCHEMA;
2001 let inner = SelectQuery::new(child_schema).where_clause(WhereExpr::ExprCompare {
2002 lhs: Expr::Column(#child_fk_column),
2003 op: Op::Eq,
2004 rhs: Expr::OuterRef(#self_pk_column),
2005 });
2006 WhereExpr::Exists(::std::boxed::Box::new(inner))
2007 }
2008
2009 #[doc = #not_exists_doc]
2010 pub fn #not_exists_ident() -> #root::core::WhereExpr {
2011 use #root::core::{Expr, Model as _, Op, SelectQuery, WhereExpr};
2012 let child_schema =
2013 <#child as #root::core::Model>::SCHEMA;
2014 let inner = SelectQuery::new(child_schema).where_clause(WhereExpr::ExprCompare {
2015 lhs: Expr::Column(#child_fk_column),
2016 op: Op::Eq,
2017 rhs: Expr::OuterRef(#self_pk_column),
2018 });
2019 WhereExpr::NotExists(::std::boxed::Box::new(inner))
2020 }
2021
2022 #[doc = #count_doc]
2023 pub async fn #count_ident(
2024 &self,
2025 pool: &#root::sql::Pool,
2026 ) -> ::core::result::Result<
2027 i64,
2028 #root::sql::ExecError,
2029 > {
2030 use #root::sql::CounterPool as _;
2031 self.#accessor_ident().count(pool).await
2032 }
2033 }
2034 });
2035 quote! {
2036 impl #struct_name {
2037 #( #methods )*
2038 }
2039 }
2040}
2041
2042fn through_accessor_tokens(
2060 struct_name: &syn::Ident,
2061 through_relations: &[ThroughAttr],
2062) -> TokenStream2 {
2063 let root = rustango_root();
2064 if through_relations.is_empty() {
2065 return TokenStream2::new();
2066 }
2067 let methods = through_relations.iter().map(|rel| {
2068 let method_name = format!("{}_through", rel.name);
2069 let count_name = format!("{}_through_count", rel.name);
2070 let fetch_name = format!("{}_through_fetch", rel.name);
2071 let first_name = format!("{}_through_first", rel.name);
2072 let pluck_name = format!("{}_through_pluck", rel.name);
2073 let method_ident = syn::Ident::new(&method_name, struct_name.span());
2074 let count_ident = syn::Ident::new(&count_name, struct_name.span());
2075 let fetch_ident = syn::Ident::new(&fetch_name, struct_name.span());
2076 let first_ident = syn::Ident::new(&first_name, struct_name.span());
2077 let pluck_ident = syn::Ident::new(&pluck_name, struct_name.span());
2078 let far = &rel.far;
2079 let intermediate = &rel.intermediate;
2080 let far_fk_column = rel.far_fk_column.as_str();
2081 let intermediate_fk_column = rel.intermediate_fk_column.as_str();
2082 let intermediate_pk_column = rel.intermediate_pk_column.as_str();
2083 let doc = format!(
2084 "Eloquent `hasManyThrough` accessor — returns a \
2085 `QuerySet<{far}>` whose rows reach this `{struct_name}` \
2086 instance through the intermediate `{intermediate}` table. \
2087 Generated SQL shape: \
2088 `… WHERE {far_fk_column} IN (SELECT \
2089 {intermediate_pk_column} FROM <{intermediate}> WHERE \
2090 {intermediate_fk_column} = self.pk)`. Chainable like any \
2091 other QuerySet — compose `.filter()` / `.order_by()` / \
2092 `.limit()` etc. on top.",
2093 );
2094 let count_doc = format!(
2095 "Eloquent `$model->{name}->count()` analog for the \
2096 through-relation — returns the number of `{far}` rows \
2097 reachable through `{intermediate}`. Equivalent to \
2098 `self.{name}_through().count(pool)` but spelled \
2099 as a bare instance method for parity with the \
2100 `reverse_has` `<name>_count` shape.",
2101 name = rel.name,
2102 );
2103 quote! {
2104 #[doc = #doc]
2105 pub fn #method_ident(&self) -> #root::query::QuerySet<#far> {
2106 use #root::core::{Filter, Model as _, Op, SelectQuery, WhereExpr};
2107 let intermediate_schema =
2108 <#intermediate as #root::core::Model>::SCHEMA;
2109 let sub = SelectQuery::new(intermediate_schema)
2110 .where_clause(WhereExpr::Predicate(Filter::new(
2111 #intermediate_fk_column,
2112 Op::Eq,
2113 self.__rustango_pk_value(),
2114 )))
2115 .projection(::std::vec![#intermediate_pk_column]);
2116 #root::query::QuerySet::<#far>::new().where_raw(
2117 WhereExpr::InSubquery {
2118 column: #far_fk_column,
2119 negated: false,
2120 subquery: ::std::boxed::Box::new(sub),
2121 },
2122 )
2123 }
2124
2125 #[doc = #count_doc]
2126 pub async fn #count_ident(
2127 &self,
2128 pool: &#root::sql::Pool,
2129 ) -> ::core::result::Result<
2130 i64,
2131 #root::sql::ExecError,
2132 > {
2133 use #root::sql::CounterPool as _;
2134 self.#method_ident().count(pool).await
2135 }
2136
2137 pub async fn #fetch_ident(
2145 &self,
2146 pool: &#root::sql::Pool,
2147 ) -> ::core::result::Result<
2148 ::std::vec::Vec<#far>,
2149 #root::sql::ExecError,
2150 > {
2151 use #root::sql::FetcherPool as _;
2152 self.#method_ident().fetch(pool).await
2153 }
2154
2155 pub async fn #first_ident(
2162 &self,
2163 pool: &#root::sql::Pool,
2164 ) -> ::core::result::Result<
2165 ::core::option::Option<#far>,
2166 #root::sql::ExecError,
2167 > {
2168 self.#method_ident().first(pool).await
2169 }
2170
2171 pub async fn #pluck_ident<U>(
2175 &self,
2176 col: &'static str,
2177 pool: &#root::sql::Pool,
2178 ) -> ::core::result::Result<
2179 ::std::vec::Vec<U>,
2180 #root::sql::ExecError,
2181 >
2182 where
2183 U: #root::sql::FlatScalar,
2184 {
2185 self.#method_ident()
2186 .values_list_flat(col)
2187 .fetch::<U>(pool)
2188 .await
2189 }
2190 }
2191 });
2192 quote! {
2193 impl #struct_name {
2194 #( #methods )*
2195 }
2196 }
2197}
2198
2199struct ColumnEntry {
2200 ident: syn::Ident,
2203 value_ty: Type,
2205 name: String,
2207 column: String,
2209 field_type_tokens: TokenStream2,
2211 uuid_column: bool,
2213}
2214
2215struct CollectedFields {
2216 field_schemas: Vec<TokenStream2>,
2217 from_row_inits: Vec<TokenStream2>,
2218 from_aliased_row_inits: Vec<TokenStream2>,
2222 from_row_inits_my: Vec<TokenStream2>,
2224 from_aliased_row_inits_my: Vec<TokenStream2>,
2225 insert_columns: Vec<TokenStream2>,
2228 insert_values: Vec<TokenStream2>,
2231 insert_pushes: Vec<TokenStream2>,
2236 returning_cols: Vec<TokenStream2>,
2239 auto_assigns: Vec<TokenStream2>,
2242 auto_field_idents: Vec<(syn::Ident, String, bool)>,
2246 generated_field_idents: Vec<(syn::Ident, String)>,
2251 first_db_auto: Option<(syn::Ident, Type)>,
2254 bulk_pushes_no_auto: Vec<TokenStream2>,
2258 bulk_pushes_all: Vec<TokenStream2>,
2262 bulk_columns_no_auto: Vec<TokenStream2>,
2265 bulk_columns_all: Vec<TokenStream2>,
2268 bulk_fill_in_place: Vec<TokenStream2>,
2271 rust_filled_idents: Vec<syn::Ident>,
2273 auto_now_idents: Vec<syn::Ident>,
2275 uuid_v7_fields: Vec<(syn::Ident, String)>,
2278 bulk_auto_uniformity: Vec<TokenStream2>,
2282 first_auto_ident: Option<syn::Ident>,
2285 has_auto: bool,
2287 pk_is_auto: bool,
2291 update_assignments: Vec<TokenStream2>,
2294 upsert_update_columns: Vec<TokenStream2>,
2297 primary_key: Option<(syn::Ident, String)>,
2298 column_entries: Vec<ColumnEntry>,
2299 field_names: Vec<String>,
2302 fk_relations: Vec<FkRelation>,
2307 soft_delete_column: Option<String>,
2312 soft_delete_field_ident: Option<syn::Ident>,
2316}
2317
2318#[derive(Clone)]
2319struct FkRelation {
2320 parent_type: Type,
2323 field_ident: syn::Ident,
2326 pk_kind: DetectedKind,
2331 nullable: bool,
2336 related_name: Option<String>,
2341}
2342
2343fn collect_fields(named: &syn::FieldsNamed, table: &str) -> syn::Result<CollectedFields> {
2344 let root = rustango_root();
2345 let cap = named.named.len();
2346 let mut out = CollectedFields {
2347 field_schemas: Vec::with_capacity(cap),
2348 from_row_inits: Vec::with_capacity(cap),
2349 from_aliased_row_inits: Vec::with_capacity(cap),
2350 from_row_inits_my: Vec::with_capacity(cap),
2351 from_aliased_row_inits_my: Vec::with_capacity(cap),
2352 insert_columns: Vec::with_capacity(cap),
2353 insert_values: Vec::with_capacity(cap),
2354 insert_pushes: Vec::with_capacity(cap),
2355 returning_cols: Vec::new(),
2356 auto_assigns: Vec::new(),
2357 auto_field_idents: Vec::new(),
2358 generated_field_idents: Vec::new(),
2359 first_db_auto: None,
2360 bulk_pushes_no_auto: Vec::with_capacity(cap),
2361 bulk_pushes_all: Vec::with_capacity(cap),
2362 bulk_columns_no_auto: Vec::with_capacity(cap),
2363 bulk_columns_all: Vec::with_capacity(cap),
2364 bulk_fill_in_place: Vec::new(),
2365 rust_filled_idents: Vec::new(),
2366 auto_now_idents: Vec::new(),
2367 uuid_v7_fields: Vec::new(),
2368 bulk_auto_uniformity: Vec::new(),
2369 first_auto_ident: None,
2370 has_auto: false,
2371 pk_is_auto: false,
2372 update_assignments: Vec::with_capacity(cap),
2373 upsert_update_columns: Vec::with_capacity(cap),
2374 primary_key: None,
2375 column_entries: Vec::with_capacity(cap),
2376 field_names: Vec::with_capacity(cap),
2377 fk_relations: Vec::new(),
2378 soft_delete_column: None,
2379 soft_delete_field_ident: None,
2380 };
2381
2382 for field in &named.named {
2383 let info = process_field(field, table)?;
2384 out.field_names.push(ident_name(info.ident));
2385 out.field_schemas.push(info.schema);
2386 out.from_row_inits.push(info.from_row_init);
2387 out.from_aliased_row_inits.push(info.from_aliased_row_init);
2388 out.from_row_inits_my.push(info.from_row_init_my);
2389 out.from_aliased_row_inits_my
2390 .push(info.from_aliased_row_init_my);
2391 if let Some(parent_ty) = info.fk_inner.clone() {
2392 out.fk_relations.push(FkRelation {
2393 parent_type: parent_ty,
2394 field_ident: info.ident.clone(),
2395 pk_kind: info.fk_pk_kind,
2396 nullable: info.nullable,
2397 related_name: info.related_name.clone(),
2398 });
2399 }
2400 if info.soft_delete {
2401 if out.soft_delete_column.is_some() {
2402 return Err(syn::Error::new_spanned(
2403 field,
2404 "only one field may be marked `#[rustango(soft_delete)]`",
2405 ));
2406 }
2407 out.soft_delete_column = Some(info.column.clone());
2408 out.soft_delete_field_ident = Some(info.ident.clone());
2409 }
2410 let column = info.column.as_str();
2411 let ident = info.ident;
2412 if info.generated_as.is_some() {
2421 out.column_entries.push(ColumnEntry {
2422 ident: ident.clone(),
2423 value_ty: info.value_ty.clone(),
2424 name: ident_name(ident),
2425 column: info.column.clone(),
2426 field_type_tokens: info.field_type_tokens,
2427 uuid_column: info.uuid_column,
2428 });
2429 out.returning_cols.push(quote!(#column));
2436 out.generated_field_idents
2437 .push((ident.clone(), info.column.clone()));
2438 continue;
2439 }
2440 out.insert_columns.push(quote!(#column));
2441 out.insert_values.push(quote! {
2442 ::core::convert::Into::<#root::core::SqlValue>::into(
2443 ::core::clone::Clone::clone(&self.#ident)
2444 )
2445 });
2446 if info.auto {
2447 out.has_auto = true;
2448 if out.first_auto_ident.is_none() {
2449 out.first_auto_ident = Some(ident.clone());
2450 }
2451 if !info.default_uuid_v7 && !info.auto_now_add && !info.auto_now {
2464 if out.first_db_auto.is_none() {
2465 out.first_db_auto = auto_inner_type(info.value_ty)
2466 .cloned()
2467 .map(|ty| (ident.clone(), ty));
2468 }
2469 out.returning_cols.push(quote!(#column));
2470 out.auto_field_idents.push((
2471 ident.clone(),
2472 info.column.clone(),
2473 info.fk_pk_kind == DetectedKind::Uuid,
2474 ));
2475 out.auto_assigns.push(quote! {
2476 self.#ident = #root::sql::try_get_returning(_returning_row, #column)?;
2477 });
2478 }
2479 if info.default_uuid_v7 {
2480 out.insert_pushes.push(quote! {
2486 if matches!(&self.#ident, #root::sql::Auto::Unset) {
2487 self.#ident = #root::sql::Auto::Set(
2488 #root::__uuid::Uuid::now_v7(),
2489 );
2490 }
2491 if let #root::sql::Auto::Set(_v) = &self.#ident {
2492 _columns.push(#column);
2493 _values.push(::core::convert::Into::<#root::core::SqlValue>::into(
2494 ::core::clone::Clone::clone(_v)
2495 ));
2496 }
2497 });
2498 } else if info.auto_now_add || info.auto_now {
2499 out.insert_pushes.push(quote! {
2521 if matches!(&self.#ident, #root::sql::Auto::Unset) {
2522 self.#ident = #root::sql::Auto::Set(
2523 #root::__chrono::Utc::now(),
2524 );
2525 }
2526 if let #root::sql::Auto::Set(_v) = &self.#ident {
2527 _columns.push(#column);
2528 _values.push(::core::convert::Into::<#root::core::SqlValue>::into(
2529 ::core::clone::Clone::clone(_v)
2530 ));
2531 }
2532 });
2533 } else {
2534 out.insert_pushes.push(quote! {
2535 if let #root::sql::Auto::Set(_v) = &self.#ident {
2536 _columns.push(#column);
2537 _values.push(::core::convert::Into::<#root::core::SqlValue>::into(
2538 ::core::clone::Clone::clone(_v)
2539 ));
2540 }
2541 });
2542 }
2543 if info.default_uuid_v7 || info.auto_now_add || info.auto_now {
2556 out.rust_filled_idents.push(ident.clone());
2557 }
2558 if info.auto_now {
2559 out.auto_now_idents.push(ident.clone());
2560 }
2561 if info.default_uuid_v7 {
2562 out.uuid_v7_fields
2563 .push((ident.clone(), info.column.clone()));
2564 }
2565 let rust_fill = if info.default_uuid_v7 {
2566 Some(quote!(#root::__uuid::Uuid::now_v7()))
2567 } else if info.auto_now_add || info.auto_now {
2568 Some(quote!(#root::__chrono::Utc::now()))
2569 } else {
2570 None
2571 };
2572 out.bulk_columns_all.push(quote!(#column));
2575 if let Some(fill) = &rust_fill {
2576 let push = quote! {
2577 _row_vals.push(::core::convert::Into::<#root::core::SqlValue>::into(
2578 match &_row.#ident {
2579 #root::sql::Auto::Set(_v) => ::core::clone::Clone::clone(_v),
2580 #root::sql::Auto::Unset => #fill,
2581 }
2582 ));
2583 };
2584 out.bulk_columns_no_auto.push(quote!(#column));
2585 out.bulk_pushes_no_auto.push(push.clone());
2586 out.bulk_pushes_all.push(push);
2587 } else {
2588 out.bulk_pushes_all.push(quote! {
2589 _row_vals.push(::core::convert::Into::<#root::core::SqlValue>::into(
2590 ::core::clone::Clone::clone(&_row.#ident)
2591 ));
2592 });
2593 }
2594 if info.default_uuid_v7 {
2595 out.bulk_fill_in_place.push(quote! {
2596 if matches!(&_row.#ident, #root::sql::Auto::Unset) {
2597 _row.#ident = #root::sql::Auto::Set(#root::__uuid::Uuid::now_v7());
2598 }
2599 });
2600 }
2601 let ident_clone = ident.clone();
2606 if rust_fill.is_none() {
2607 out.bulk_auto_uniformity.push(quote! {
2608 for _r in rows.iter().skip(1) {
2609 if matches!(_r.#ident_clone, #root::sql::Auto::Unset) != _first_unset {
2610 return ::core::result::Result::Err(
2611 #root::sql::ExecError::Sql(
2612 #root::sql::SqlError::BulkAutoMixed
2613 )
2614 );
2615 }
2616 }
2617 });
2618 }
2619 } else {
2620 out.insert_pushes.push(quote! {
2621 _columns.push(#column);
2622 _values.push(::core::convert::Into::<#root::core::SqlValue>::into(
2623 ::core::clone::Clone::clone(&self.#ident)
2624 ));
2625 });
2626 out.bulk_columns_no_auto.push(quote!(#column));
2628 out.bulk_columns_all.push(quote!(#column));
2629 let push_expr = quote! {
2630 _row_vals.push(::core::convert::Into::<#root::core::SqlValue>::into(
2631 ::core::clone::Clone::clone(&_row.#ident)
2632 ));
2633 };
2634 out.bulk_pushes_no_auto.push(push_expr.clone());
2635 out.bulk_pushes_all.push(push_expr);
2636 }
2637 if info.primary_key {
2638 if out.primary_key.is_some() {
2639 return Err(syn::Error::new_spanned(
2640 field,
2641 "only one field may be marked `#[rustango(primary_key)]`",
2642 ));
2643 }
2644 out.primary_key = Some((ident.clone(), info.column.clone()));
2645 if info.auto {
2646 out.pk_is_auto = true;
2647 }
2648 } else if info.auto_now_add {
2649 } else if info.auto_now {
2651 out.update_assignments.push(quote! {
2654 #root::core::Assignment::new(
2655 #column,
2656 {
2657 let _now = #root::__chrono::Utc::now();
2658 self.#ident = #root::sql::Auto::Set(_now);
2659 ::core::convert::Into::<#root::core::SqlValue>::into(_now)
2660 },
2661 )
2662 });
2663 out.upsert_update_columns.push(quote!(#column));
2664 } else {
2665 out.update_assignments.push(quote! {
2666 #root::core::Assignment::new(
2667 #column,
2668 ::core::convert::Into::<#root::core::SqlValue>::into(
2669 ::core::clone::Clone::clone(&self.#ident)
2670 ),
2671 )
2672 });
2673 out.upsert_update_columns.push(quote!(#column));
2674 }
2675 out.column_entries.push(ColumnEntry {
2676 ident: ident.clone(),
2677 value_ty: info.value_ty.clone(),
2678 name: ident_name(ident),
2679 column: info.column.clone(),
2680 field_type_tokens: info.field_type_tokens,
2681 uuid_column: info.uuid_column,
2682 });
2683 }
2684 Ok(out)
2685}
2686
2687fn capped_index_name(name: String) -> String {
2690 const MAX: usize = 63;
2691 if name.len() <= MAX {
2692 return name;
2693 }
2694 let hash = name.bytes().fold(0x811c_9dc5_u32, |h, b| {
2696 (h ^ u32::from(b)).wrapping_mul(0x0100_0193)
2697 });
2698 let mut cut = MAX - 9;
2699 while !name.is_char_boundary(cut) {
2700 cut -= 1;
2701 }
2702 format!("{}_{hash:08x}", &name[..cut])
2703}
2704
2705fn model_impl_tokens(
2706 struct_name: &syn::Ident,
2707 model_name: &str,
2708 table: &str,
2709 display: Option<&str>,
2710 app_label: Option<&str>,
2711 admin: Option<&AdminAttrs>,
2712 default_order: &[(String, bool, proc_macro2::Span)],
2713 field_schemas: &[TokenStream2],
2714 soft_delete_column: Option<&str>,
2715 permissions: bool,
2716 audit_track: Option<&[String]>,
2717 m2m_relations: &[M2MAttr],
2718 indexes: &[IndexAttr],
2719 checks: &[CheckAttr],
2720 excludes: &[ExcludeAttr],
2721 composite_fks: &[CompositeFkAttr],
2722 generic_fks: &[GenericFkAttr],
2723 scope: Option<&str>,
2724 access: Option<&str>,
2725 is_view: bool,
2726 verbose_name: Option<&str>,
2727 verbose_name_plural: Option<&str>,
2728 managed: bool,
2729 base_manager_name: Option<&str>,
2730 order_with_respect_to: Option<&str>,
2731 proxy: bool,
2732 required_db_features: &[String],
2733 required_db_vendor: Option<&str>,
2734 default_related_name: Option<&str>,
2735 db_table_comment: Option<&str>,
2736 get_latest_by: Option<(&str, bool)>,
2737 extra_permissions: &[(String, String)],
2738 default_permissions: &[String],
2739 global_scopes: &[GlobalScopeAttr],
2740 reverse_has_relations: &[ReverseHasAttr],
2741 generic_has_relations: &[GenericHasAttr],
2742) -> TokenStream2 {
2743 let root = rustango_root();
2744 let display_tokens = if let Some(name) = display {
2745 quote!(::core::option::Option::Some(#name))
2746 } else {
2747 quote!(::core::option::Option::None)
2748 };
2749 let app_label_tokens = if let Some(name) = app_label {
2750 quote!(::core::option::Option::Some(#name))
2751 } else {
2752 quote!(::core::option::Option::None)
2753 };
2754 let soft_delete_tokens = if let Some(col) = soft_delete_column {
2755 quote!(::core::option::Option::Some(#col))
2756 } else {
2757 quote!(::core::option::Option::None)
2758 };
2759 let audit_track_tokens = match audit_track {
2760 None => quote!(::core::option::Option::None),
2761 Some(names) => {
2762 let lits = names.iter().map(|n| n.as_str());
2763 quote!(::core::option::Option::Some(&[ #(#lits),* ]))
2764 }
2765 };
2766 let admin_tokens = admin_config_tokens(admin);
2767 let scope_tokens = match scope.map(|s| s.to_ascii_lowercase()).as_deref() {
2771 Some("registry") => quote!(#root::core::ModelScope::Registry),
2772 _ => quote!(#root::core::ModelScope::Tenant),
2773 };
2774 let access_tokens = match access {
2776 Some("admin_only") => {
2777 quote!(::core::option::Option::Some(#root::core::TableAccess::AdminOnly))
2778 }
2779 Some("never") => quote!(::core::option::Option::Some(#root::core::TableAccess::Never)),
2780 Some(_) => quote!(::core::option::Option::Some(#root::core::TableAccess::Open)),
2781 None => quote!(::core::option::Option::None),
2782 };
2783 let verbose_name_tokens = optional_str(verbose_name);
2784 let verbose_name_plural_tokens = optional_str(verbose_name_plural);
2785 let base_manager_name_tokens = optional_str(base_manager_name);
2786 let order_with_respect_to_tokens = optional_str(order_with_respect_to);
2787 let required_db_features_lits: Vec<&str> =
2788 required_db_features.iter().map(String::as_str).collect();
2789 let required_db_vendor_tokens = optional_str(required_db_vendor);
2790 let default_related_name_tokens = optional_str(default_related_name);
2791 let db_table_comment_tokens = optional_str(db_table_comment);
2792 let get_latest_by_tokens = match get_latest_by {
2793 Some((col, desc)) => {
2794 quote!(::core::option::Option::Some((#col, #desc)))
2795 }
2796 None => quote!(::core::option::Option::None),
2797 };
2798 let extra_permission_tokens: Vec<_> = extra_permissions
2799 .iter()
2800 .map(|(c, l)| quote!((#c, #l)))
2801 .collect();
2802 let default_permission_tokens: Vec<_> = default_permissions
2803 .iter()
2804 .map(|action| quote!(#action))
2805 .collect();
2806 let indexes_tokens = indexes.iter().map(|idx| {
2807 let derived_name = idx
2812 .name
2813 .clone()
2814 .unwrap_or_else(|| capped_index_name(format!("{table}_{}_idx", idx.columns.join("_"))));
2815 let name = derived_name.as_str();
2816 let cols: Vec<&str> = idx.columns.iter().map(String::as_str).collect();
2817 let unique = idx.unique;
2818 let method_variant = match idx.method.as_str() {
2822 "gin" => quote!(#root::core::IndexMethod::Gin),
2823 "gist" => quote!(#root::core::IndexMethod::Gist),
2824 "brin" => quote!(#root::core::IndexMethod::Brin),
2825 "spgist" => quote!(#root::core::IndexMethod::SpGist),
2826 "hash" => quote!(#root::core::IndexMethod::Hash),
2827 "bloom" => quote!(#root::core::IndexMethod::Bloom),
2828 _ => quote!(#root::core::IndexMethod::BTree),
2829 };
2830 let where_clause = match &idx.where_clause {
2831 Some(s) => quote!(::core::option::Option::Some(#s)),
2832 None => quote!(::core::option::Option::None),
2833 };
2834 let include_lits: Vec<&str> = idx.include.iter().map(String::as_str).collect();
2835 quote! {{
2836 let mut i = #root::core::IndexSchema::new(#name, &[ #(#cols),* ]);
2837 i.unique = #unique;
2838 i.method = #method_variant;
2839 i.where_clause = #where_clause;
2840 i.include = &[ #(#include_lits),* ];
2841 i
2842 }}
2843 });
2844 let checks_tokens = checks.iter().map(|c| {
2845 let name = c.name.as_str();
2846 let expr = c.expr.as_str();
2847 quote!(#root::core::CheckConstraint::new(#name, #expr))
2848 });
2849 let excludes_tokens = excludes.iter().map(|e| {
2850 let name = e.name.as_str();
2851 let using = e.using.as_str();
2852 let element_tokens = e.elements.iter().map(|(col, op)| {
2853 let col_s = col.as_str();
2854 let op_s = op.as_str();
2855 quote!((#col_s, #op_s))
2856 });
2857 let where_tokens = match e.where_clause.as_deref() {
2858 Some(w) => quote!(::core::option::Option::Some(#w)),
2859 None => quote!(::core::option::Option::None),
2860 };
2861 quote! {{
2862 let mut e = #root::core::ExclusionConstraint::new(
2863 #name,
2864 #using,
2865 &[ #(#element_tokens),* ],
2866 );
2867 e.where_clause = #where_tokens;
2868 e
2869 }}
2870 });
2871 let composite_fk_tokens = composite_fks.iter().map(|rel| {
2872 let name = rel.name.as_str();
2873 let to = rel.to.as_str();
2874 let from_cols: Vec<&str> = rel.from.iter().map(String::as_str).collect();
2875 let on_cols: Vec<&str> = rel.on.iter().map(String::as_str).collect();
2876 quote! {
2877 #root::core::CompositeFkRelation::new(
2878 #name,
2879 #to,
2880 &[ #(#from_cols),* ],
2881 &[ #(#on_cols),* ],
2882 )
2883 }
2884 });
2885 let generic_fk_tokens = generic_fks.iter().map(|rel| {
2886 let name = rel.name.as_str();
2887 let ct_col = rel.ct_column.as_str();
2888 let pk_col = rel.pk_column.as_str();
2889 quote!(#root::core::GenericRelation::new(#name, #ct_col, #pk_col))
2890 });
2891 let default_order_tokens = default_order.iter().map(|(col, desc, _)| {
2894 let col_lit = col.as_str();
2895 quote! { (#col_lit, #desc) }
2896 });
2897
2898 let global_scope_tokens = global_scopes.iter().map(|s| {
2903 let name = s.name.as_str();
2904 let mut apply = s.apply.clone();
2906 if let Some(first) = apply.segments.first_mut() {
2907 if first.ident == "Self" {
2908 first.ident = struct_name.clone();
2909 }
2910 }
2911 quote!(#root::core::GlobalScope::new(#name, #apply))
2912 });
2913
2914 let m2m_tokens = m2m_relations.iter().map(|rel| {
2915 let name = rel.name.as_str();
2916 let to = rel.to.as_str();
2917 let through = rel.through.as_str();
2918 let src = rel.src.as_str();
2919 let dst = rel.dst.as_str();
2920 let auto_create = rel.auto_create;
2921 quote! {{
2922 let mut m = #root::core::M2MRelation::new(#name, #to, #through, #src, #dst);
2923 m.auto_create = #auto_create;
2924 m
2925 }}
2926 });
2927 let reverse_relations_override = if reverse_has_relations.is_empty() {
2933 quote!()
2934 } else {
2935 let entries = reverse_has_relations.iter().map(|rel| {
2936 let name = rel.name.as_str();
2937 let child = &rel.child;
2938 let child_fk_column = rel.child_fk_column.as_str();
2939 let self_pk_column = rel.self_pk_column();
2940 quote! {
2941 #root::core::ReverseRelation::new(
2942 #name,
2943 <#child as #root::core::Model>::SCHEMA,
2944 #child_fk_column,
2945 #self_pk_column,
2946 )
2947 }
2948 });
2949 quote! {
2950 fn reverse_relations() -> &'static [#root::core::ReverseRelation] {
2951 const RELS: &[#root::core::ReverseRelation] = &[ #(#entries),* ];
2952 RELS
2953 }
2954 }
2955 };
2956 let generic_reverse_relations_override = if generic_has_relations.is_empty() {
2959 quote!()
2960 } else {
2961 let entries = generic_has_relations.iter().map(|rel| {
2962 let name = rel.name.as_str();
2963 let child = &rel.child;
2964 let ct_column = rel.ct_column.as_str();
2965 let pk_column = rel.pk_column.as_str();
2966 let self_pk_column = rel.self_pk_column();
2967 quote! {
2968 #root::core::GenericReverseRelation::new(
2969 #name,
2970 <#child as #root::core::Model>::SCHEMA,
2971 #ct_column,
2972 #pk_column,
2973 #self_pk_column,
2974 )
2975 }
2976 });
2977 quote! {
2978 fn generic_reverse_relations() -> &'static [#root::core::GenericReverseRelation] {
2979 const RELS: &[#root::core::GenericReverseRelation] = &[ #(#entries),* ];
2980 RELS
2981 }
2982 }
2983 };
2984 let fields_slice = const_slice(
2985 "e!(#root::core::FieldSchema),
2986 field_schemas.iter().cloned(),
2987 );
2988 let m2m_slice = const_slice("e!(#root::core::M2MRelation), m2m_tokens);
2989 let indexes_slice = const_slice("e!(#root::core::IndexSchema), indexes_tokens);
2990 let checks_slice = const_slice("e!(#root::core::CheckConstraint), checks_tokens);
2991 let excludes_slice = const_slice("e!(#root::core::ExclusionConstraint), excludes_tokens);
2992 let composite_fk_slice = const_slice(
2993 "e!(#root::core::CompositeFkRelation),
2994 composite_fk_tokens,
2995 );
2996 let generic_fk_slice = const_slice("e!(#root::core::GenericRelation), generic_fk_tokens);
2997 let audited_update_override = if audit_track.is_some() {
2998 quote! {
2999 fn __rustango_audited_update() -> ::core::option::Option<#root::audit::AuditedUpdate> {
3000 ::core::option::Option::Some(Self::__rustango_update_audited)
3001 }
3002 fn __rustango_audited_delete() -> ::core::option::Option<#root::audit::AuditedDelete> {
3003 ::core::option::Option::Some(Self::__rustango_delete_audited)
3004 }
3005 fn __rustango_audited_create() -> ::core::option::Option<#root::audit::AuditedCreate> {
3006 ::core::option::Option::Some(Self::__rustango_create_audited)
3007 }
3008 fn __rustango_audited_update_record(
3009 ) -> ::core::option::Option<#root::audit::AuditedUpdateRecord> {
3010 ::core::option::Option::Some(Self::__rustango_update_record_audited)
3011 }
3012 fn __rustango_audited_cascade() -> ::core::option::Option<#root::audit::AuditedCascade> {
3013 ::core::option::Option::Some(Self::__rustango_cascade_audited)
3014 }
3015 }
3016 } else {
3017 quote!()
3018 };
3019 let global_scope_slice = const_slice("e!(#root::core::GlobalScope), global_scope_tokens);
3020 quote! {
3021 impl #root::core::Model for #struct_name {
3022 const SCHEMA: &'static #root::core::ModelSchema = &{
3023 #root::core::ModelSchema::from_parts(#root::core::ModelSchemaParts {
3024 name: #model_name,
3025 table: #table,
3026 fields: #fields_slice,
3027 display: #display_tokens,
3028 app_label: #app_label_tokens,
3029 admin: #admin_tokens,
3030 soft_delete_column: #soft_delete_tokens,
3031 permissions: #permissions,
3032 audit_track: #audit_track_tokens,
3033 m2m: #m2m_slice,
3034 indexes: #indexes_slice,
3035 check_constraints: #checks_slice,
3036 exclusion_constraints: #excludes_slice,
3037 composite_relations: #composite_fk_slice,
3038 generic_relations: #generic_fk_slice,
3039 scope: #scope_tokens,
3040 default_order: &[ #(#default_order_tokens),* ],
3041 is_view: #is_view,
3042 verbose_name: #verbose_name_tokens,
3043 verbose_name_plural: #verbose_name_plural_tokens,
3044 managed: #managed,
3045 base_manager_name: #base_manager_name_tokens,
3046 order_with_respect_to: #order_with_respect_to_tokens,
3047 proxy: #proxy,
3048 required_db_features: &[ #(#required_db_features_lits),* ],
3049 required_db_vendor: #required_db_vendor_tokens,
3050 default_related_name: #default_related_name_tokens,
3051 db_table_comment: #db_table_comment_tokens,
3052 get_latest_by: #get_latest_by_tokens,
3053 extra_permissions: &[ #(#extra_permission_tokens),* ],
3054 default_permissions: &[ #(#default_permission_tokens),* ],
3055 global_scopes: #global_scope_slice,
3056 access: #access_tokens,
3057 })
3058 };
3059
3060 #reverse_relations_override
3061 #generic_reverse_relations_override
3062 #audited_update_override
3063 }
3064 }
3065}
3066
3067fn admin_config_tokens(admin: Option<&AdminAttrs>) -> TokenStream2 {
3071 let root = rustango_root();
3072 let Some(admin) = admin else {
3073 return quote!(::core::option::Option::None);
3074 };
3075
3076 let list_display = admin
3077 .list_display
3078 .as_ref()
3079 .map(|(v, _)| v.as_slice())
3080 .unwrap_or(&[]);
3081 let list_display_lits = list_display.iter().map(|s| s.as_str());
3082
3083 let search_fields = admin
3084 .search_fields
3085 .as_ref()
3086 .map(|(v, _)| v.as_slice())
3087 .unwrap_or(&[]);
3088 let search_fields_lits = search_fields.iter().map(|s| s.as_str());
3089
3090 let readonly_fields = admin
3091 .readonly_fields
3092 .as_ref()
3093 .map(|(v, _)| v.as_slice())
3094 .unwrap_or(&[]);
3095 let readonly_fields_lits = readonly_fields.iter().map(|s| s.as_str());
3096
3097 let list_filter = admin
3098 .list_filter
3099 .as_ref()
3100 .map(|(v, _)| v.as_slice())
3101 .unwrap_or(&[]);
3102 let list_filter_lits = list_filter.iter().map(|s| s.as_str());
3103
3104 let actions = admin
3105 .actions
3106 .as_ref()
3107 .map(|(v, _)| v.as_slice())
3108 .unwrap_or(&[]);
3109 let actions_lits = actions.iter().map(|s| s.as_str());
3110
3111 let fieldsets = admin
3112 .fieldsets
3113 .as_ref()
3114 .map(|(v, _)| v.as_slice())
3115 .unwrap_or(&[]);
3116 let fieldset_tokens = const_slice(
3117 "e!(#root::core::Fieldset),
3118 fieldsets.iter().map(|(title, fields)| {
3119 let title = title.as_str();
3120 let field_lits = fields.iter().map(String::as_str);
3121 quote!(#root::core::Fieldset::new(#title, &[ #( #field_lits ),* ]))
3122 }),
3123 );
3124
3125 let list_display_links = admin
3126 .list_display_links
3127 .as_ref()
3128 .map(|(v, _)| v.as_slice())
3129 .unwrap_or(&[]);
3130 let list_display_links_lits = list_display_links.iter().map(|s| s.as_str());
3131
3132 let search_help_text = admin.search_help_text.as_deref().unwrap_or("");
3133 let actions_on_top = admin.actions_on_top.unwrap_or(true);
3134 let actions_on_bottom = admin.actions_on_bottom.unwrap_or(false);
3135 let date_hierarchy = admin.date_hierarchy.as_deref().unwrap_or("");
3136
3137 let prepopulated = admin
3138 .prepopulated_fields
3139 .as_ref()
3140 .map(|(v, _)| v.as_slice())
3141 .unwrap_or(&[]);
3142 let prepopulated_tokens = const_slice(
3143 "e!(#root::core::PrepopulatedField),
3144 prepopulated.iter().map(|(target, sources)| {
3145 let target = target.as_str();
3146 let source_lits = sources.iter().map(String::as_str);
3147 quote!(#root::core::PrepopulatedField::new(#target, &[ #( #source_lits ),* ]))
3148 }),
3149 );
3150
3151 let raw_id_fields = admin
3152 .raw_id_fields
3153 .as_ref()
3154 .map(|(v, _)| v.as_slice())
3155 .unwrap_or(&[]);
3156 let raw_id_fields_lits = raw_id_fields.iter().map(|s| s.as_str());
3157
3158 let autocomplete_fields = admin
3159 .autocomplete_fields
3160 .as_ref()
3161 .map(|(v, _)| v.as_slice())
3162 .unwrap_or(&[]);
3163 let autocomplete_fields_lits = autocomplete_fields.iter().map(|s| s.as_str());
3164
3165 let list_select_related_tokens = match admin.list_select_related.as_deref() {
3167 None | Some("all") => quote!(#root::core::ListSelectRelated::All),
3168 Some("none") => quote!(#root::core::ListSelectRelated::None),
3169 Some(raw) => {
3170 let names: Vec<&str> = raw
3171 .split(',')
3172 .map(str::trim)
3173 .filter(|s| !s.is_empty())
3174 .collect();
3175 quote!(#root::core::ListSelectRelated::Only(&[ #( #names ),* ]))
3176 }
3177 };
3178
3179 let formfield_pairs: Vec<(&str, &str)> = admin
3182 .formfield_overrides
3183 .as_ref()
3184 .map(|(v, _)| v.iter().map(|(f, w)| (f.as_str(), w.as_str())).collect())
3185 .unwrap_or_default();
3186 let formfield_tokens = formfield_pairs.iter().map(|(field, widget)| {
3187 let field = *field;
3188 let widget = *widget;
3189 quote!((#field, #widget))
3190 });
3191
3192 let list_per_page = admin.list_per_page.unwrap_or(0);
3193
3194 let ordering_pairs = admin
3195 .ordering
3196 .as_ref()
3197 .map(|(v, _)| v.as_slice())
3198 .unwrap_or(&[]);
3199 let ordering_tokens = ordering_pairs.iter().map(|(name, desc)| {
3200 let name = name.as_str();
3201 let desc = *desc;
3202 quote!((#name, #desc))
3203 });
3204
3205 quote! {
3206 ::core::option::Option::Some({
3207 const ADMIN: &#root::core::AdminConfig = &{
3208 #root::core::AdminConfig::from_parts(#root::core::AdminConfigParts {
3209 list_display: &[ #( #list_display_lits ),* ],
3210 search_fields: &[ #( #search_fields_lits ),* ],
3211 list_per_page: #list_per_page,
3212 ordering: &[ #( #ordering_tokens ),* ],
3213 readonly_fields: &[ #( #readonly_fields_lits ),* ],
3214 list_filter: &[ #( #list_filter_lits ),* ],
3215 actions: &[ #( #actions_lits ),* ],
3216 fieldsets: #fieldset_tokens,
3217 list_display_links: &[ #( #list_display_links_lits ),* ],
3218 search_help_text: #search_help_text,
3219 actions_on_top: #actions_on_top,
3220 actions_on_bottom: #actions_on_bottom,
3221 date_hierarchy: #date_hierarchy,
3222 prepopulated_fields: #prepopulated_tokens,
3223 raw_id_fields: &[ #( #raw_id_fields_lits ),* ],
3224 autocomplete_fields: &[ #( #autocomplete_fields_lits ),* ],
3225 list_select_related: #list_select_related_tokens,
3226 formfield_overrides: &[ #( #formfield_tokens ),* ],
3227 })
3228 };
3229 ADMIN
3230 })
3231 }
3232}
3233
3234fn inherent_impl_tokens(
3235 struct_name: &syn::Ident,
3236 fields: &CollectedFields,
3237 primary_key: Option<&(syn::Ident, String)>,
3238 column_consts: &TokenStream2,
3239 audited_fields: Option<&[&ColumnEntry]>,
3240 upsert_unique: Option<&[String]>,
3241 manager_fns: &[syn::Ident],
3242) -> TokenStream2 {
3243 let root = rustango_root();
3244
3245 let pool_insert_method = if audited_fields.is_some() && !fields.has_auto {
3263 quote!()
3272 } else if audited_fields.is_some() && fields.has_auto {
3273 quote!()
3276 } else if fields.has_auto {
3277 let pushes = &fields.insert_pushes;
3278 let filled = &fields.rust_filled_idents;
3280 let was_unset: Vec<syn::Ident> = filled
3281 .iter()
3282 .map(|i| quote::format_ident!("__{}_was_unset", ident_name(i)))
3283 .collect();
3284 let insert_or_ignore_call = quote! {
3285 #( let #was_unset = matches!(&self.#filled, #root::sql::Auto::Unset); )*
3286 let mut _columns: ::std::vec::Vec<&'static str> =
3287 ::std::vec::Vec::new();
3288 let mut _values: ::std::vec::Vec<#root::core::SqlValue> =
3289 ::std::vec::Vec::new();
3290 #( #pushes )*
3291 let _query = #root::core::InsertQuery::new(
3292 <Self as #root::core::Model>::SCHEMA,
3293 _columns,
3294 _values,
3295 );
3296 let _result = #root::sql::insert_or_ignore(pool, &_query).await;
3297 if !matches!(_result, ::core::result::Result::Ok(true)) {
3298 #( if #was_unset { self.#filled = #root::sql::Auto::Unset; } )*
3299 }
3300 _result
3301 };
3302 if fields.returning_cols.is_empty() {
3309 quote! {
3310
3311
3312 pub async fn insert_or_ignore(
3320 &mut self,
3321 pool: &#root::sql::Pool,
3322 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
3323 #insert_or_ignore_call
3324 }
3325 }
3326 } else {
3327 quote! {
3328
3329
3330 pub async fn insert_or_ignore(
3347 &mut self,
3348 pool: &#root::sql::Pool,
3349 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
3350 #insert_or_ignore_call
3351 }
3352 }
3353 }
3354 } else {
3355 let insert_columns = &fields.insert_columns;
3356 let insert_values = &fields.insert_values;
3357 quote! {
3358
3359
3360 pub async fn insert_or_ignore(
3380 &self,
3381 pool: &#root::sql::Pool,
3382 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
3383 let _query = #root::core::InsertQuery::new(
3384 <Self as #root::core::Model>::SCHEMA,
3385 ::std::vec![ #( #insert_columns ),* ],
3386 ::std::vec![ #( #insert_values ),* ],
3387 );
3388 #root::sql::insert_or_ignore(pool, &_query).await
3389 }
3390 }
3391 };
3392
3393 let audit_pair_tokens: Vec<TokenStream2> = audited_fields
3396 .map(|tracked| {
3397 tracked
3398 .iter()
3399 .map(|c| {
3400 let column_lit = c.column.as_str();
3401 let ident = &c.ident;
3402 quote! {
3403 (
3404 #column_lit,
3405 #root::core::__json_value(&self.#ident),
3406 )
3407 }
3408 })
3409 .collect()
3410 })
3411 .unwrap_or_default();
3412 let audit_pk_to_string = if let Some((pk_ident, _)) = primary_key {
3413 if fields.pk_is_auto {
3414 quote!(self.#pk_ident.get().map(|v| ::std::format!("{}", v)).unwrap_or_default())
3415 } else {
3416 quote!(::std::format!("{}", &self.#pk_ident))
3417 }
3418 } else {
3419 quote!(::std::string::String::new())
3420 };
3421 let delete_audited_body = if primary_key.is_some() {
3424 quote! {
3425 ::std::boxed::Box::pin(#root::audit::delete_many_with_audit::<Self>(
3426 pool,
3427 query,
3428 |_r: &Self| _r.__rustango_audit_entry(#root::audit::AuditOp::Delete),
3429 ))
3430 }
3431 } else {
3432 quote! {
3433 let _ = (pool, query);
3434 ::std::boxed::Box::pin(async {
3435 ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
3436 table: <Self as #root::core::Model>::SCHEMA.table,
3437 })
3438 })
3439 }
3440 };
3441 let cascade_audited_body = if primary_key.is_some() {
3442 quote! {
3443 ::std::boxed::Box::pin(#root::audit::cascade_rows::<Self>(
3444 tx,
3445 <Self as #root::core::Model>::SCHEMA,
3446 rows,
3447 |_r: &Self| _r.__rustango_audit_entry(#root::audit::AuditOp::Delete),
3448 ))
3449 }
3450 } else {
3451 quote! {
3452 let _ = (tx, rows);
3453 ::std::boxed::Box::pin(async {
3454 ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
3455 table: <Self as #root::core::Model>::SCHEMA.table,
3456 })
3457 })
3458 }
3459 };
3460 let update_audited_body = if primary_key.is_some() {
3461 quote! {
3462 ::std::boxed::Box::pin(#root::audit::update_many_with_audit::<Self>(
3463 pool,
3464 query,
3465 move |_r: &Self| _r.__rustango_audit_entry(op),
3466 ))
3467 }
3468 } else {
3469 quote! {
3470 let _ = (pool, query, op);
3471 ::std::boxed::Box::pin(async {
3472 ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
3473 table: <Self as #root::core::Model>::SCHEMA.table,
3474 })
3475 })
3476 }
3477 };
3478 let audit_entry_method = if audited_fields.is_some() {
3479 let pairs = audit_pair_tokens.iter();
3480 let pk_str = audit_pk_to_string.clone();
3481 quote! {
3482 #[doc(hidden)]
3484 pub fn __rustango_audit_pairs(
3485 &self,
3486 ) -> ::std::vec::Vec<(&'static str, #root::__serde_json::Value)> {
3487 ::std::vec![ #( #pairs ),* ]
3488 }
3489
3490 #[doc(hidden)]
3492 pub fn __rustango_audit_pk(&self) -> ::std::string::String {
3493 #pk_str
3494 }
3495
3496 #[doc(hidden)]
3498 pub fn __rustango_audit_entry(
3499 &self,
3500 operation: #root::audit::AuditOp,
3501 ) -> #root::audit::PendingEntry {
3502 #root::audit::PendingEntry {
3503 entity_table: <Self as #root::core::Model>::SCHEMA.table,
3504 entity_pk: self.__rustango_audit_pk(),
3505 operation,
3506 source: #root::audit::current_source(),
3507 changes: #root::audit::snapshot_changes(&self.__rustango_audit_pairs()),
3508 }
3509 }
3510
3511 #[doc(hidden)]
3513 pub fn __rustango_update_audited<'a>(
3514 pool: &'a #root::sql::Pool,
3515 query: &'a #root::core::UpdateQuery,
3516 op: #root::audit::AuditOp,
3517 ) -> ::std::pin::Pin<::std::boxed::Box<
3518 dyn ::core::future::Future<
3519 Output = ::core::result::Result<u64, #root::sql::ExecError>,
3520 > + ::core::marker::Send + 'a,
3521 >> {
3522 #update_audited_body
3523 }
3524
3525 #[doc(hidden)]
3527 pub fn __rustango_create_audited<'a, 't>(
3528 tx: &'a mut #root::sql::PoolTx<'t>,
3529 pk: #root::core::SqlValue,
3530 ) -> ::std::pin::Pin<::std::boxed::Box<
3531 dyn ::core::future::Future<
3532 Output = ::core::result::Result<(), #root::sql::ExecError>,
3533 > + ::core::marker::Send + 'a,
3534 >> {
3535 ::std::boxed::Box::pin(#root::audit::record_create::<Self>(
3536 tx,
3537 <Self as #root::core::Model>::SCHEMA,
3538 pk,
3539 |_r: &Self| _r.__rustango_audit_entry(#root::audit::AuditOp::Create),
3540 ))
3541 }
3542
3543 #[doc(hidden)]
3545 pub fn __rustango_update_record_audited<'a, 't>(
3546 tx: &'a mut #root::sql::PoolTx<'t>,
3547 pk: #root::core::SqlValue,
3548 ) -> ::std::pin::Pin<::std::boxed::Box<
3549 dyn ::core::future::Future<
3550 Output = ::core::result::Result<(), #root::sql::ExecError>,
3551 > + ::core::marker::Send + 'a,
3552 >> {
3553 ::std::boxed::Box::pin(#root::audit::record_update::<Self>(
3554 tx,
3555 <Self as #root::core::Model>::SCHEMA,
3556 pk,
3557 |_r: &Self| _r.__rustango_audit_entry(#root::audit::AuditOp::Update),
3558 ))
3559 }
3560
3561 #[doc(hidden)]
3563 pub fn __rustango_cascade_audited<'a, 't>(
3564 tx: &'a mut #root::sql::PoolTx<'t>,
3565 rows: #root::core::WhereExpr,
3566 ) -> ::std::pin::Pin<::std::boxed::Box<
3567 dyn ::core::future::Future<
3568 Output = ::core::result::Result<
3569 ::std::vec::Vec<(#root::core::SqlValue, #root::audit::PendingEntry)>,
3570 #root::sql::ExecError,
3571 >,
3572 > + ::core::marker::Send + 'a,
3573 >> {
3574 #cascade_audited_body
3575 }
3576
3577 #[doc(hidden)]
3579 pub fn __rustango_delete_audited<'a>(
3580 pool: &'a #root::sql::Pool,
3581 query: &'a #root::core::DeleteQuery,
3582 ) -> ::std::pin::Pin<::std::boxed::Box<
3583 dyn ::core::future::Future<
3584 Output = ::core::result::Result<u64, #root::sql::ExecError>,
3585 > + ::core::marker::Send + 'a,
3586 >> {
3587 #delete_audited_body
3588 }
3589 }
3590 } else {
3591 quote!()
3592 };
3593 let make_entry = |op_path: TokenStream2, written: Option<TokenStream2>| -> TokenStream2 {
3596 if let (Some(v), Some(col)) = (written, fields.soft_delete_column.as_deref()) {
3597 quote! {
3598 let mut _audit_entry = self.__rustango_audit_entry(#op_path);
3599 _audit_entry.set_tracked(#col, #v);
3600 }
3601 } else {
3602 quote!(let _audit_entry = self.__rustango_audit_entry(#op_path);)
3603 }
3604 };
3605 let soft_deleted_json = quote!(#root::__serde_json::to_value(&_now)
3606 .unwrap_or(#root::__serde_json::Value::Null));
3607 let restored_json = quote!(#root::__serde_json::Value::Null);
3608 let make_pool_update = |op_path: TokenStream2, written: TokenStream2| -> TokenStream2 {
3611 if audited_fields.is_some() {
3612 let entry = make_entry(op_path, Some(written));
3613 quote! {
3614 #entry
3615 #root::audit::save_one_with_audit(pool, &_query, &_audit_entry).await
3616 }
3617 } else {
3618 quote!(#root::sql::update_pool(pool, &_query).await)
3619 }
3620 };
3621 let pool_softdelete_run =
3622 make_pool_update(quote!(#root::audit::AuditOp::SoftDelete), soft_deleted_json);
3623 let pool_restore_run = make_pool_update(quote!(#root::audit::AuditOp::Restore), restored_json);
3624
3625 let save_ret = quote!(::core::result::Result<u64, #root::sql::ExecError>);
3629 let save_keep = quote!(matches!(_saved, ::core::result::Result::Ok(n) if n > 0));
3630 let save_partial_methods = |pk_ident: &syn::Ident,
3631 pk_column_lit: &str,
3632 run: TokenStream2|
3633 -> TokenStream2 {
3634 let assignments = &fields.update_assignments;
3635 let assignment_cols = &fields.upsert_update_columns;
3637 let write = restore_auto_now_unless(
3638 &fields.auto_now_idents,
3639 &save_ret,
3640 &save_keep,
3641 "e! {
3642 let mut _filtered: ::std::vec::Vec<#root::core::Assignment> =
3645 ::std::vec::Vec::with_capacity(_wanted_cols.len());
3646 #(
3647 if _wanted_cols.contains(#assignment_cols) {
3648 _filtered.push(#assignments);
3649 }
3650 )*
3651 if _filtered.is_empty() {
3652 #root::__tracing::warn!(
3657 target: "rustango::save_partial",
3658 model = _schema.name,
3659 "save_partial: every named field maps to a non-assignable column — no-op"
3660 );
3661 return ::core::result::Result::Ok(0);
3662 }
3663 let _query = #root::core::UpdateQuery::new(
3664 _schema,
3665 _filtered,
3666 #root::core::WhereExpr::Predicate(#root::core::Filter::new(
3667 #pk_column_lit,
3668 #root::core::Op::Eq,
3669 ::core::convert::Into::<#root::core::SqlValue>::into(
3670 ::core::clone::Clone::clone(&self.#pk_ident)
3671 ),
3672 )),
3673 );
3674 #run
3675 },
3676 );
3677 quote! {
3678 pub async fn save_partial(
3706 &mut self,
3707 fields: &[&str],
3708 pool: &#root::sql::Pool,
3709 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
3710 if fields.is_empty() {
3711 #root::__tracing::warn!(
3712 target: "rustango::save_partial",
3713 model = <Self as #root::core::Model>::SCHEMA.name,
3714 "save_partial called with empty field list — no-op"
3715 );
3716 return ::core::result::Result::Ok(0);
3717 }
3718 let _schema = <Self as #root::core::Model>::SCHEMA;
3719 let mut _wanted_cols: ::std::collections::HashSet<&'static str> =
3721 ::std::collections::HashSet::with_capacity(fields.len());
3722 for f in fields {
3723 match _schema.field(f) {
3724 ::core::option::Option::Some(fs) => {
3725 _wanted_cols.insert(fs.column);
3726 }
3727 ::core::option::Option::None => {
3728 return ::core::result::Result::Err(
3729 #root::sql::ExecError::Query(
3730 #root::core::QueryError::UnknownField {
3731 model: _schema.name,
3732 field: (*f).to_owned(),
3733 }
3734 )
3735 );
3736 }
3737 }
3738 }
3739 #write
3740 }
3741
3742 pub async fn save_partial_typed<
3762 L: #root::core::TypedFieldList<Self>,
3763 >(
3764 &mut self,
3765 fields: L,
3766 pool: &#root::sql::Pool,
3767 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
3768 let _names = fields.rust_field_names();
3769 let _refs: ::std::vec::Vec<&str> =
3770 _names.iter().copied().collect();
3771 self.save_partial(&_refs, pool).await
3772 }
3773 }
3774 };
3775 let pool_save_method = if let Some((pk_ident, pk_col)) = primary_key {
3776 let pk_column_lit = pk_col.as_str();
3777 if audited_fields.is_some() {
3778 quote!()
3779 } else {
3780 let partial = save_partial_methods(
3781 pk_ident,
3782 pk_column_lit,
3783 quote!(#root::sql::update_pool(pool, &_query).await),
3784 );
3785 quote! {
3786 pub async fn save(
3794 &mut self,
3795 pool: &#root::sql::Pool,
3796 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
3797 self.__rustango_save_exec(pool).await
3798 }
3799
3800 #partial
3801 }
3802 }
3803 } else {
3804 quote!()
3805 };
3806
3807 let pool_insert_method = if audited_fields.is_some() {
3814 if let Some(_) = primary_key {
3815 let returning_cols: Vec<proc_macro2::TokenStream> = if !fields.returning_cols.is_empty()
3817 {
3818 fields.returning_cols.clone()
3819 } else {
3820 primary_key
3827 .map(|(_, col)| {
3828 let lit = col.as_str();
3829 vec![quote!(#lit)]
3830 })
3831 .unwrap_or_default()
3832 };
3833 let assigned: Vec<syn::Ident> = fields
3835 .auto_field_idents
3836 .iter()
3837 .map(|(ident, _, _)| ident.clone())
3838 .collect();
3839 let body = restore_auto_now_unless(
3840 &assigned,
3841 "e!(::core::result::Result<(), #root::sql::ExecError>),
3842 "e!(_saved.is_ok()),
3843 "e! {
3844 let mut _query = self.__rustango_insert_query();
3845 if _query.returning.is_empty() {
3847 _query.returning = ::std::vec![ #( #returning_cols ),* ];
3848 }
3849 #root::audit::insert_one_with_audit(pool, &_query, self, |_m: &Self, _op| {
3850 _m.__rustango_audit_entry(_op)
3851 })
3852 .await
3853 },
3854 );
3855 quote! {
3856 pub async fn insert(
3864 &mut self,
3865 pool: &#root::sql::Pool,
3866 ) -> ::core::result::Result<(), #root::sql::ExecError> {
3867 #body
3868 }
3869 }
3870 } else {
3871 quote!()
3872 }
3873 } else {
3874 pool_insert_method
3876 };
3877
3878 let pool_save_method = if let Some(tracked) = audited_fields {
3899 if let Some((pk_ident, pk_col)) = primary_key {
3900 let pk_column_lit = pk_col.as_str();
3901 let partial = save_partial_methods(
3902 pk_ident,
3903 pk_column_lit,
3904 quote! {
3905 self.__rustango_save_with_diff(
3906 pool,
3907 &_query,
3908 ::core::option::Option::Some(&_wanted_cols),
3909 )
3910 .await
3911 },
3912 );
3913 let mk_before_pairs =
3918 |getter: &dyn Fn(&ColumnEntry) -> TokenStream2| -> Vec<TokenStream2> {
3919 tracked
3920 .iter()
3921 .map(|c| {
3922 let getter = getter(c);
3923 let column_lit = c.column.as_str();
3924 let value_ty = &c.value_ty;
3925 quote! {
3926 (
3927 #column_lit,
3928 match #getter::<#value_ty>(
3929 _audit_before_row, #column_lit,
3930 ) {
3931 ::core::result::Result::Ok(v) => {
3932 #root::core::__json_value(&v)
3933 }
3934 ::core::result::Result::Err(_) => #root::__serde_json::Value::Null,
3935 },
3936 )
3937 }
3938 })
3939 .collect()
3940 };
3941 let before_pairs_pg = mk_before_pairs(&|_| quote!(#root::sql::try_get_returning));
3942 let before_pairs_my = mk_before_pairs(&|c| {
3943 if c.uuid_column {
3944 quote!(#root::sql::try_get_flat_my)
3945 } else {
3946 quote!(#root::sql::try_get_returning_my)
3947 }
3948 });
3949 let before_pairs_sqlite =
3950 mk_before_pairs(&|_| quote!(#root::sql::try_get_returning_sqlite));
3951 let tracked_cols: Vec<&str> = tracked.iter().map(|c| c.column.as_str()).collect();
3952 let pk_value_for_bind = if fields.pk_is_auto {
3953 quote!(self.#pk_ident.get().copied().unwrap_or_default())
3954 } else {
3955 quote!(::core::clone::Clone::clone(&self.#pk_ident))
3956 };
3957 let assignments = &fields.update_assignments;
3958 let unset_dispatch = if fields.has_auto {
3959 quote! {
3960 if matches!(self.#pk_ident, #root::sql::Auto::Unset) {
3961 return self.insert(pool).await.map(|()| 1u64);
3962 }
3963 }
3964 } else {
3965 quote!()
3966 };
3967 let write = restore_auto_now_unless(
3968 &fields.auto_now_idents,
3969 &save_ret,
3970 &save_keep,
3971 "e! {
3972 let _query = #root::core::UpdateQuery::new(
3973 <Self as #root::core::Model>::SCHEMA,
3974 ::std::vec![ #( #assignments ),* ],
3975 #root::core::WhereExpr::Predicate(#root::core::Filter::new(
3976 #pk_column_lit,
3977 #root::core::Op::Eq,
3978 ::core::convert::Into::<#root::core::SqlValue>::into(
3979 ::core::clone::Clone::clone(&self.#pk_ident)
3980 ),
3981 )),
3982 );
3983 self.__rustango_save_with_diff(pool, &_query, ::core::option::Option::None)
3984 .await
3985 .and_then(#root::sql::__macro_internals::row_saved)
3986 },
3987 );
3988 quote! {
3989 pub async fn save(
4001 &mut self,
4002 pool: &#root::sql::Pool,
4003 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4004 #unset_dispatch
4005 #write
4006 }
4007
4008 #[doc(hidden)]
4010 async fn __rustango_save_with_diff(
4011 &self,
4012 pool: &#root::sql::Pool,
4013 query: &#root::core::UpdateQuery,
4014 only: ::core::option::Option<&::std::collections::HashSet<&'static str>>,
4015 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4016 let mut _after_pairs = self.__rustango_audit_pairs();
4017 if let ::core::option::Option::Some(only) = only {
4018 _after_pairs.retain(|(col, _)| only.contains(col));
4019 }
4020 let _before_query = #root::audit::before_image_query(
4021 <Self as #root::core::Model>::SCHEMA,
4022 #pk_column_lit,
4023 ::core::convert::Into::<#root::core::SqlValue>::into(
4024 #pk_value_for_bind,
4025 ),
4026 &[ #( #tracked_cols ),* ],
4027 );
4028 #root::audit::save_one_with_diff(
4029 pool,
4030 query,
4031 &_before_query,
4032 <Self as #root::core::Model>::SCHEMA.table,
4033 self.__rustango_audit_pk(),
4034 _after_pairs,
4035 |_audit_before_row| ::std::vec![ #( #before_pairs_pg ),* ],
4036 |_audit_before_row| ::std::vec![ #( #before_pairs_my ),* ],
4037 |_audit_before_row| ::std::vec![ #( #before_pairs_sqlite ),* ],
4038 ).await
4039 }
4040
4041 #partial
4042 }
4043 } else {
4044 quote!()
4045 }
4046 } else {
4047 pool_save_method
4048 };
4049
4050 let pool_delete_method = {
4057 let pk_column_lit = primary_key.map(|(_, col)| col.as_str()).unwrap_or("id");
4058 let pk_ident_for_pool = primary_key.map(|(ident, _)| ident);
4059 if let Some(pk_ident) = pk_ident_for_pool {
4060 if audited_fields.is_some() {
4061 quote! {
4062 pub async fn delete(
4069 &self,
4070 pool: &#root::sql::Pool,
4071 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4072 let _query = #root::core::DeleteQuery::by_pk(
4073 <Self as #root::core::Model>::SCHEMA,
4074 #pk_column_lit,
4075 ::core::convert::Into::<#root::core::SqlValue>::into(
4076 ::core::clone::Clone::clone(&self.#pk_ident)
4077 ),
4078 );
4079 let _audit_entry =
4080 self.__rustango_audit_entry(#root::audit::AuditOp::Delete);
4081 #root::audit::delete_one_with_audit(
4082 pool, &_query, &_audit_entry,
4083 ).await
4084 }
4085 }
4086 } else {
4087 quote! {
4088 pub async fn delete(
4096 &self,
4097 pool: &#root::sql::Pool,
4098 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4099 self.__rustango_delete_exec(pool).await
4100 }
4101 }
4102 }
4103 } else {
4104 quote!()
4105 }
4106 };
4107
4108 let (bulk_delete_run, destroy_run, bulk_update_run, truncate_run, truncate_cascade_run) =
4111 if audited_fields.is_some() {
4112 (
4113 quote!(#root::audit::delete_many_with_audit::<Self>(pool, &_query, |_r: &Self| {
4114 _r.__rustango_audit_entry(#root::audit::AuditOp::Delete)
4115 })
4116 .await),
4117 quote!(#root::audit::delete_each_with_audit::<Self>(pool, &_queries, |_r: &Self| {
4118 _r.__rustango_audit_entry(#root::audit::AuditOp::Delete)
4119 })
4120 .await),
4121 quote!(#root::audit::update_many_with_audit::<Self>(pool, &_query, |_r: &Self| {
4122 _r.__rustango_audit_entry(#root::audit::AuditOp::Update)
4123 })
4124 .await),
4125 quote!(#root::audit::truncate_with_audit(
4126 pool,
4127 <Self as #root::core::Model>::SCHEMA.table,
4128 #root::sql::TruncateMode::Plain,
4129 )
4130 .await),
4131 quote!(#root::audit::truncate_with_audit(
4132 pool,
4133 <Self as #root::core::Model>::SCHEMA.table,
4134 #root::sql::TruncateMode::Cascade,
4135 )
4136 .await
4137 .map(drop)),
4138 )
4139 } else {
4140 (
4141 quote!(#root::sql::delete_pool(pool, &_query).await),
4142 quote!(#root::sql::__rustango_delete_all_or_none(pool, _queries).await),
4143 quote!(#root::sql::update_pool(pool, &_query).await),
4144 quote!(#root::sql::truncate(pool, <Self as #root::core::Model>::SCHEMA).await),
4145 quote!(#root::sql::truncate_cascade(pool, <Self as #root::core::Model>::SCHEMA).await),
4146 )
4147 };
4148
4149 let refresh_replicate_methods = if let Some((pk_ident, _)) = primary_key {
4162 let other_field_clones: Vec<TokenStream2> = fields
4163 .column_entries
4164 .iter()
4165 .filter(|c| &c.ident != pk_ident)
4166 .map(|c| {
4167 let ident = &c.ident;
4168 quote! {
4169 #ident: ::core::clone::Clone::clone(&self.#ident)
4170 }
4171 })
4172 .collect();
4173 let pk_clone_token = if fields.pk_is_auto {
4174 quote! { #pk_ident: #root::sql::Auto::Unset }
4175 } else {
4176 quote! { #pk_ident: ::core::clone::Clone::clone(&self.#pk_ident) }
4177 };
4178 let replicate_doc = if fields.pk_is_auto {
4179 quote! {
4180 }
4192 } else {
4193 quote! {
4194 }
4200 };
4201 let column_names: ::std::collections::HashSet<String> = fields
4215 .column_entries
4216 .iter()
4217 .map(|c| ident_name(&c.ident))
4218 .collect();
4219 let emit_if_no_field_collision = |name: &str, tokens: TokenStream2| -> TokenStream2 {
4220 if column_names.contains(name) {
4221 quote! {}
4222 } else {
4223 tokens
4224 }
4225 };
4226 let count_method = emit_if_no_field_collision(
4227 "count",
4228 quote! {
4229 pub async fn count(
4241 pool: &#root::sql::Pool,
4242 ) -> ::core::result::Result<i64, #root::sql::ExecError> {
4243 use #root::sql::CounterPool as _;
4244 #root::query::QuerySet::<Self>::default()
4245 .count(pool)
4246 .await
4247 }
4248 },
4249 );
4250 let value_method = emit_if_no_field_collision(
4251 "value",
4252 quote! {
4253 pub async fn value<U>(
4263 col: &str,
4264 pool: &#root::sql::Pool,
4265 ) -> ::core::result::Result<
4266 ::core::option::Option<U>,
4267 #root::sql::ExecError,
4268 >
4269 where
4270 U: #root::sql::FlatScalar,
4271 {
4272 let _col_static: &'static str = Self::__resolve_col(col)?;
4273 #root::query::QuerySet::<Self>::default()
4274 .values_list_flat(_col_static)
4275 .first::<U>(pool)
4276 .await
4277 }
4278 },
4279 );
4280 let sum_method = emit_if_no_field_collision(
4281 "sum",
4282 quote! {
4283 pub async fn sum<U>(
4290 col: &str,
4291 pool: &#root::sql::Pool,
4292 ) -> ::core::result::Result<
4293 ::core::option::Option<U>,
4294 #root::sql::ExecError,
4295 >
4296 where
4297 (::core::option::Option<U>,): #root::sql::MaybePgFromRow
4298 + #root::sql::MaybeMyFromRow
4299 + #root::sql::MaybeSqliteFromRow
4300 + ::core::marker::Send
4301 + ::core::marker::Unpin,
4302 {
4303 #root::query::QuerySet::<Self>::default()
4304 .sum::<U>(col, pool)
4305 .await
4306 }
4307 },
4308 );
4309 let avg_method = emit_if_no_field_collision(
4310 "avg",
4311 quote! {
4312 pub async fn avg<U>(
4318 col: &str,
4319 pool: &#root::sql::Pool,
4320 ) -> ::core::result::Result<
4321 ::core::option::Option<U>,
4322 #root::sql::ExecError,
4323 >
4324 where
4325 (::core::option::Option<U>,): #root::sql::MaybePgFromRow
4326 + #root::sql::MaybeMyFromRow
4327 + #root::sql::MaybeSqliteFromRow
4328 + ::core::marker::Send
4329 + ::core::marker::Unpin,
4330 {
4331 #root::query::QuerySet::<Self>::default()
4332 .avg::<U>(col, pool)
4333 .await
4334 }
4335 },
4336 );
4337 let min_method = emit_if_no_field_collision(
4338 "min",
4339 quote! {
4340 pub async fn min<U>(
4346 col: &str,
4347 pool: &#root::sql::Pool,
4348 ) -> ::core::result::Result<
4349 ::core::option::Option<U>,
4350 #root::sql::ExecError,
4351 >
4352 where
4353 (::core::option::Option<U>,): #root::sql::MaybePgFromRow
4354 + #root::sql::MaybeMyFromRow
4355 + #root::sql::MaybeSqliteFromRow
4356 + ::core::marker::Send
4357 + ::core::marker::Unpin,
4358 {
4359 #root::query::QuerySet::<Self>::default()
4360 .min::<U>(col, pool)
4361 .await
4362 }
4363 },
4364 );
4365 let max_method = emit_if_no_field_collision(
4366 "max",
4367 quote! {
4368 pub async fn max<U>(
4374 col: &str,
4375 pool: &#root::sql::Pool,
4376 ) -> ::core::result::Result<
4377 ::core::option::Option<U>,
4378 #root::sql::ExecError,
4379 >
4380 where
4381 (::core::option::Option<U>,): #root::sql::MaybePgFromRow
4382 + #root::sql::MaybeMyFromRow
4383 + #root::sql::MaybeSqliteFromRow
4384 + ::core::marker::Send
4385 + ::core::marker::Unpin,
4386 {
4387 #root::query::QuerySet::<Self>::default()
4388 .max::<U>(col, pool)
4389 .await
4390 }
4391 },
4392 );
4393 let first_method = emit_if_no_field_collision(
4394 "first",
4395 quote! {
4396 pub async fn first(
4403 pool: &#root::sql::Pool,
4404 ) -> ::core::result::Result<
4405 ::core::option::Option<Self>,
4406 #root::sql::ExecError,
4407 > {
4408 #root::query::QuerySet::<Self>::default()
4409 .first(pool)
4410 .await
4411 }
4412 },
4413 );
4414 let last_method = emit_if_no_field_collision(
4415 "last",
4416 quote! {
4417 pub async fn last(
4430 pool: &#root::sql::Pool,
4431 ) -> ::core::result::Result<
4432 ::core::option::Option<Self>,
4433 #root::sql::ExecError,
4434 > {
4435 #root::query::QuerySet::<Self>::default()
4436 .last(pool)
4437 .await
4438 }
4439 },
4440 );
4441 quote! {
4442 pub async fn refresh_from_db(
4461 &mut self,
4462 pool: &#root::sql::Pool,
4463 ) -> ::core::result::Result<(), #root::sql::ExecError> {
4464 match self.fresh(pool).await? {
4465 ::core::option::Option::Some(_fresh) => {
4466 *self = _fresh;
4467 ::core::result::Result::Ok(())
4468 }
4469 ::core::option::Option::None => ::core::result::Result::Err(
4470 #root::sql::ExecError::Driver(
4471 #root::sql::sqlx::Error::RowNotFound,
4472 ),
4473 ),
4474 }
4475 }
4476
4477 pub async fn increment(
4497 &self,
4498 col: &str,
4499 by: i64,
4500 pool: &#root::sql::Pool,
4501 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4502 Self::__increment_one(self, col, by, pool).await
4503 }
4504
4505 pub async fn decrement(
4514 &self,
4515 col: &str,
4516 by: i64,
4517 pool: &#root::sql::Pool,
4518 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4519 Self::__increment_one(self, col, -by, pool).await
4520 }
4521
4522 pub async fn increment_each(
4532 col: &str,
4533 by: i64,
4534 pool: &#root::sql::Pool,
4535 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4536 Self::__increment_all(col, by, pool).await
4537 }
4538
4539 pub async fn decrement_each(
4544 col: &str,
4545 by: i64,
4546 pool: &#root::sql::Pool,
4547 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4548 Self::__increment_all(col, -by, pool).await
4549 }
4550
4551 #[doc(hidden)]
4557 pub async fn __increment_one(
4558 this: &Self,
4559 col: &str,
4560 by: i64,
4561 pool: &#root::sql::Pool,
4562 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4563 let _pk_val: #root::core::SqlValue = ::core::convert::Into::into(
4564 ::core::clone::Clone::clone(&this.#pk_ident),
4565 );
4566 #root::sql::model_shortcuts::increment_one_pool::<Self>(
4567 ::core::stringify!(#pk_ident),
4568 _pk_val,
4569 col,
4570 by,
4571 pool,
4572 )
4573 .await
4574 }
4575
4576 #[doc(hidden)]
4579 pub async fn __increment_all(
4580 col: &str,
4581 by: i64,
4582 pool: &#root::sql::Pool,
4583 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4584 let _query =
4585 #root::sql::model_shortcuts::increment_all_query::<Self>(col, by)?;
4586 #bulk_update_run
4587 }
4588
4589 #[doc(hidden)]
4592 pub fn __resolve_col(
4593 col: &str,
4594 ) -> ::core::result::Result<&'static str, #root::sql::ExecError> {
4595 #root::sql::model_shortcuts::resolve_col::<Self>(col)
4596 }
4597
4598 #[doc(hidden)]
4601 #[must_use]
4602 pub fn __add_signed_expr(
4603 col_static: &'static str,
4604 signed_by: i64,
4605 ) -> #root::core::Expr {
4606 #root::sql::model_shortcuts::add_signed_expr(col_static, signed_by)
4607 }
4608
4609 pub async fn fresh(
4629 &self,
4630 pool: &#root::sql::Pool,
4631 ) -> ::core::result::Result<
4632 ::core::option::Option<Self>,
4633 #root::sql::ExecError,
4634 > {
4635 use #root::sql::FetcherPool as _;
4636 let _pk_val: #root::core::SqlValue = ::core::convert::Into::into(
4637 ::core::clone::Clone::clone(&self.#pk_ident),
4638 );
4639 let _rows: ::std::vec::Vec<Self> =
4641 #root::query::QuerySet::<Self>::default()
4642 .without_global_scopes()
4643 .filter(::core::stringify!(#pk_ident), _pk_val)
4644 .limit(1)
4645 .fetch(pool)
4646 .await?;
4647 ::core::result::Result::Ok(_rows.into_iter().next())
4648 }
4649
4650 #replicate_doc
4651 #[must_use]
4652 pub fn replicate(&self) -> Self {
4653 Self {
4654 #pk_clone_token,
4655 #( #other_field_clones, )*
4656 }
4657 }
4658
4659 #first_method
4660
4661 #last_method
4662
4663 pub async fn first_or_fail(
4673 pool: &#root::sql::Pool,
4674 ) -> ::core::result::Result<Self, #root::sql::ExecError> {
4675 match #root::query::QuerySet::<Self>::default().first(pool).await? {
4679 ::core::option::Option::Some(_row) => ::core::result::Result::Ok(_row),
4680 ::core::option::Option::None => ::core::result::Result::Err(
4681 #root::sql::ExecError::Driver(
4682 #root::sql::sqlx::Error::RowNotFound,
4683 ),
4684 ),
4685 }
4686 }
4687
4688 pub async fn pluck<U>(
4700 col: &'static str,
4701 pool: &#root::sql::Pool,
4702 ) -> ::core::result::Result<::std::vec::Vec<U>, #root::sql::ExecError>
4703 where
4704 U: #root::sql::FlatScalar,
4705 {
4706 #root::query::QuerySet::<Self>::default()
4707 .values_list_flat(col)
4708 .fetch::<U>(pool)
4709 .await
4710 }
4711
4712 pub async fn chunk<F, Fut>(
4738 n: i64,
4739 pool: &#root::sql::Pool,
4740 mut cb: F,
4741 ) -> ::core::result::Result<(), #root::sql::ExecError>
4742 where
4743 F: ::core::ops::FnMut(::std::vec::Vec<Self>) -> Fut,
4744 Fut: ::core::future::Future<
4745 Output = ::core::result::Result<(), #root::sql::ExecError>,
4746 >,
4747 {
4748 use #root::sql::FetcherPool as _;
4749 let pk_col = match <Self as #root::core::Model>::SCHEMA.primary_key() {
4751 ::core::option::Option::Some(f) => f.name,
4752 ::core::option::Option::None => {
4753 return ::core::result::Result::Ok(());
4754 }
4755 };
4756 let mut offset: i64 = 0;
4757 loop {
4758 let rows: ::std::vec::Vec<Self> =
4759 #root::query::QuerySet::<Self>::default()
4760 .order_by(&[(pk_col, false)])
4761 .limit(n)
4762 .offset(offset)
4763 .fetch(pool)
4764 .await?;
4765 if rows.is_empty() {
4766 return ::core::result::Result::Ok(());
4767 }
4768 let len = rows.len() as i64;
4769 cb(rows).await?;
4770 if len < n {
4771 return ::core::result::Result::Ok(());
4772 }
4773 offset += n;
4774 }
4775 }
4776
4777 pub async fn chunk_by_id<F, Fut>(
4789 n: i64,
4790 pool: &#root::sql::Pool,
4791 mut cb: F,
4792 ) -> ::core::result::Result<(), #root::sql::ExecError>
4793 where
4794 F: ::core::ops::FnMut(::std::vec::Vec<Self>) -> Fut,
4795 Fut: ::core::future::Future<
4796 Output = ::core::result::Result<(), #root::sql::ExecError>,
4797 >,
4798 {
4799 use #root::sql::FetcherPool as _;
4800 let pk_col = match <Self as #root::core::Model>::SCHEMA.primary_key() {
4802 ::core::option::Option::Some(f) => f.name,
4803 ::core::option::Option::None => {
4804 return ::core::result::Result::Ok(());
4805 }
4806 };
4807 let key = ::std::format!("{}__gt", pk_col);
4810 let mut last_seen: ::core::option::Option<#root::core::SqlValue> =
4811 ::core::option::Option::None;
4812 loop {
4813 let mut qs = #root::query::QuerySet::<Self>::default();
4814 if let ::core::option::Option::Some(v) = last_seen.take() {
4815 qs = qs.filter(key.as_str(), v);
4816 }
4817 let rows: ::std::vec::Vec<Self> = qs
4818 .order_by(&[(pk_col, false)])
4819 .limit(n)
4820 .fetch(pool)
4821 .await?;
4822 let len = rows.len() as i64;
4823 let ::core::option::Option::Some(max_pk) =
4826 rows.last().map(|r| r.__rustango_pk_value())
4827 else {
4828 return ::core::result::Result::Ok(());
4829 };
4830 cb(rows).await?;
4831 if len < n {
4832 return ::core::result::Result::Ok(());
4833 }
4834 last_seen = ::core::option::Option::Some(max_pk);
4835 }
4836 }
4837
4838 pub async fn each<F, Fut>(
4856 batch: i64,
4857 pool: &#root::sql::Pool,
4858 mut cb: F,
4859 ) -> ::core::result::Result<(), #root::sql::ExecError>
4860 where
4861 F: ::core::ops::FnMut(Self) -> Fut,
4862 Fut: ::core::future::Future<
4863 Output = ::core::result::Result<(), #root::sql::ExecError>,
4864 >,
4865 {
4866 use #root::sql::FetcherPool as _;
4867 let pk_col = match <Self as #root::core::Model>::SCHEMA.primary_key() {
4869 ::core::option::Option::Some(f) => f.name,
4870 ::core::option::Option::None => {
4871 return ::core::result::Result::Ok(());
4872 }
4873 };
4874 let key = ::std::format!("{}__gt", pk_col);
4875 let mut last_seen: ::core::option::Option<#root::core::SqlValue> =
4876 ::core::option::Option::None;
4877 loop {
4878 let mut qs = #root::query::QuerySet::<Self>::default();
4879 if let ::core::option::Option::Some(v) = last_seen.take() {
4880 qs = qs.filter(key.as_str(), v);
4881 }
4882 let rows: ::std::vec::Vec<Self> = qs
4883 .order_by(&[(pk_col, false)])
4884 .limit(batch)
4885 .fetch(pool)
4886 .await?;
4887 let len = rows.len() as i64;
4888 let ::core::option::Option::Some(max_pk) =
4889 rows.last().map(|r| r.__rustango_pk_value())
4890 else {
4891 return ::core::result::Result::Ok(());
4892 };
4893 for row in rows {
4894 cb(row).await?;
4895 }
4896 if len < batch {
4897 return ::core::result::Result::Ok(());
4898 }
4899 last_seen = ::core::option::Option::Some(max_pk);
4900 }
4901 }
4902
4903 pub async fn truncate(
4919 pool: &#root::sql::Pool,
4920 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4921 #truncate_run
4922 }
4923
4924 pub async fn truncate_cascade(
4933 pool: &#root::sql::Pool,
4934 ) -> ::core::result::Result<(), #root::sql::ExecError> {
4935 #truncate_cascade_run
4936 }
4937
4938 pub async fn destroy<V>(
4953 pks: impl ::core::iter::IntoIterator<Item = V>,
4954 pool: &#root::sql::Pool,
4955 ) -> ::core::result::Result<u64, #root::sql::ExecError>
4956 where
4957 V: ::core::convert::Into<#root::core::SqlValue>,
4958 {
4959 let _values: ::std::vec::Vec<#root::core::SqlValue> =
4960 pks.into_iter().map(::core::convert::Into::into).collect();
4961 if _values.is_empty() {
4962 return ::core::result::Result::Ok(0);
4963 }
4964 let _pk = <Self as #root::core::Model>::SCHEMA
4965 .primary_key()
4966 .ok_or_else(|| {
4967 #root::sql::ExecError::Sql(#root::sql::SqlError::MissingPrimaryKey)
4968 })?;
4969 let mut _queries = ::std::vec::Vec::new();
4971 for _keys in #root::sql::__rustango_in_batches::<Self>(pool, _values)? {
4972 _queries.push(
4973 #root::query::QuerySet::<Self>::default()
4974 .filter_op(
4975 _pk.name,
4976 #root::core::Op::In,
4977 #root::core::SqlValue::List(_keys),
4978 )
4979 .compile_delete()?,
4980 );
4981 }
4982 #destroy_run
4983 }
4984
4985 pub async fn where_(
5003 col: &str,
5004 val: impl ::core::convert::Into<#root::core::SqlValue>,
5005 pool: &#root::sql::Pool,
5006 ) -> ::core::result::Result<
5007 ::std::vec::Vec<Self>,
5008 #root::sql::ExecError,
5009 > {
5010 use #root::sql::FetcherPool as _;
5011 #root::query::QuerySet::<Self>::default()
5012 .filter(col, val)
5013 .fetch(pool)
5014 .await
5015 }
5016
5017 pub async fn where_in<V>(
5031 col: &str,
5032 vals: impl ::core::iter::IntoIterator<Item = V>,
5033 pool: &#root::sql::Pool,
5034 ) -> ::core::result::Result<
5035 ::std::vec::Vec<Self>,
5036 #root::sql::ExecError,
5037 >
5038 where
5039 V: ::core::convert::Into<#root::core::SqlValue>,
5040 {
5041 use #root::sql::FetcherPool as _;
5042 let _values: ::std::vec::Vec<#root::core::SqlValue> =
5043 vals.into_iter().map(::core::convert::Into::into).collect();
5044 if _values.is_empty() {
5045 return ::core::result::Result::Ok(::std::vec::Vec::new());
5046 }
5047 let _key = ::std::format!("{}__in", col);
5048 let _batches = #root::sql::__rustango_in_batches::<Self>(pool, _values)?;
5049 let _split = _batches.len() > 1;
5050 let mut _rows = ::std::vec::Vec::new();
5051 for _keys in _batches {
5052 _rows.extend(
5053 #root::query::QuerySet::<Self>::default()
5054 .filter(&_key, #root::core::SqlValue::List(_keys))
5055 .fetch(pool)
5056 .await?,
5057 );
5058 }
5059 if _split {
5060 _rows = #root::sql::__rustango_dedupe_by_pk(_rows);
5061 }
5062 ::core::result::Result::Ok(_rows)
5063 }
5064
5065 pub async fn where_not_in<V>(
5076 col: &str,
5077 vals: impl ::core::iter::IntoIterator<Item = V>,
5078 pool: &#root::sql::Pool,
5079 ) -> ::core::result::Result<
5080 ::std::vec::Vec<Self>,
5081 #root::sql::ExecError,
5082 >
5083 where
5084 V: ::core::convert::Into<#root::core::SqlValue>,
5085 {
5086 use #root::sql::FetcherPool as _;
5087 let _values: ::std::vec::Vec<#root::core::SqlValue> =
5088 vals.into_iter().map(::core::convert::Into::into).collect();
5089 if _values.is_empty() {
5090 return #root::query::QuerySet::<Self>::default()
5091 .fetch(pool)
5092 .await;
5093 }
5094 let _key = ::std::format!("{}__not_in", col);
5095 let _values = #root::sql::__rustango_one_in_list::<Self>(pool, _values)?;
5097 #root::query::QuerySet::<Self>::default()
5098 .filter(&_key, #root::core::SqlValue::List(_values))
5099 .fetch(pool)
5100 .await
5101 }
5102
5103 pub async fn where_null(
5111 col: &str,
5112 pool: &#root::sql::Pool,
5113 ) -> ::core::result::Result<
5114 ::std::vec::Vec<Self>,
5115 #root::sql::ExecError,
5116 > {
5117 use #root::sql::FetcherPool as _;
5118 let _key = ::std::format!("{}__isnull", col);
5119 #root::query::QuerySet::<Self>::default()
5120 .filter(&_key, #root::core::SqlValue::Bool(true))
5121 .fetch(pool)
5122 .await
5123 }
5124
5125 pub async fn where_not_null(
5133 col: &str,
5134 pool: &#root::sql::Pool,
5135 ) -> ::core::result::Result<
5136 ::std::vec::Vec<Self>,
5137 #root::sql::ExecError,
5138 > {
5139 use #root::sql::FetcherPool as _;
5140 let _key = ::std::format!("{}__isnull", col);
5141 #root::query::QuerySet::<Self>::default()
5142 .filter(&_key, #root::core::SqlValue::Bool(false))
5143 .fetch(pool)
5144 .await
5145 }
5146
5147 pub async fn random_n(
5160 n: i64,
5161 pool: &#root::sql::Pool,
5162 ) -> ::core::result::Result<
5163 ::std::vec::Vec<Self>,
5164 #root::sql::ExecError,
5165 > {
5166 use #root::sql::FetcherPool as _;
5167 #root::query::QuerySet::<Self>::default()
5168 .order_random()
5169 .limit(n)
5170 .fetch(pool)
5171 .await
5172 }
5173
5174 pub async fn random(
5183 pool: &#root::sql::Pool,
5184 ) -> ::core::result::Result<
5185 ::core::option::Option<Self>,
5186 #root::sql::ExecError,
5187 > {
5188 ::core::result::Result::Ok(
5189 Self::random_n(1, pool).await?.into_iter().next(),
5190 )
5191 }
5192
5193 pub async fn oldest(
5202 field: &str,
5203 pool: &#root::sql::Pool,
5204 ) -> ::core::result::Result<
5205 ::std::vec::Vec<Self>,
5206 #root::sql::ExecError,
5207 > {
5208 use #root::sql::FetcherPool as _;
5209 #root::query::QuerySet::<Self>::default()
5210 .order_by(&[(field, false)])
5211 .fetch(pool)
5212 .await
5213 }
5214
5215 pub async fn newest(
5224 field: &str,
5225 pool: &#root::sql::Pool,
5226 ) -> ::core::result::Result<
5227 ::std::vec::Vec<Self>,
5228 #root::sql::ExecError,
5229 > {
5230 use #root::sql::FetcherPool as _;
5231 #root::query::QuerySet::<Self>::default()
5232 .order_by(&[(field, true)])
5233 .fetch(pool)
5234 .await
5235 }
5236
5237 pub async fn where_year(
5247 col: &str,
5248 year: i64,
5249 pool: &#root::sql::Pool,
5250 ) -> ::core::result::Result<
5251 ::std::vec::Vec<Self>,
5252 #root::sql::ExecError,
5253 > {
5254 use #root::sql::FetcherPool as _;
5255 let _key = ::std::format!("{}__year", col);
5256 #root::query::QuerySet::<Self>::default()
5257 .filter(&_key, #root::core::SqlValue::I64(year))
5258 .fetch(pool)
5259 .await
5260 }
5261
5262 pub async fn where_month(
5271 col: &str,
5272 month: i64,
5273 pool: &#root::sql::Pool,
5274 ) -> ::core::result::Result<
5275 ::std::vec::Vec<Self>,
5276 #root::sql::ExecError,
5277 > {
5278 use #root::sql::FetcherPool as _;
5279 let _key = ::std::format!("{}__month", col);
5280 #root::query::QuerySet::<Self>::default()
5281 .filter(&_key, #root::core::SqlValue::I64(month))
5282 .fetch(pool)
5283 .await
5284 }
5285
5286 pub async fn where_day(
5295 col: &str,
5296 day: i64,
5297 pool: &#root::sql::Pool,
5298 ) -> ::core::result::Result<
5299 ::std::vec::Vec<Self>,
5300 #root::sql::ExecError,
5301 > {
5302 use #root::sql::FetcherPool as _;
5303 let _key = ::std::format!("{}__day", col);
5304 #root::query::QuerySet::<Self>::default()
5305 .filter(&_key, #root::core::SqlValue::I64(day))
5306 .fetch(pool)
5307 .await
5308 }
5309
5310 pub async fn where_hour(
5319 col: &str,
5320 hour: i64,
5321 pool: &#root::sql::Pool,
5322 ) -> ::core::result::Result<
5323 ::std::vec::Vec<Self>,
5324 #root::sql::ExecError,
5325 > {
5326 use #root::sql::FetcherPool as _;
5327 let _key = ::std::format!("{}__hour", col);
5328 #root::query::QuerySet::<Self>::default()
5329 .filter(&_key, #root::core::SqlValue::I64(hour))
5330 .fetch(pool)
5331 .await
5332 }
5333
5334 pub async fn where_minute(
5343 col: &str,
5344 minute: i64,
5345 pool: &#root::sql::Pool,
5346 ) -> ::core::result::Result<
5347 ::std::vec::Vec<Self>,
5348 #root::sql::ExecError,
5349 > {
5350 use #root::sql::FetcherPool as _;
5351 let _key = ::std::format!("{}__minute", col);
5352 #root::query::QuerySet::<Self>::default()
5353 .filter(&_key, #root::core::SqlValue::I64(minute))
5354 .fetch(pool)
5355 .await
5356 }
5357
5358 pub async fn where_like(
5368 col: &str,
5369 pattern: impl ::core::convert::Into<::std::string::String>,
5370 pool: &#root::sql::Pool,
5371 ) -> ::core::result::Result<
5372 ::std::vec::Vec<Self>,
5373 #root::sql::ExecError,
5374 > {
5375 use #root::sql::FetcherPool as _;
5376 let _key = ::std::format!("{}__like", col);
5377 #root::query::QuerySet::<Self>::default()
5378 .filter(
5379 &_key,
5380 #root::core::SqlValue::String(pattern.into()),
5381 )
5382 .fetch(pool)
5383 .await
5384 }
5385
5386 pub async fn where_ilike(
5395 col: &str,
5396 pattern: impl ::core::convert::Into<::std::string::String>,
5397 pool: &#root::sql::Pool,
5398 ) -> ::core::result::Result<
5399 ::std::vec::Vec<Self>,
5400 #root::sql::ExecError,
5401 > {
5402 use #root::sql::FetcherPool as _;
5403 let _key = ::std::format!("{}__ilike", col);
5404 #root::query::QuerySet::<Self>::default()
5405 .filter(
5406 &_key,
5407 #root::core::SqlValue::String(pattern.into()),
5408 )
5409 .fetch(pool)
5410 .await
5411 }
5412
5413 pub async fn where_starts_with(
5422 col: &str,
5423 prefix: impl ::core::convert::Into<::std::string::String>,
5424 pool: &#root::sql::Pool,
5425 ) -> ::core::result::Result<
5426 ::std::vec::Vec<Self>,
5427 #root::sql::ExecError,
5428 > {
5429 use #root::sql::FetcherPool as _;
5430 let _key = ::std::format!("{}__startswith", col);
5431 #root::query::QuerySet::<Self>::default()
5432 .filter(
5433 &_key,
5434 #root::core::SqlValue::String(prefix.into()),
5435 )
5436 .fetch(pool)
5437 .await
5438 }
5439
5440 pub async fn where_ends_with(
5449 col: &str,
5450 suffix: impl ::core::convert::Into<::std::string::String>,
5451 pool: &#root::sql::Pool,
5452 ) -> ::core::result::Result<
5453 ::std::vec::Vec<Self>,
5454 #root::sql::ExecError,
5455 > {
5456 use #root::sql::FetcherPool as _;
5457 let _key = ::std::format!("{}__endswith", col);
5458 #root::query::QuerySet::<Self>::default()
5459 .filter(
5460 &_key,
5461 #root::core::SqlValue::String(suffix.into()),
5462 )
5463 .fetch(pool)
5464 .await
5465 }
5466
5467 pub async fn where_contains(
5476 col: &str,
5477 substr: impl ::core::convert::Into<::std::string::String>,
5478 pool: &#root::sql::Pool,
5479 ) -> ::core::result::Result<
5480 ::std::vec::Vec<Self>,
5481 #root::sql::ExecError,
5482 > {
5483 use #root::sql::FetcherPool as _;
5484 let _key = ::std::format!("{}__contains", col);
5485 #root::query::QuerySet::<Self>::default()
5486 .filter(
5487 &_key,
5488 #root::core::SqlValue::String(substr.into()),
5489 )
5490 .fetch(pool)
5491 .await
5492 }
5493
5494 pub async fn where_gt(
5502 col: &str,
5503 val: impl ::core::convert::Into<#root::core::SqlValue>,
5504 pool: &#root::sql::Pool,
5505 ) -> ::core::result::Result<
5506 ::std::vec::Vec<Self>,
5507 #root::sql::ExecError,
5508 > {
5509 use #root::sql::FetcherPool as _;
5510 let _key = ::std::format!("{}__gt", col);
5511 #root::query::QuerySet::<Self>::default()
5512 .filter(&_key, val)
5513 .fetch(pool)
5514 .await
5515 }
5516
5517 pub async fn where_gte(
5525 col: &str,
5526 val: impl ::core::convert::Into<#root::core::SqlValue>,
5527 pool: &#root::sql::Pool,
5528 ) -> ::core::result::Result<
5529 ::std::vec::Vec<Self>,
5530 #root::sql::ExecError,
5531 > {
5532 use #root::sql::FetcherPool as _;
5533 let _key = ::std::format!("{}__gte", col);
5534 #root::query::QuerySet::<Self>::default()
5535 .filter(&_key, val)
5536 .fetch(pool)
5537 .await
5538 }
5539
5540 pub async fn where_lt(
5548 col: &str,
5549 val: impl ::core::convert::Into<#root::core::SqlValue>,
5550 pool: &#root::sql::Pool,
5551 ) -> ::core::result::Result<
5552 ::std::vec::Vec<Self>,
5553 #root::sql::ExecError,
5554 > {
5555 use #root::sql::FetcherPool as _;
5556 let _key = ::std::format!("{}__lt", col);
5557 #root::query::QuerySet::<Self>::default()
5558 .filter(&_key, val)
5559 .fetch(pool)
5560 .await
5561 }
5562
5563 pub async fn where_lte(
5571 col: &str,
5572 val: impl ::core::convert::Into<#root::core::SqlValue>,
5573 pool: &#root::sql::Pool,
5574 ) -> ::core::result::Result<
5575 ::std::vec::Vec<Self>,
5576 #root::sql::ExecError,
5577 > {
5578 use #root::sql::FetcherPool as _;
5579 let _key = ::std::format!("{}__lte", col);
5580 #root::query::QuerySet::<Self>::default()
5581 .filter(&_key, val)
5582 .fetch(pool)
5583 .await
5584 }
5585
5586 pub async fn where_ne(
5594 col: &str,
5595 val: impl ::core::convert::Into<#root::core::SqlValue>,
5596 pool: &#root::sql::Pool,
5597 ) -> ::core::result::Result<
5598 ::std::vec::Vec<Self>,
5599 #root::sql::ExecError,
5600 > {
5601 use #root::sql::FetcherPool as _;
5602 let _key = ::std::format!("{}__ne", col);
5603 #root::query::QuerySet::<Self>::default()
5604 .filter(&_key, val)
5605 .fetch(pool)
5606 .await
5607 }
5608
5609 pub async fn where_any(
5623 cols: &[&str],
5624 val: impl ::core::convert::Into<#root::core::SqlValue>,
5625 pool: &#root::sql::Pool,
5626 ) -> ::core::result::Result<
5627 ::std::vec::Vec<Self>,
5628 #root::sql::ExecError,
5629 > {
5630 Self::__where_multi(cols, val, false, pool).await
5631 }
5632
5633 pub async fn where_all(
5640 cols: &[&str],
5641 val: impl ::core::convert::Into<#root::core::SqlValue>,
5642 pool: &#root::sql::Pool,
5643 ) -> ::core::result::Result<
5644 ::std::vec::Vec<Self>,
5645 #root::sql::ExecError,
5646 > {
5647 Self::__where_multi(cols, val, true, pool).await
5648 }
5649
5650 #[doc(hidden)]
5654 pub async fn __where_multi(
5655 cols: &[&str],
5656 val: impl ::core::convert::Into<#root::core::SqlValue>,
5657 all: bool,
5658 pool: &#root::sql::Pool,
5659 ) -> ::core::result::Result<
5660 ::std::vec::Vec<Self>,
5661 #root::sql::ExecError,
5662 > {
5663 #root::sql::model_shortcuts::where_multi_pool::<Self>(cols, val, all, pool)
5664 .await
5665 }
5666
5667 pub async fn take(
5677 n: i64,
5678 pool: &#root::sql::Pool,
5679 ) -> ::core::result::Result<
5680 ::std::vec::Vec<Self>,
5681 #root::sql::ExecError,
5682 > {
5683 use #root::sql::FetcherPool as _;
5684 #root::query::QuerySet::<Self>::default()
5685 .limit(n)
5686 .fetch(pool)
5687 .await
5688 }
5689
5690 pub async fn for_page(
5702 page: i64,
5703 per_page: i64,
5704 pool: &#root::sql::Pool,
5705 ) -> ::core::result::Result<
5706 ::std::vec::Vec<Self>,
5707 #root::sql::ExecError,
5708 > {
5709 use #root::sql::FetcherPool as _;
5710 let _offset = #root::list_params::page_offset(page, per_page);
5711 #root::query::QuerySet::<Self>::default()
5712 .limit(per_page)
5713 .offset(_offset)
5714 .fetch(pool)
5715 .await
5716 }
5717
5718 pub async fn paginate(
5734 page: i64,
5735 per_page: i64,
5736 pool: &#root::sql::Pool,
5737 ) -> ::core::result::Result<
5738 (::std::vec::Vec<Self>, i64),
5739 #root::sql::ExecError,
5740 > {
5741 let total = {
5747 use #root::sql::CounterPool as _;
5748 #root::query::QuerySet::<Self>::default()
5749 .count(pool)
5750 .await?
5751 };
5752 let rows = Self::for_page(page, per_page, pool).await?;
5753 ::core::result::Result::Ok((rows, total))
5754 }
5755
5756 pub async fn update_where(
5770 where_col: &str,
5771 where_val: impl ::core::convert::Into<#root::core::SqlValue>,
5772 set_col: &str,
5773 set_val: impl ::core::convert::Into<#root::core::SqlValue>,
5774 pool: &#root::sql::Pool,
5775 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
5776 let _query = #root::query::QuerySet::<Self>::default()
5777 .filter(where_col, where_val)
5778 .update()
5779 .set(set_col, set_val)
5780 .compile()?;
5781 #bulk_update_run
5782 }
5783
5784 pub async fn delete_where(
5792 where_col: &str,
5793 where_val: impl ::core::convert::Into<#root::core::SqlValue>,
5794 pool: &#root::sql::Pool,
5795 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
5796 let _query = #root::query::QuerySet::<Self>::default()
5797 .filter_op(where_col, #root::core::Op::Eq, where_val)
5798 .compile_delete()?;
5799 #bulk_delete_run
5800 }
5801
5802 pub async fn update_all(
5815 set_col: &str,
5816 set_val: impl ::core::convert::Into<#root::core::SqlValue>,
5817 pool: &#root::sql::Pool,
5818 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
5819 let _query = #root::query::QuerySet::<Self>::default()
5820 .update()
5821 .set(set_col, set_val)
5822 .compile()?;
5823 #bulk_update_run
5824 }
5825
5826 pub async fn where_not_like(
5835 col: &str,
5836 pattern: impl ::core::convert::Into<::std::string::String>,
5837 pool: &#root::sql::Pool,
5838 ) -> ::core::result::Result<
5839 ::std::vec::Vec<Self>,
5840 #root::sql::ExecError,
5841 > {
5842 use #root::sql::FetcherPool as _;
5843 let _key = ::std::format!("{}__not_like", col);
5844 #root::query::QuerySet::<Self>::default()
5845 .filter(
5846 &_key,
5847 #root::core::SqlValue::String(pattern.into()),
5848 )
5849 .fetch(pool)
5850 .await
5851 }
5852
5853 pub async fn where_not_ilike(
5862 col: &str,
5863 pattern: impl ::core::convert::Into<::std::string::String>,
5864 pool: &#root::sql::Pool,
5865 ) -> ::core::result::Result<
5866 ::std::vec::Vec<Self>,
5867 #root::sql::ExecError,
5868 > {
5869 use #root::sql::FetcherPool as _;
5870 let _key = ::std::format!("{}__not_ilike", col);
5871 #root::query::QuerySet::<Self>::default()
5872 .filter(
5873 &_key,
5874 #root::core::SqlValue::String(pattern.into()),
5875 )
5876 .fetch(pool)
5877 .await
5878 }
5879
5880 pub async fn where_not_between(
5889 col: &str,
5890 lo: impl ::core::convert::Into<#root::core::SqlValue>,
5891 hi: impl ::core::convert::Into<#root::core::SqlValue>,
5892 pool: &#root::sql::Pool,
5893 ) -> ::core::result::Result<
5894 ::std::vec::Vec<Self>,
5895 #root::sql::ExecError,
5896 > {
5897 use #root::sql::FetcherPool as _;
5898 let _key = ::std::format!("{}__not_between", col);
5899 let _vals = #root::core::SqlValue::List(::std::vec![
5900 ::core::convert::Into::into(lo),
5901 ::core::convert::Into::into(hi),
5902 ]);
5903 #root::query::QuerySet::<Self>::default()
5904 .filter(&_key, _vals)
5905 .fetch(pool)
5906 .await
5907 }
5908
5909 #[must_use]
5912 pub fn table_name() -> &'static str {
5913 <Self as #root::core::Model>::SCHEMA.table
5914 }
5915
5916 #[must_use]
5921 pub fn primary_key_column() -> ::core::option::Option<&'static str> {
5922 <Self as #root::core::Model>::SCHEMA
5923 .primary_key()
5924 .map(|f| f.column)
5925 }
5926
5927 pub async fn where_between(
5936 col: &str,
5937 lo: impl ::core::convert::Into<#root::core::SqlValue>,
5938 hi: impl ::core::convert::Into<#root::core::SqlValue>,
5939 pool: &#root::sql::Pool,
5940 ) -> ::core::result::Result<
5941 ::std::vec::Vec<Self>,
5942 #root::sql::ExecError,
5943 > {
5944 use #root::sql::FetcherPool as _;
5945 let _key = ::std::format!("{}__between", col);
5946 let _vals = #root::core::SqlValue::List(::std::vec![
5947 ::core::convert::Into::into(lo),
5948 ::core::convert::Into::into(hi),
5949 ]);
5950 #root::query::QuerySet::<Self>::default()
5951 .filter(&_key, _vals)
5952 .fetch(pool)
5953 .await
5954 }
5955
5956 pub async fn first_where(
5971 col: &str,
5972 val: impl ::core::convert::Into<#root::core::SqlValue>,
5973 pool: &#root::sql::Pool,
5974 ) -> ::core::result::Result<
5975 ::core::option::Option<Self>,
5976 #root::sql::ExecError,
5977 > {
5978 #root::query::QuerySet::<Self>::default()
5979 .filter(col, val)
5980 .first(pool)
5981 .await
5982 }
5983
5984 #value_method
5985
5986 pub async fn latest(
6000 field: &str,
6001 pool: &#root::sql::Pool,
6002 ) -> ::core::result::Result<
6003 ::core::option::Option<Self>,
6004 #root::sql::ExecError,
6005 > {
6006 #root::query::QuerySet::<Self>::default()
6007 .latest(field, pool)
6008 .await
6009 }
6010
6011 pub async fn earliest(
6017 field: &str,
6018 pool: &#root::sql::Pool,
6019 ) -> ::core::result::Result<
6020 ::core::option::Option<Self>,
6021 #root::sql::ExecError,
6022 > {
6023 #root::query::QuerySet::<Self>::default()
6024 .earliest(field, pool)
6025 .await
6026 }
6027
6028 #count_method
6029
6030 pub async fn exists(
6039 pool: &#root::sql::Pool,
6040 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
6041 use #root::sql::ExistsPool as _;
6042 #root::query::QuerySet::<Self>::default()
6043 .exists(pool)
6044 .await
6045 }
6046
6047 pub async fn doesnt_exist(
6054 pool: &#root::sql::Pool,
6055 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
6056 Self::exists(pool).await.map(|e| !e)
6057 }
6058
6059 pub async fn contains_pk(
6073 pk: impl ::core::convert::Into<#root::core::SqlValue> + ::core::marker::Send,
6074 pool: &#root::sql::Pool,
6075 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
6076 use #root::sql::ExistsPool as _;
6077 #root::query::QuerySet::<Self>::default()
6078 .contains_pk(pool, pk)
6079 .await
6080 }
6081
6082 #sum_method
6083 #avg_method
6084 #min_method
6085 #max_method
6086
6087 pub async fn all(
6103 pool: &#root::sql::Pool,
6104 ) -> ::core::result::Result<::std::vec::Vec<Self>, #root::sql::ExecError>
6105 {
6106 use #root::sql::FetcherPool as _;
6107 #root::query::QuerySet::<Self>::default()
6108 .fetch(pool)
6109 .await
6110 }
6111
6112 pub async fn find_many<V>(
6135 pks: impl ::core::iter::IntoIterator<Item = V>,
6136 pool: &#root::sql::Pool,
6137 ) -> ::core::result::Result<
6138 ::std::vec::Vec<Self>,
6139 #root::sql::ExecError,
6140 >
6141 where
6142 V: ::core::convert::Into<#root::core::SqlValue>,
6143 {
6144 use #root::sql::FetcherPool as _;
6145 let _values: ::std::vec::Vec<#root::core::SqlValue> =
6146 pks.into_iter().map(::core::convert::Into::into).collect();
6147 if _values.is_empty() {
6148 return ::core::result::Result::Ok(::std::vec::Vec::new());
6149 }
6150 let _key = ::std::format!("{}__in", ::core::stringify!(#pk_ident));
6151 let _batches = #root::sql::__rustango_in_batches::<Self>(pool, _values)?;
6152 let _split = _batches.len() > 1;
6153 let mut _rows = ::std::vec::Vec::new();
6154 for _keys in _batches {
6155 _rows.extend(
6156 #root::query::QuerySet::<Self>::default()
6157 .filter(&_key, #root::core::SqlValue::List(_keys))
6158 .fetch(pool)
6159 .await?,
6160 );
6161 }
6162 if _split {
6163 _rows = #root::sql::__rustango_dedupe_by_pk(_rows);
6164 }
6165 ::core::result::Result::Ok(_rows)
6166 }
6167
6168 pub async fn find(
6183 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6184 pool: &#root::sql::Pool,
6185 ) -> ::core::result::Result<::core::option::Option<Self>, #root::sql::ExecError>
6186 {
6187 use #root::sql::FetcherPool as _;
6188 let _pk_val: #root::core::SqlValue = pk.into();
6189 let mut _rows: ::std::vec::Vec<Self> =
6190 #root::query::QuerySet::<Self>::default()
6191 .filter(::core::stringify!(#pk_ident), _pk_val)
6192 .limit(1)
6193 .fetch(pool)
6194 .await?;
6195 ::core::result::Result::Ok(_rows.into_iter().next())
6196 }
6197
6198 pub async fn find_or_fail(
6215 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6216 pool: &#root::sql::Pool,
6217 ) -> ::core::result::Result<Self, #root::sql::ExecError> {
6218 match Self::find(pk, pool).await? {
6219 ::core::option::Option::Some(_row) => ::core::result::Result::Ok(_row),
6220 ::core::option::Option::None => ::core::result::Result::Err(
6221 #root::sql::ExecError::Driver(
6222 #root::sql::sqlx::Error::RowNotFound,
6223 ),
6224 ),
6225 }
6226 }
6227
6228 pub async fn find_many_or_fail<V>(
6248 pks: impl ::core::iter::IntoIterator<Item = V>,
6249 pool: &#root::sql::Pool,
6250 ) -> ::core::result::Result<
6251 ::std::vec::Vec<Self>,
6252 #root::sql::ExecError,
6253 >
6254 where
6255 V: ::core::convert::Into<#root::core::SqlValue>,
6256 {
6257 let _values: ::std::vec::Vec<#root::core::SqlValue> =
6258 pks.into_iter().map(::core::convert::Into::into).collect();
6259 if _values.is_empty() {
6260 return ::core::result::Result::Ok(::std::vec::Vec::new());
6261 }
6262 use #root::sql::ExistsPool as _;
6263 let mut _seen: ::std::collections::HashSet<
6264 ::std::string::String,
6265 > = ::std::collections::HashSet::new();
6266 let mut _keys: ::std::vec::Vec<#root::core::SqlValue> =
6267 ::std::vec::Vec::new();
6268 for v in &_values {
6269 if _seen.insert(v.to_display_string()) {
6270 _keys.push(::core::clone::Clone::clone(v));
6271 }
6272 }
6273 let _rows = Self::find_many(_values, pool).await?;
6274 let _found: ::std::collections::HashSet<::std::string::String> = _rows
6275 .iter()
6276 .map(|r| r.__rustango_pk_value().to_display_string())
6277 .collect();
6278 for _key in _keys {
6281 if _found.contains(&_key.to_display_string()) {
6282 continue;
6283 }
6284 let _hit = #root::query::QuerySet::<Self>::default()
6285 .filter(::core::stringify!(#pk_ident), _key)
6286 .exists(pool)
6287 .await?;
6288 if !_hit {
6289 return ::core::result::Result::Err(
6290 #root::sql::ExecError::Driver(
6291 #root::sql::sqlx::Error::RowNotFound,
6292 ),
6293 );
6294 }
6295 }
6296 ::core::result::Result::Ok(_rows)
6297 }
6298
6299 pub async fn find_or<F>(
6314 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6315 pool: &#root::sql::Pool,
6316 fallback: F,
6317 ) -> ::core::result::Result<Self, #root::sql::ExecError>
6318 where
6319 F: ::core::ops::FnOnce() -> Self,
6320 {
6321 ::core::result::Result::Ok(
6322 Self::find(pk, pool).await?.unwrap_or_else(fallback),
6323 )
6324 }
6325
6326 pub async fn find_or_new<F>(
6340 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6341 pool: &#root::sql::Pool,
6342 fallback: F,
6343 ) -> ::core::result::Result<(Self, bool), #root::sql::ExecError>
6344 where
6345 F: ::core::ops::FnOnce() -> Self,
6346 {
6347 match Self::find(pk, pool).await? {
6348 ::core::option::Option::Some(_row) => ::core::result::Result::Ok((_row, true)),
6349 ::core::option::Option::None => {
6350 ::core::result::Result::Ok((fallback(), false))
6351 }
6352 }
6353 }
6354
6355 pub async fn find_or_insert<F>(
6379 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6380 pool: &#root::sql::Pool,
6381 fallback: F,
6382 ) -> ::core::result::Result<(Self, bool), #root::sql::ExecError>
6383 where
6384 F: ::core::ops::FnOnce() -> Self,
6385 {
6386 if let ::core::option::Option::Some(_row) = Self::find(pk, pool).await? {
6387 return ::core::result::Result::Ok((_row, true));
6388 }
6389 let mut _new = fallback();
6390 _new.save(pool).await?;
6391 ::core::result::Result::Ok((_new, false))
6392 }
6393
6394 pub async fn first_or<F>(
6401 pool: &#root::sql::Pool,
6402 fallback: F,
6403 ) -> ::core::result::Result<Self, #root::sql::ExecError>
6404 where
6405 F: ::core::ops::FnOnce() -> Self,
6406 {
6407 ::core::result::Result::Ok(
6411 #root::query::QuerySet::<Self>::default()
6412 .first(pool)
6413 .await?
6414 .unwrap_or_else(fallback),
6415 )
6416 }
6417
6418 pub async fn sole(
6428 col: &str,
6429 val: impl ::core::convert::Into<#root::core::SqlValue>,
6430 pool: &#root::sql::Pool,
6431 ) -> ::core::result::Result<Self, #root::sql::ExecError> {
6432 let mut _rows = Self::where_(col, val, pool).await?;
6433 match _rows.len() {
6434 0 => ::core::result::Result::Err(
6435 #root::sql::ExecError::Driver(
6436 #root::sql::sqlx::Error::RowNotFound,
6437 ),
6438 ),
6439 1 => ::core::result::Result::Ok(_rows.remove(0)),
6440 n => ::core::result::Result::Err(
6441 #root::sql::ExecError::MultipleRowsReturned {
6442 op: "sole",
6443 table: <Self as #root::core::Model>::SCHEMA.name,
6444 count: n,
6445 },
6446 ),
6447 }
6448 }
6449 }
6450 } else {
6451 quote!()
6452 };
6453
6454 let tx_emit = |op: TokenStream2| -> TokenStream2 {
6457 if audited_fields.is_some() && primary_key.is_some() {
6458 quote! {
6459 #root::audit::__emit_many_tx(
6460 tx,
6461 &[self.__rustango_audit_entry(#root::audit::AuditOp::#op)],
6462 )
6463 .await?;
6464 }
6465 } else {
6466 quote!()
6467 }
6468 };
6469 let tx_insert_method = {
6470 let self_ref = if fields.has_auto {
6471 quote!(&mut self)
6472 } else {
6473 quote!(&self)
6474 };
6475 let emit = tx_emit(quote!(Create));
6476 let refuse_pkless = if audited_fields.is_some() && primary_key.is_none() {
6477 quote! {
6478 return ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
6479 table: <Self as #root::core::Model>::SCHEMA.table,
6480 });
6481 }
6482 } else {
6483 quote!()
6484 };
6485 quote! {
6486 #[allow(unreachable_code)]
6492 pub async fn insert_tx(
6493 #self_ref,
6494 tx: &mut #root::sql::PoolTx<'_>,
6495 ) -> ::core::result::Result<(), #root::sql::ExecError> {
6496 #refuse_pkless
6497 self.__rustango_insert_exec(&mut *tx).await?;
6498 #emit
6499 ::core::result::Result::Ok(())
6500 }
6501 }
6502 };
6503 let tx_save_method = if let Some((pk_ident, _)) = primary_key {
6504 let emit_create = tx_emit(quote!(Create));
6505 let emit_update = tx_emit(quote!(Update));
6506 let was_unset = if fields.pk_is_auto {
6507 quote!(matches!(self.#pk_ident, #root::sql::Auto::Unset))
6508 } else {
6509 quote!(false)
6510 };
6511 quote! {
6512 pub async fn save_tx(
6518 &mut self,
6519 tx: &mut #root::sql::PoolTx<'_>,
6520 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6521 let _was_unset = #was_unset;
6522 let _n = self.__rustango_save_exec(&mut *tx).await?;
6523 if _n > 0 {
6524 if _was_unset {
6525 #emit_create
6526 } else {
6527 #emit_update
6528 }
6529 }
6530 ::core::result::Result::Ok(_n)
6531 }
6532 }
6533 } else {
6534 quote!()
6535 };
6536 let tx_delete_method = if primary_key.is_some() {
6537 let emit = tx_emit(quote!(Delete));
6538 quote! {
6539 pub async fn delete_tx(
6545 &self,
6546 tx: &mut #root::sql::PoolTx<'_>,
6547 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6548 let _n = self.__rustango_delete_exec(&mut *tx).await?;
6549 if _n > 0 {
6550 #emit
6551 }
6552 ::core::result::Result::Ok(_n)
6553 }
6554 }
6555 } else {
6556 quote!()
6557 };
6558
6559 let refuse = |method: &str| -> TokenStream2 {
6563 let reason = format!("`{method}` cannot audit; use the `&Pool` method");
6564 quote!(#root::sql::__macro_internals::refuse_audited::<Self>(#reason)?;)
6565 };
6566 let refuse_save = refuse("save_on");
6567 let save_method = if let Some((pk_ident, pk_column)) = primary_key {
6568 let pk_column_lit = pk_column.as_str();
6569 let assignments = &fields.update_assignments;
6570 let dispatch_unset = if fields.pk_is_auto {
6571 quote! {
6572 if matches!(self.#pk_ident, #root::sql::Auto::Unset) {
6573 return self.__rustango_insert_exec(_executor).await.map(|()| 1u64);
6574 }
6575 }
6576 } else {
6577 quote!()
6578 };
6579 let write = restore_auto_now_unless(
6580 &fields.auto_now_idents,
6581 &save_ret,
6582 &save_keep,
6583 "e! {
6584 let _query = #root::core::UpdateQuery::new(
6585 <Self as #root::core::Model>::SCHEMA,
6586 ::std::vec![ #( #assignments ),* ],
6587 #root::core::WhereExpr::Predicate(#root::core::Filter::new(
6588 #pk_column_lit,
6589 #root::core::Op::Eq,
6590 ::core::convert::Into::<#root::core::SqlValue>::into(
6591 ::core::clone::Clone::clone(&self.#pk_ident)
6592 ),
6593 )),
6594 );
6595 #root::sql::__macro_internals::update_on(_executor, &_query)
6596 .await
6597 .and_then(#root::sql::__macro_internals::row_saved)
6598 },
6599 );
6600 let upsert = if fields.pk_is_auto {
6601 let upsert_cols = &fields.upsert_update_columns;
6602 let upsert_pushes = &fields.insert_pushes;
6603 let (gen_idents, gen_cols): (Vec<_>, Vec<_>) =
6604 fields.uuid_v7_fields.iter().cloned().unzip();
6605 let upsert_returning = &fields.returning_cols;
6606 let auto_now_idents = &fields.auto_now_idents;
6607 let upsert_target_columns: Vec<String> =
6610 upsert_unique.map_or_else(|| vec![pk_column.clone()], <[String]>::to_vec);
6611 let upsert_target_lits = upsert_target_columns
6612 .iter()
6613 .map(String::as_str)
6614 .collect::<Vec<_>>();
6615 let conflict_clause = if fields.upsert_update_columns.is_empty() {
6616 quote!(#root::core::ConflictClause::DoNothing)
6617 } else {
6618 quote!(#root::core::ConflictClause::DoUpdate {
6619 target: ::std::vec![ #( #upsert_target_lits ),* ],
6620 update_columns: ::std::vec![ #( #upsert_cols ),* ],
6621 })
6622 };
6623 let unit_ret = quote!(::core::result::Result<(), #root::sql::ExecError>);
6624 let unit_keep = quote!(_saved.is_ok());
6625 let exec_body = restore_auto_now_unless(
6626 auto_now_idents,
6627 &unit_ret,
6628 &unit_keep,
6629 "e! {
6630 let _query = self.__rustango_upsert_query();
6631 let _row = #root::sql::__macro_internals::insert_returning_on(
6632 _executor, _query,
6633 )
6634 .await?;
6635 #root::sql::apply_auto_pk(_row, self)
6636 },
6637 );
6638 let pool_body = if audited_fields.is_some() {
6639 restore_auto_now_unless(
6640 auto_now_idents,
6641 &unit_ret,
6642 &unit_keep,
6643 "e! {
6644 let _query = self.__rustango_upsert_query();
6645 #root::audit::insert_one_with_audit(pool, &_query, self, |_m: &Self, _op| {
6646 _m.__rustango_audit_entry(_op)
6647 })
6648 .await
6649 },
6650 )
6651 } else {
6652 quote!(self.__rustango_upsert_exec(pool).await)
6653 };
6654 let refuse_upsert = refuse("upsert_on");
6655 quote! {
6656 pub async fn upsert(
6664 &mut self,
6665 pool: &#root::sql::Pool,
6666 ) -> ::core::result::Result<(), #root::sql::ExecError> {
6667 #pool_body
6668 }
6669
6670 pub async fn upsert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6676 &mut self,
6677 _executor: _E,
6678 ) -> ::core::result::Result<(), #root::sql::ExecError> {
6679 #refuse_upsert
6680 self.__rustango_upsert_exec(_executor).await
6681 }
6682
6683 #[doc(hidden)]
6684 pub async fn __rustango_upsert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6685 &mut self,
6686 _executor: _E,
6687 ) -> ::core::result::Result<(), #root::sql::ExecError> {
6688 #exec_body
6689 }
6690
6691 #[doc(hidden)]
6693 pub fn __rustango_upsert_query(&mut self) -> #root::core::InsertQuery {
6694 let mut _columns: ::std::vec::Vec<&'static str> = ::std::vec::Vec::new();
6695 let mut _values: ::std::vec::Vec<#root::core::SqlValue> =
6696 ::std::vec::Vec::new();
6697 #[allow(unused_mut)]
6698 let mut _generated: ::std::vec::Vec<&'static str> = ::std::vec::Vec::new();
6699 #(
6700 if matches!(&self.#gen_idents, #root::sql::Auto::Unset) {
6701 _generated.push(#gen_cols);
6702 }
6703 )*
6704 #( self.#auto_now_idents = #root::sql::Auto::Set(#root::__chrono::Utc::now()); )*
6705 #( #upsert_pushes )*
6706 #root::core::InsertQuery::new(
6707 <Self as #root::core::Model>::SCHEMA,
6708 _columns,
6709 _values,
6710 )
6711 .returning(::std::vec![ #( #upsert_returning ),* ])
6712 .on_conflict(#conflict_clause)
6713 .generated(_generated)
6714 }
6715 }
6716 } else {
6717 quote!()
6718 };
6719 Some(quote! {
6720 pub async fn save_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6727 &mut self,
6728 _executor: _E,
6729 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6730 #refuse_save
6731 self.__rustango_save_exec(_executor).await
6732 }
6733
6734 #[doc(hidden)]
6735 pub async fn __rustango_save_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6736 &mut self,
6737 _executor: _E,
6738 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6739 #dispatch_unset
6740 #write
6741 }
6742
6743 #[deprecated(since = "0.62.0", note = "use `save`")]
6748 pub async fn save_pool(
6749 &mut self,
6750 pool: &#root::sql::Pool,
6751 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6752 self.save(pool).await
6753 }
6754
6755 #upsert
6756 })
6757 } else {
6758 None
6759 };
6760
6761 let pk_methods = primary_key.map(|(pk_ident, pk_column)| {
6762 let pk_column_lit = pk_column.as_str();
6763 let refuse_delete = refuse("delete_on");
6764 let soft_delete_methods = if let Some(col) = fields.soft_delete_column.as_deref() {
6771 let col_lit = col;
6772 let sd_field_ident = fields
6773 .soft_delete_field_ident
6774 .clone()
6775 .expect("soft_delete_column without ident");
6776 let refuse_soft_delete = refuse("soft_delete_on");
6777 let refuse_restore = refuse("restore_on");
6778 let mark_deleted = quote! {
6780 if _n > 0 {
6781 self.#sd_field_ident = ::core::option::Option::Some(_now);
6782 }
6783 };
6784 let mark_restored = quote! {
6785 if _n > 0 {
6786 self.#sd_field_ident = ::core::option::Option::None;
6787 }
6788 };
6789 quote! {
6790 pub async fn soft_delete_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6797 &mut self,
6798 _executor: _E,
6799 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6800 #refuse_soft_delete
6801 let _now = #root::__chrono::Utc::now();
6802 let _query = #root::soft_delete::__mark_query(
6803 <Self as #root::core::Model>::SCHEMA,
6804 #col_lit,
6805 #pk_column_lit,
6806 ::core::convert::Into::<#root::core::SqlValue>::into(
6807 ::core::clone::Clone::clone(&self.#pk_ident)
6808 ),
6809 ::core::option::Option::Some(_now),
6810 );
6811 let _n = #root::sql::__macro_internals::update_on(_executor, &_query).await?;
6812 #mark_deleted
6813 ::core::result::Result::Ok(_n)
6814 }
6815
6816 pub async fn restore_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6822 &mut self,
6823 _executor: _E,
6824 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6825 #refuse_restore
6826 let _query = #root::soft_delete::__mark_query(
6827 <Self as #root::core::Model>::SCHEMA,
6828 #col_lit,
6829 #pk_column_lit,
6830 ::core::convert::Into::<#root::core::SqlValue>::into(
6831 ::core::clone::Clone::clone(&self.#pk_ident)
6832 ),
6833 ::core::option::Option::None,
6834 );
6835 let _n = #root::sql::__macro_internals::update_on(_executor, &_query).await?;
6836 #mark_restored
6837 ::core::result::Result::Ok(_n)
6838 }
6839
6840 pub async fn soft_delete(
6855 &mut self,
6856 pool: &#root::sql::Pool,
6857 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6858 let _now = #root::__chrono::Utc::now();
6859 let _query = #root::soft_delete::__mark_query(
6860 <Self as #root::core::Model>::SCHEMA,
6861 #col_lit,
6862 #pk_column_lit,
6863 ::core::convert::Into::<#root::core::SqlValue>::into(
6864 ::core::clone::Clone::clone(&self.#pk_ident)
6865 ),
6866 ::core::option::Option::Some(_now),
6867 );
6868 let _n = { #pool_softdelete_run }?;
6869 #mark_deleted
6870 ::core::result::Result::Ok(_n)
6871 }
6872
6873 pub async fn restore(
6883 &mut self,
6884 pool: &#root::sql::Pool,
6885 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6886 let _query = #root::soft_delete::__mark_query(
6887 <Self as #root::core::Model>::SCHEMA,
6888 #col_lit,
6889 #pk_column_lit,
6890 ::core::convert::Into::<#root::core::SqlValue>::into(
6891 ::core::clone::Clone::clone(&self.#pk_ident)
6892 ),
6893 ::core::option::Option::None,
6894 );
6895 let _n = { #pool_restore_run }?;
6896 #mark_restored
6897 ::core::result::Result::Ok(_n)
6898 }
6899
6900 pub async fn force_delete(
6914 &self,
6915 pool: &#root::sql::Pool,
6916 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6917 Self::delete(self, pool).await
6918 }
6919
6920 pub fn trashed(&self) -> bool {
6929 ::core::option::Option::is_some(&self.#sd_field_ident)
6930 }
6931
6932 pub async fn active(
6945 pool: &#root::sql::Pool,
6946 ) -> ::core::result::Result<
6947 ::std::vec::Vec<Self>,
6948 #root::sql::ExecError,
6949 > {
6950 use #root::sql::FetcherPool as _;
6951 #root::query::QuerySet::<Self>::default()
6952 .active()
6953 .fetch(pool)
6954 .await
6955 }
6956
6957 pub async fn only_trashed(
6969 pool: &#root::sql::Pool,
6970 ) -> ::core::result::Result<
6971 ::std::vec::Vec<Self>,
6972 #root::sql::ExecError,
6973 > {
6974 use #root::sql::FetcherPool as _;
6975 #root::query::QuerySet::<Self>::default()
6976 .only_trashed()
6977 .fetch(pool)
6978 .await
6979 }
6980
6981 pub async fn with_trashed(
6997 pool: &#root::sql::Pool,
6998 ) -> ::core::result::Result<
6999 ::std::vec::Vec<Self>,
7000 #root::sql::ExecError,
7001 > {
7002 use #root::sql::FetcherPool as _;
7003 #root::query::QuerySet::<Self>::default()
7004 .with_trashed()
7005 .fetch(pool)
7006 .await
7007 }
7008
7009 }
7010 } else {
7011 quote!()
7012 };
7013 quote! {
7014
7015
7016 pub async fn delete_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7022 &self,
7023 _executor: _E,
7024 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
7025 #refuse_delete
7026 self.__rustango_delete_exec(_executor).await
7027 }
7028
7029 #[doc(hidden)]
7030 pub async fn __rustango_delete_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7031 &self,
7032 _executor: _E,
7033 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
7034 let _query = #root::core::DeleteQuery::by_pk(
7035 <Self as #root::core::Model>::SCHEMA,
7036 #pk_column_lit,
7037 ::core::convert::Into::<#root::core::SqlValue>::into(
7038 ::core::clone::Clone::clone(&self.#pk_ident)
7039 ),
7040 );
7041 #root::sql::__macro_internals::delete_on(_executor, &_query).await
7042 }
7043
7044 #[deprecated(since = "0.62.0", note = "use `delete`")]
7049 pub async fn delete_pool(
7050 &self,
7051 pool: &#root::sql::Pool,
7052 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
7053 self.delete(pool).await
7054 }
7055
7056
7057 #pool_delete_method
7058 #pool_insert_method
7059 #audit_entry_method
7060 #pool_save_method
7061 #refresh_replicate_methods
7062 #tx_delete_method
7063 #tx_insert_method
7064 #tx_save_method
7065 #soft_delete_methods
7066
7067 pub fn is(&self, other: &Self) -> bool {
7076 self.#pk_ident == other.#pk_ident
7077 }
7078
7079 pub fn is_not(&self, other: &Self) -> bool {
7082 self.#pk_ident != other.#pk_ident
7083 }
7084
7085 #[must_use]
7093 pub fn get_key(&self) -> #root::core::SqlValue {
7094 ::core::convert::Into::into(::core::clone::Clone::clone(&self.#pk_ident))
7095 }
7096
7097 }
7098 });
7099
7100 let refuse_insert = refuse("insert_on");
7101 let audited_insert = audited_fields.is_some() && primary_key.is_some();
7104 let insert_pool_body = if audited_fields.is_some() {
7105 quote! {
7106 ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
7107 table: <Self as #root::core::Model>::SCHEMA.table,
7108 })
7109 }
7110 } else {
7111 quote!(self.__rustango_insert_exec(pool).await)
7112 };
7113 let plain_insert_auto = if audited_insert {
7114 quote!()
7115 } else {
7116 quote! {
7117 pub async fn insert(
7123 &mut self,
7124 pool: &#root::sql::Pool,
7125 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7126 #insert_pool_body
7127 }
7128 }
7129 };
7130 let plain_insert_plain = if audited_insert {
7131 quote!()
7132 } else {
7133 quote! {
7134 pub async fn insert(
7139 &self,
7140 pool: &#root::sql::Pool,
7141 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7142 #insert_pool_body
7143 }
7144 }
7145 };
7146 let insert_method = if fields.has_auto {
7147 let pushes = &fields.insert_pushes;
7148 let returning_cols = &fields.returning_cols;
7149 quote! {
7150 #plain_insert_auto
7151 pub async fn insert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7157 &mut self,
7158 _executor: _E,
7159 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7160 #refuse_insert
7161 self.__rustango_insert_exec(_executor).await
7162 }
7163
7164 #[doc(hidden)]
7165 pub async fn __rustango_insert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7166 &mut self,
7167 _executor: _E,
7168 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7169 let _query = self.__rustango_insert_query();
7170 if _query.returning.is_empty() {
7172 return #root::sql::__macro_internals::insert_owned_on(_executor, _query).await;
7173 }
7174 let _row = #root::sql::__macro_internals::insert_returning_on(
7175 _executor, _query,
7176 )
7177 .await?;
7178 #root::sql::apply_auto_pk(_row, self)
7179 }
7180
7181 #[doc(hidden)]
7183 pub fn __rustango_insert_query(&mut self) -> #root::core::InsertQuery {
7184 let mut _columns: ::std::vec::Vec<&'static str> = ::std::vec::Vec::new();
7185 let mut _values: ::std::vec::Vec<#root::core::SqlValue> = ::std::vec::Vec::new();
7186 #( #pushes )*
7187 #root::core::InsertQuery::new(
7188 <Self as #root::core::Model>::SCHEMA,
7189 _columns,
7190 _values,
7191 )
7192 .returning(::std::vec![ #( #returning_cols ),* ])
7193 }
7194
7195 #[deprecated(since = "0.62.0", note = "use `insert`")]
7200 pub async fn insert_pool(
7201 &mut self,
7202 pool: &#root::sql::Pool,
7203 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7204 self.insert(pool).await
7205 }
7206 }
7207 } else {
7208 let insert_columns = &fields.insert_columns;
7209 let insert_values = &fields.insert_values;
7210 let pool_self = if audited_fields.is_some() {
7212 quote!(&mut self)
7213 } else {
7214 quote!(&self)
7215 };
7216 quote! {
7217 #plain_insert_plain
7218 pub async fn insert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7224 &self,
7225 _executor: _E,
7226 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7227 #refuse_insert
7228 self.__rustango_insert_exec(_executor).await
7229 }
7230
7231 #[doc(hidden)]
7232 pub async fn __rustango_insert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7233 &self,
7234 _executor: _E,
7235 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7236 #root::sql::__macro_internals::insert_owned_on(
7237 _executor,
7238 self.__rustango_insert_query(),
7239 )
7240 .await
7241 }
7242
7243 #[doc(hidden)]
7245 pub fn __rustango_insert_query(&self) -> #root::core::InsertQuery {
7246 #root::core::InsertQuery::new(
7247 <Self as #root::core::Model>::SCHEMA,
7248 ::std::vec![ #( #insert_columns ),* ],
7249 ::std::vec![ #( #insert_values ),* ],
7250 )
7251 }
7252
7253 #[deprecated(since = "0.62.0", note = "use `insert`")]
7258 pub async fn insert_pool(
7259 #pool_self,
7260 pool: &#root::sql::Pool,
7261 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7262 self.insert(pool).await
7263 }
7264 }
7265 };
7266
7267 let bulk_pool_body = if audited_fields.is_some() {
7270 quote! {
7271 let mut _tx = #root::sql::__macro_internals::write_tx(pool).await?;
7272 Self::__rustango_bulk_insert_exec(rows, &mut *_tx).await?;
7273 if rows.iter().any(|_row| _row.__rustango_audit_pk().is_empty()) {
7275 return ::core::result::Result::Err(#root::sql::ExecError::AuditUnsupported {
7276 table: <Self as #root::core::Model>::SCHEMA.table,
7277 reason: "MySQL has no RETURNING to read the written rows",
7278 });
7279 }
7280 let _entries: ::std::vec::Vec<#root::audit::PendingEntry> = rows
7281 .iter()
7282 .map(|_row| _row.__rustango_audit_entry(#root::audit::AuditOp::Create))
7283 .collect();
7284 #root::audit::__emit_many_tx(&mut _tx, &_entries).await?;
7285 _tx.commit().await?;
7286 ::core::result::Result::Ok(())
7287 }
7288 } else {
7289 quote!(Self::__rustango_bulk_insert_exec(rows, pool).await)
7290 };
7291 let refuse_bulk = refuse("bulk_insert_on");
7292 let bulk_insert_method = if fields.has_auto {
7293 let cols_no_auto = &fields.bulk_columns_no_auto;
7294 let cols_all = &fields.bulk_columns_all;
7295 let pushes_no_auto = &fields.bulk_pushes_no_auto;
7296 let pushes_all = &fields.bulk_pushes_all;
7297 let returning_cols = &fields.returning_cols;
7298 let uniformity = &fields.bulk_auto_uniformity;
7299 let fill_in_place = &fields.bulk_fill_in_place;
7300 let first_auto_ident = fields
7302 .first_db_auto
7303 .as_ref()
7304 .map(|(ident, _)| ident)
7305 .or(fields.first_auto_ident.as_ref())
7306 .expect("has_auto implies first_auto_ident is Some");
7307 quote! {
7308 pub async fn bulk_insert(
7318 rows: &mut [Self],
7319 pool: &#root::sql::Pool,
7320 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7321 #bulk_pool_body
7322 }
7323
7324 pub async fn bulk_insert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7330 rows: &mut [Self],
7331 _executor: _E,
7332 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7333 #refuse_bulk
7334 Self::__rustango_bulk_insert_exec(rows, _executor).await
7335 }
7336
7337 #[doc(hidden)]
7338 pub async fn __rustango_bulk_insert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7339 rows: &mut [Self],
7340 _executor: _E,
7341 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7342 if rows.is_empty() {
7343 return ::core::result::Result::Ok(());
7344 }
7345 for _row in rows.iter_mut() {
7346 #( #fill_in_place )*
7347 }
7348 let (_columns, _all_rows) = Self::__rustango_bulk_rows(rows)?;
7349 let _query = #root::core::BulkInsertQuery::new(
7350 <Self as #root::core::Model>::SCHEMA,
7351 _columns,
7352 _all_rows,
7353 )
7354 .returning(::std::vec![ #( #returning_cols ),* ]);
7355 let _returned =
7356 #root::sql::__macro_internals::bulk_insert_owned_on(_executor, _query).await?;
7357 if _returned.is_empty() {
7358 return ::core::result::Result::Ok(());
7359 }
7360 if _returned.len() != rows.len() {
7361 return ::core::result::Result::Err(#root::sql::ExecError::Sql(
7362 #root::sql::SqlError::BulkInsertReturningMismatch {
7363 expected: rows.len(),
7364 actual: _returned.len(),
7365 },
7366 ));
7367 }
7368 for (_row, _row_mut) in _returned.into_iter().zip(rows.iter_mut()) {
7369 #root::sql::apply_auto_pk(_row, _row_mut)?;
7370 }
7371 ::core::result::Result::Ok(())
7372 }
7373
7374 #[doc(hidden)]
7378 pub fn __rustango_bulk_rows(
7379 rows: &[Self],
7380 ) -> ::core::result::Result<
7381 (
7382 ::std::vec::Vec<&'static str>,
7383 ::std::vec::Vec<::std::vec::Vec<#root::core::SqlValue>>,
7384 ),
7385 #root::sql::ExecError,
7386 > {
7387 let _first_unset = match rows.first() {
7388 ::core::option::Option::Some(_r) => {
7389 matches!(_r.#first_auto_ident, #root::sql::Auto::Unset)
7390 }
7391 ::core::option::Option::None => true,
7392 };
7393 #( #uniformity )*
7394 let mut _all_rows: ::std::vec::Vec<
7395 ::std::vec::Vec<#root::core::SqlValue>,
7396 > = ::std::vec::Vec::with_capacity(rows.len());
7397 let _columns: ::std::vec::Vec<&'static str> = if _first_unset {
7398 for _row in rows.iter() {
7399 let mut _row_vals: ::std::vec::Vec<#root::core::SqlValue> =
7400 ::std::vec::Vec::new();
7401 #( #pushes_no_auto )*
7402 _all_rows.push(_row_vals);
7403 }
7404 ::std::vec![ #( #cols_no_auto ),* ]
7405 } else {
7406 for _row in rows.iter() {
7407 let mut _row_vals: ::std::vec::Vec<#root::core::SqlValue> =
7408 ::std::vec::Vec::new();
7409 #( #pushes_all )*
7410 _all_rows.push(_row_vals);
7411 }
7412 ::std::vec![ #( #cols_all ),* ]
7413 };
7414 ::core::result::Result::Ok((_columns, _all_rows))
7415 }
7416 }
7417 } else {
7418 let cols_all = &fields.bulk_columns_all;
7419 let pushes_all = &fields.bulk_pushes_all;
7420 quote! {
7421 pub async fn bulk_insert(
7428 rows: &[Self],
7429 pool: &#root::sql::Pool,
7430 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7431 #bulk_pool_body
7432 }
7433
7434 pub async fn bulk_insert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7440 rows: &[Self],
7441 _executor: _E,
7442 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7443 #refuse_bulk
7444 Self::__rustango_bulk_insert_exec(rows, _executor).await
7445 }
7446
7447 #[doc(hidden)]
7448 pub async fn __rustango_bulk_insert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7449 rows: &[Self],
7450 _executor: _E,
7451 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7452 if rows.is_empty() {
7453 return ::core::result::Result::Ok(());
7454 }
7455 let (_columns, _all_rows) = Self::__rustango_bulk_rows(rows)?;
7456 let _query = #root::core::BulkInsertQuery::new(
7457 <Self as #root::core::Model>::SCHEMA,
7458 _columns,
7459 _all_rows,
7460 );
7461 #root::sql::__macro_internals::bulk_insert_owned_on(_executor, _query)
7462 .await
7463 .map(drop)
7464 }
7465
7466 #[doc(hidden)]
7468 pub fn __rustango_bulk_rows(
7469 rows: &[Self],
7470 ) -> ::core::result::Result<
7471 (
7472 ::std::vec::Vec<&'static str>,
7473 ::std::vec::Vec<::std::vec::Vec<#root::core::SqlValue>>,
7474 ),
7475 #root::sql::ExecError,
7476 > {
7477 let mut _all_rows: ::std::vec::Vec<
7478 ::std::vec::Vec<#root::core::SqlValue>,
7479 > = ::std::vec::Vec::with_capacity(rows.len());
7480 for _row in rows.iter() {
7481 let mut _row_vals: ::std::vec::Vec<#root::core::SqlValue> =
7482 ::std::vec::Vec::new();
7483 #( #pushes_all )*
7484 _all_rows.push(_row_vals);
7485 }
7486 ::core::result::Result::Ok((::std::vec![ #( #cols_all ),* ], _all_rows))
7487 }
7488 }
7489 };
7490
7491 let run_conflict_bulk_insert = match (audited_fields.is_some(), primary_key.is_some()) {
7498 (false, _) => quote!(#root::sql::bulk_insert_pool(pool, &_query).await),
7499 (true, true) => quote! {
7500 #root::audit::bulk_insert_with_audit::<Self>(pool, &_query, |_r: &Self, _op| {
7501 _r.__rustango_audit_entry(_op)
7502 })
7503 .await
7504 },
7505 (true, false) => quote! {
7506 ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
7507 table: <Self as #root::core::Model>::SCHEMA.table,
7508 })
7509 },
7510 };
7511 let bulk_upsert_pool_method = {
7512 let (gen_idents, gen_cols): (Vec<_>, Vec<_>) =
7513 fields.uuid_v7_fields.iter().cloned().unzip();
7514 quote! {
7515 pub async fn bulk_upsert_pool(
7544 rows: &[Self],
7545 target: &[&'static str],
7546 update_cols: &[&'static str],
7547 pool: &#root::sql::Pool,
7548 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7549 if rows.is_empty() {
7550 return ::core::result::Result::Ok(());
7551 }
7552 let (_columns, _all_rows) = Self::__rustango_bulk_rows(rows)?;
7553 #[allow(unused_mut)]
7554 let mut _generated: ::std::vec::Vec<&'static str> = ::std::vec::Vec::new();
7555 #(
7556 if rows.iter().all(|_r| matches!(&_r.#gen_idents, #root::sql::Auto::Unset)) {
7557 _generated.push(#gen_cols);
7558 }
7559 )*
7560 let _query = #root::core::BulkInsertQuery::new(
7561 <Self as #root::core::Model>::SCHEMA,
7562 _columns,
7563 _all_rows,
7564 )
7565 .on_conflict_do_update(target, update_cols)
7566 .generated(_generated);
7567 #run_conflict_bulk_insert
7568 }
7569
7570 pub async fn bulk_insert_or_ignore_pool(
7580 rows: &[Self],
7581 pool: &#root::sql::Pool,
7582 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7583 if rows.is_empty() {
7584 return ::core::result::Result::Ok(());
7585 }
7586 let (_columns, _all_rows) = Self::__rustango_bulk_rows(rows)?;
7587 let _query = #root::core::BulkInsertQuery::new(
7588 <Self as #root::core::Model>::SCHEMA,
7589 _columns,
7590 _all_rows,
7591 )
7592 .on_conflict_do_nothing();
7593 #run_conflict_bulk_insert
7594 }
7595 }
7596 };
7597
7598 let bulk_update_rows_run = if audited_fields.is_some() {
7607 quote!(#root::audit::bulk_update_with_audit::<Self>(pool, &_query, |_r: &Self| {
7608 _r.__rustango_audit_entry(#root::audit::AuditOp::Update)
7609 })
7610 .await)
7611 } else {
7612 quote!(#root::sql::bulk_update_pool(pool, &_query).await)
7613 };
7614 let bulk_update_method = match &fields.primary_key {
7615 None => quote! {},
7616 Some((pk_ident, pk_col)) => {
7617 let mut col_arms: Vec<TokenStream2> = Vec::new();
7621 let mut val_arms: Vec<TokenStream2> = Vec::new();
7622 for entry in &fields.column_entries {
7623 if &entry.column == pk_col {
7624 continue;
7625 }
7626 let col = &entry.column;
7627 let ident = &entry.ident;
7628 col_arms.push(quote! { #col => #col, });
7629 val_arms.push(quote! {
7630 #col => _row_vals.push(
7631 ::core::convert::Into::<#root::core::SqlValue>::into(
7632 ::core::clone::Clone::clone(&_o.#ident)
7633 )
7634 ),
7635 });
7636 }
7637 quote! {
7638 pub async fn bulk_update(
7664 objs: &[Self],
7665 fields: &[&str],
7666 pool: &#root::sql::Pool,
7667 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
7668 if objs.is_empty() || fields.is_empty() {
7669 return ::core::result::Result::Ok(0);
7670 }
7671 let _model_name = <Self as #root::core::Model>::SCHEMA.name;
7672 let mut _update_columns: ::std::vec::Vec<&'static str> =
7673 ::std::vec::Vec::with_capacity(fields.len());
7674 for &_f in fields {
7675 let _col: &'static str = match _f {
7676 #pk_col => {
7677 return ::core::result::Result::Err(
7678 ::core::convert::Into::into(
7679 #root::core::QueryError::BulkUpdatePrimaryKey {
7680 model: _model_name,
7681 field: ::std::string::ToString::to_string(_f),
7682 }
7683 )
7684 );
7685 }
7686 #( #col_arms )*
7687 _ => {
7688 return ::core::result::Result::Err(
7689 ::core::convert::Into::into(
7690 #root::core::QueryError::UnknownField {
7691 model: _model_name,
7692 field: ::std::string::ToString::to_string(_f),
7693 }
7694 )
7695 );
7696 }
7697 };
7698 #[allow(unreachable_code)]
7707 _update_columns.push(_col);
7708 }
7709 let mut _rows: ::std::vec::Vec<
7710 ::std::vec::Vec<#root::core::SqlValue>,
7711 > = ::std::vec::Vec::with_capacity(objs.len());
7712 for _o in objs.iter() {
7713 let mut _row_vals: ::std::vec::Vec<#root::core::SqlValue> =
7714 ::std::vec::Vec::with_capacity(fields.len() + 1);
7715 _row_vals.push(
7717 ::core::convert::Into::<#root::core::SqlValue>::into(
7718 ::core::clone::Clone::clone(&_o.#pk_ident)
7719 )
7720 );
7721 for &_f in fields {
7722 match _f {
7723 #( #val_arms )*
7724 _ => {}
7727 }
7728 }
7729 _rows.push(_row_vals);
7730 }
7731 let _query = #root::core::BulkUpdateQuery::new(
7732 <Self as #root::core::Model>::SCHEMA,
7733 _update_columns,
7734 _rows,
7735 );
7736 #bulk_update_rows_run
7737 }
7738 }
7739 }
7740 };
7741
7742 let pk_value_helper = primary_key.map(|(pk_ident, _)| {
7743 quote! {
7744 #[doc(hidden)]
7749 pub fn __rustango_pk_value(&self) -> #root::core::SqlValue {
7750 ::core::convert::Into::<#root::core::SqlValue>::into(
7751 ::core::clone::Clone::clone(&self.#pk_ident)
7752 )
7753 }
7754 }
7755 });
7756
7757 let has_pk_value_impl = primary_key.map(|(pk_ident, _)| {
7758 let column_arms = fields.column_entries.iter().map(|entry| {
7759 let col = &entry.column;
7760 let ident = &entry.ident;
7761 quote! {
7762 #col => ::core::option::Option::Some(
7763 ::core::convert::Into::<#root::core::SqlValue>::into(
7764 ::core::clone::Clone::clone(&self.#ident)
7765 )
7766 ),
7767 }
7768 });
7769 quote! {
7770 impl #root::sql::HasPkValue for #struct_name {
7771 fn __rustango_pk_value_impl(&self) -> #root::core::SqlValue {
7772 ::core::convert::Into::<#root::core::SqlValue>::into(
7773 ::core::clone::Clone::clone(&self.#pk_ident)
7774 )
7775 }
7776 fn __rustango_column_value(
7777 &self,
7778 column: &str,
7779 ) -> ::core::option::Option<#root::core::SqlValue> {
7780 match column {
7781 #( #column_arms )*
7782 _ => ::core::option::Option::None,
7783 }
7784 }
7785 }
7786 }
7787 });
7788
7789 let fk_pk_access_impl = fk_pk_access_impl_tokens(struct_name, &fields.fk_relations);
7790
7791 let assign_auto_pk_pool_impl = {
7797 let auto_assigns = &fields.auto_assigns;
7798 let auto_assigns_sqlite: Vec<TokenStream2> = fields
7804 .auto_field_idents
7805 .iter()
7806 .map(|(ident, column, uuid)| {
7807 if *uuid {
7809 return quote! {
7810 self.#ident = #root::sql::Auto::Set(#root::sql::try_get_flat_sqlite(
7811 _returning_row, #column
7812 )?);
7813 };
7814 }
7815 quote! {
7816 self.#ident = #root::sql::try_get_returning_sqlite(
7817 _returning_row, #column
7818 )?;
7819 }
7820 })
7821 .collect();
7822 let generated_assigns: Vec<TokenStream2> = fields
7826 .generated_field_idents
7827 .iter()
7828 .map(|(ident, column)| {
7829 quote! {
7830 self.#ident = #root::sql::try_get_returning(_returning_row, #column)?;
7831 }
7832 })
7833 .collect();
7834 let generated_assigns_sqlite: Vec<TokenStream2> = fields
7835 .generated_field_idents
7836 .iter()
7837 .map(|(ident, column)| {
7838 quote! {
7839 self.#ident = #root::sql::try_get_returning_sqlite(
7840 _returning_row, #column
7841 )?;
7842 }
7843 })
7844 .collect();
7845 let mysql_body = if let Some((first, value_ty)) = fields.first_db_auto.as_ref() {
7846 quote! {
7864 let _converted = <#value_ty as #root::sql::MysqlAutoIdSet>
7865 ::rustango_from_mysql_auto_id(_id)?;
7866 self.#first = #root::sql::Auto::Set(_converted);
7867 ::core::result::Result::Ok(())
7868 }
7869 } else {
7870 quote! {
7871 let _ = _id;
7872 ::core::result::Result::Ok(())
7873 }
7874 };
7875 quote! {
7876 impl #root::sql::AssignAutoPkPool for #struct_name {
7877 fn __rustango_assign_from_pg_row(
7878 &mut self,
7879 _returning_row: &#root::sql::PgReturningRow,
7880 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7881 #( #auto_assigns )*
7882 #( #generated_assigns )*
7883 ::core::result::Result::Ok(())
7884 }
7885 fn __rustango_assign_from_mysql_id(
7886 &mut self,
7887 _id: i64,
7888 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7889 #mysql_body
7890 }
7891 fn __rustango_assign_from_sqlite_row(
7892 &mut self,
7893 _returning_row: &#root::sql::SqliteReturningRow,
7894 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7895 #( #auto_assigns_sqlite )*
7896 #( #generated_assigns_sqlite )*
7897 ::core::result::Result::Ok(())
7898 }
7899 }
7900 }
7901 };
7902
7903 let from_aliased_row_inits = &fields.from_aliased_row_inits;
7904 let aliased_row_helper = quote! {
7905 #[doc(hidden)]
7911 #[cfg(feature = "postgres")]
7912 pub fn __rustango_from_aliased_row(
7913 row: &#root::sql::sqlx::postgres::PgRow,
7914 prefix: &str,
7915 ) -> ::core::result::Result<Self, #root::sql::sqlx::Error> {
7916 ::core::result::Result::Ok(Self {
7917 #( #from_aliased_row_inits ),*
7918 })
7919 }
7920 };
7921 let from_aliased_row_inits_my = &fields.from_aliased_row_inits_my;
7924 let aliased_row_helper_my = quote! {
7925 #root::__impl_my_aliased_row_decoder!(#struct_name, |row, prefix| {
7926 #( #from_aliased_row_inits_my ),*
7927 });
7928 };
7929
7930 let aliased_row_helper_sqlite = quote! {
7933 #root::__impl_sqlite_aliased_row_decoder!(#struct_name, |row, prefix| {
7934 #( #from_aliased_row_inits ),*
7935 });
7936 };
7937
7938 let load_related_impl = load_related_impl_tokens(struct_name, &fields.fk_relations);
7939 let load_related_impl_my = load_related_impl_my_tokens(struct_name, &fields.fk_relations);
7940 let load_related_impl_sqlite =
7941 load_related_impl_sqlite_tokens(struct_name, &fields.fk_relations);
7942
7943 let extra_manager_fns: Vec<TokenStream2> = manager_fns
7949 .iter()
7950 .map(|fn_ident| {
7951 let model_name_str = struct_name.to_string();
7952 let fn_name_str = fn_ident.to_string();
7953 let doc = format!(
7954 "Custom-named QuerySet accessor for [`{model_name_str}`]. \
7955 Generated by `#[rustango(manager_fn = \"{fn_name_str}\")]` — \
7956 equivalent to `Self::objects()`. Chains with any \
7957 `impl ... for QuerySet<{model_name_str}> {{ ... }}` \
7958 extension methods."
7959 );
7960 quote! {
7961 #[doc = #doc]
7962 #[must_use]
7963 pub fn #fn_ident() -> #root::query::QuerySet<#struct_name> {
7964 #root::query::QuerySet::new()
7965 }
7966 }
7967 })
7968 .collect();
7969
7970 quote! {
7971 impl #struct_name {
7972 #[must_use]
7974 pub fn objects() -> #root::query::QuerySet<#struct_name> {
7975 #root::query::QuerySet::new()
7976 }
7977
7978 #[must_use]
7991 pub fn query() -> #root::query::QuerySet<#struct_name> {
7992 #root::query::QuerySet::new()
7993 }
7994
7995 #( #extra_manager_fns )*
7996
7997 #insert_method
7998
7999 #bulk_insert_method
8000
8001 #bulk_upsert_pool_method
8002
8003 #bulk_update_method
8004
8005 #save_method
8006
8007 #pk_methods
8008
8009 #pk_value_helper
8010
8011 #aliased_row_helper
8012
8013 #column_consts
8014 }
8015
8016 #aliased_row_helper_my
8017
8018 #aliased_row_helper_sqlite
8019
8020 #load_related_impl
8021
8022 #load_related_impl_my
8023
8024 #load_related_impl_sqlite
8025
8026 #has_pk_value_impl
8027
8028 #fk_pk_access_impl
8029
8030 #assign_auto_pk_pool_impl
8031 }
8032}
8033
8034fn column_const_tokens(module_ident: &syn::Ident, entries: &[ColumnEntry]) -> TokenStream2 {
8036 let lines = entries.iter().map(|e| {
8037 let ident = &e.ident;
8038 let col_ty = column_type_ident(ident);
8039 quote! {
8040 #[allow(non_upper_case_globals)]
8041 pub const #ident: #module_ident::#col_ty = #module_ident::#col_ty;
8042 }
8043 });
8044 quote! { #(#lines)* }
8045}
8046
8047fn column_module_tokens(
8050 module_ident: &syn::Ident,
8051 struct_name: &syn::Ident,
8052 entries: &[ColumnEntry],
8053) -> TokenStream2 {
8054 let root = rustango_root();
8055 let items = entries.iter().map(|e| {
8056 let col_ty = column_type_ident(&e.ident);
8057 let value_ty = &e.value_ty;
8058 let name = &e.name;
8059 let column = &e.column;
8060 let field_type_tokens = &e.field_type_tokens;
8061 quote! {
8062 #[derive(::core::clone::Clone, ::core::marker::Copy)]
8063 pub struct #col_ty;
8064
8065 impl #root::core::Column for #col_ty {
8066 type Model = super::#struct_name;
8067 type Value = #value_ty;
8068 const NAME: &'static str = #name;
8069 const COLUMN: &'static str = #column;
8070 const FIELD_TYPE: #root::core::FieldType = #field_type_tokens;
8071 }
8072 }
8073 });
8074 quote! {
8075 #[doc(hidden)]
8076 #[allow(non_camel_case_types, non_snake_case)]
8077 pub mod #module_ident {
8078 #[allow(unused_imports)]
8083 use super::*;
8084 #(#items)*
8085 }
8086 }
8087}
8088
8089fn column_type_ident(field_ident: &syn::Ident) -> syn::Ident {
8090 let name = ident_name(field_ident);
8091 syn::Ident::new(&format!("{name}_col"), field_ident.span())
8092}
8093
8094fn restore_auto_now_unless(
8097 idents: &[syn::Ident],
8098 ret: &TokenStream2,
8099 keep: &TokenStream2,
8100 body: &TokenStream2,
8101) -> TokenStream2 {
8102 if idents.is_empty() {
8103 return body.clone();
8104 }
8105 let prev: Vec<syn::Ident> = (0..idents.len())
8106 .map(|i| quote::format_ident!("__rustango_auto_now_prev_{}", i))
8107 .collect();
8108 quote! {
8109 #( let #prev = ::core::clone::Clone::clone(&self.#idents); )*
8110 let _saved: #ret = async { #body }.await;
8111 if !(#keep) {
8112 #( self.#idents = #prev; )*
8113 }
8114 _saved
8115 }
8116}
8117
8118fn ident_name(ident: &syn::Ident) -> String {
8120 syn::ext::IdentExt::unraw(ident).to_string()
8121}
8122
8123fn ident_from_name(name: &str, span: proc_macro2::Span) -> syn::Ident {
8125 match syn::parse_str::<syn::Ident>(name) {
8126 Ok(mut ident) => {
8127 ident.set_span(span);
8128 ident
8129 }
8130 Err(_) => syn::Ident::new_raw(name, span),
8131 }
8132}
8133
8134fn column_module_ident(struct_name: &syn::Ident) -> syn::Ident {
8135 syn::Ident::new(
8136 &format!("__rustango_cols_{struct_name}"),
8137 struct_name.span(),
8138 )
8139}
8140
8141fn from_row_impl_tokens(
8142 struct_name: &syn::Ident,
8143 from_row_inits: &[TokenStream2],
8144 from_row_inits_my: &[TokenStream2],
8145) -> TokenStream2 {
8146 let root = rustango_root();
8147 quote! {
8158 #[cfg(feature = "postgres")]
8159 impl<'r> #root::sql::sqlx::FromRow<'r, #root::sql::sqlx::postgres::PgRow>
8160 for #struct_name
8161 {
8162 fn from_row(
8163 row: &'r #root::sql::sqlx::postgres::PgRow,
8164 ) -> ::core::result::Result<Self, #root::sql::sqlx::Error> {
8165 ::core::result::Result::Ok(Self {
8166 #( #from_row_inits ),*
8167 })
8168 }
8169 }
8170
8171 #root::__impl_my_from_row!(#struct_name, |row| {
8172 #( #from_row_inits_my ),*
8173 });
8174
8175 #root::__impl_sqlite_from_row!(#struct_name, |row| {
8176 #( #from_row_inits ),*
8177 });
8178 }
8179}
8180
8181struct ContainerAttrs {
8182 table: Option<String>,
8183 display: Option<(String, proc_macro2::Span)>,
8184 app: Option<String>,
8191 admin: Option<AdminAttrs>,
8196 audit: Option<AuditAttrs>,
8202 permissions: bool,
8206 m2m: Vec<M2MAttr>,
8210 generic_m2m: Vec<GenericM2MAttr>,
8215 indexes: Vec<IndexAttr>,
8221 checks: Vec<CheckAttr>,
8224 excludes: Vec<ExcludeAttr>,
8230 composite_fks: Vec<CompositeFkAttr>,
8234 generic_fks: Vec<GenericFkAttr>,
8238 scope: Option<String>,
8244 access: Option<String>,
8247 manager_fns: Vec<syn::Ident>,
8252 default_order: Vec<(String, bool, proc_macro2::Span)>,
8258 is_view: bool,
8263 managed: bool,
8269 base_manager_name: Option<String>,
8273 order_with_respect_to: Option<String>,
8278 proxy: bool,
8284 required_db_features: Vec<String>,
8289 required_db_vendor: Option<String>,
8295 default_related_name: Option<String>,
8301 db_table_comment: Option<String>,
8306 get_latest_by: Option<(String, bool, proc_macro2::Span)>,
8312 extra_permissions: Vec<(String, String)>,
8317 default_permissions: Vec<String>,
8324 verbose_name: Option<String>,
8330 verbose_name_plural: Option<String>,
8334 global_scopes: Vec<GlobalScopeAttr>,
8340 through_relations: Vec<ThroughAttr>,
8349 reverse_has_relations: Vec<ReverseHasAttr>,
8358 generic_has_relations: Vec<GenericHasAttr>,
8363}
8364
8365struct GlobalScopeAttr {
8371 name: String,
8372 apply: syn::Path,
8373}
8374
8375struct ThroughAttr {
8405 name: String,
8407 far: syn::Ident,
8411 far_fk_column: String,
8417 intermediate: syn::Ident,
8421 intermediate_fk_column: String,
8425 intermediate_pk_column: String,
8430}
8431
8432struct ReverseHasAttr {
8471 name: String,
8474 child: syn::Ident,
8478 child_fk_column: String,
8482 self_pk_column: Option<String>,
8485 self_pk_span: Option<proc_macro2::Span>,
8487}
8488
8489impl ReverseHasAttr {
8490 fn self_pk_column(&self) -> &str {
8491 self.self_pk_column.as_deref().unwrap_or("id")
8492 }
8493}
8494
8495struct GenericHasAttr {
8501 name: String,
8503 child: syn::Ident,
8506 ct_column: String,
8509 pk_column: String,
8512 self_pk_column: Option<String>,
8515 self_pk_span: Option<proc_macro2::Span>,
8517}
8518
8519impl GenericHasAttr {
8520 fn self_pk_column(&self) -> &str {
8521 self.self_pk_column.as_deref().unwrap_or("id")
8522 }
8523}
8524
8525struct IndexAttr {
8527 span: proc_macro2::Span,
8529 name: Option<String>,
8531 columns: Vec<String>,
8533 unique: bool,
8535 method: String,
8541 where_clause: Option<String>,
8545 include: Vec<String>,
8548}
8549
8550struct CheckAttr {
8552 name: String,
8553 expr: String,
8554}
8555
8556struct ExcludeAttr {
8561 name: String,
8563 using: String,
8565 elements: Vec<(String, String)>,
8568 where_clause: Option<String>,
8570}
8571
8572struct CompositeFkAttr {
8578 name: String,
8580 to: String,
8582 from: Vec<String>,
8584 on: Vec<String>,
8586}
8587
8588struct GenericFkAttr {
8593 name: String,
8595 ct_column: String,
8597 pk_column: String,
8599}
8600
8601struct M2MAttr {
8603 name: String,
8605 to: String,
8607 through: String,
8609 src: String,
8611 dst: String,
8613 auto_create: bool,
8618}
8619
8620struct GenericM2MAttr {
8625 name: String,
8627 through: String,
8629 pk_column: String,
8631 ct_column: String,
8634 related_column: String,
8636}
8637
8638#[derive(Default)]
8644struct AuditAttrs {
8645 track: Option<(Vec<String>, proc_macro2::Span)>,
8649}
8650
8651#[derive(Default)]
8656struct AdminAttrs {
8657 list_display: Option<(Vec<String>, proc_macro2::Span)>,
8658 search_fields: Option<(Vec<String>, proc_macro2::Span)>,
8659 list_per_page: Option<usize>,
8660 ordering: Option<(Vec<(String, bool)>, proc_macro2::Span)>,
8661 readonly_fields: Option<(Vec<String>, proc_macro2::Span)>,
8662 list_filter: Option<(Vec<String>, proc_macro2::Span)>,
8663 actions: Option<(Vec<String>, proc_macro2::Span)>,
8666 fieldsets: Option<(Vec<(String, Vec<String>)>, proc_macro2::Span)>,
8670 list_display_links: Option<(Vec<String>, proc_macro2::Span)>,
8674 search_help_text: Option<String>,
8678 actions_on_top: Option<bool>,
8681 actions_on_bottom: Option<bool>,
8685 date_hierarchy: Option<String>,
8690 prepopulated_fields: Option<(Vec<(String, Vec<String>)>, proc_macro2::Span)>,
8696 raw_id_fields: Option<(Vec<String>, proc_macro2::Span)>,
8700 autocomplete_fields: Option<(Vec<String>, proc_macro2::Span)>,
8705 list_select_related: Option<String>,
8710 formfield_overrides: Option<(Vec<(String, String)>, proc_macro2::Span)>,
8716}
8717
8718#[derive(Clone, Copy, PartialEq, Eq)]
8721enum ModelAttrName {
8722 Rustango,
8723 Rgo,
8724}
8725
8726impl ModelAttrName {
8727 fn of(attr: &syn::Attribute) -> Option<Self> {
8728 let path = attr.path();
8729 if path.is_ident("rustango") {
8730 Some(Self::Rustango)
8731 } else if path.is_ident("rgo") {
8732 Some(Self::Rgo)
8733 } else {
8734 None
8735 }
8736 }
8737
8738 fn as_str(self) -> &'static str {
8739 match self {
8740 Self::Rustango => "rustango",
8741 Self::Rgo => "rgo",
8742 }
8743 }
8744}
8745
8746fn model_attrs(attrs: &[syn::Attribute]) -> syn::Result<Vec<&syn::Attribute>> {
8749 let mut first: Option<ModelAttrName> = None;
8750 let mut out = Vec::new();
8751 for attr in attrs {
8752 let Some(name) = ModelAttrName::of(attr) else {
8753 continue;
8754 };
8755 match first {
8756 Some(seen) if seen != name => {
8757 return Err(syn::Error::new_spanned(
8758 attr.path(),
8759 format!(
8760 "`#[{}(...)]` and `#[{}(...)]` are the same attribute; \
8761 use one name on an item or field",
8762 seen.as_str(),
8763 name.as_str(),
8764 ),
8765 ));
8766 }
8767 _ => first = Some(name),
8768 }
8769 out.push(attr);
8770 }
8771 Ok(out)
8772}
8773
8774fn parse_container_attrs(input: &DeriveInput) -> syn::Result<ContainerAttrs> {
8775 let mut out = ContainerAttrs {
8776 table: None,
8777 display: None,
8778 app: None,
8779 admin: None,
8780 audit: None,
8781 permissions: true,
8790 m2m: Vec::new(),
8791 generic_m2m: Vec::new(),
8792 indexes: Vec::new(),
8793 checks: Vec::new(),
8794 excludes: Vec::new(),
8795 composite_fks: Vec::new(),
8796 generic_fks: Vec::new(),
8797 scope: None,
8798 access: None,
8799 manager_fns: Vec::new(),
8800 default_order: Vec::new(),
8801 is_view: false,
8802 managed: true,
8803 verbose_name: None,
8804 verbose_name_plural: None,
8805 base_manager_name: None,
8806 order_with_respect_to: None,
8807 proxy: false,
8808 required_db_features: Vec::new(),
8809 required_db_vendor: None,
8810 default_related_name: None,
8811 db_table_comment: None,
8812 get_latest_by: None,
8813 extra_permissions: Vec::new(),
8814 default_permissions: Vec::new(),
8815 global_scopes: Vec::new(),
8816 through_relations: Vec::new(),
8817 reverse_has_relations: Vec::new(),
8818 generic_has_relations: Vec::new(),
8819 };
8820 for attr in model_attrs(&input.attrs)? {
8821 let written = ModelAttrName::of(attr).map_or("rustango", ModelAttrName::as_str);
8823 attr.parse_nested_meta(|meta| {
8824 if meta.path.is_ident("table") {
8825 let s: LitStr = meta.value()?.parse()?;
8826 let name = s.value();
8827 validate_table_name(&name, s.span())?;
8837 out.table = Some(name);
8838 return Ok(());
8839 }
8840 if meta.path.is_ident("display") {
8841 let s: LitStr = meta.value()?.parse()?;
8842 out.display = Some((s.value(), s.span()));
8843 return Ok(());
8844 }
8845 if meta.path.is_ident("app") {
8846 let s: LitStr = meta.value()?.parse()?;
8847 out.app = Some(s.value());
8848 return Ok(());
8849 }
8850 if meta.path.is_ident("scope") {
8851 let s: LitStr = meta.value()?.parse()?;
8852 let val = s.value();
8853 if !matches!(val.to_ascii_lowercase().as_str(), "registry" | "tenant") {
8854 return Err(meta.error(format!(
8855 "`scope` must be \"registry\" or \"tenant\", got {val:?}"
8856 )));
8857 }
8858 out.scope = Some(val);
8859 return Ok(());
8860 }
8861 if meta.path.is_ident("access") {
8862 let s: LitStr = meta.value()?.parse()?;
8863 let val = s.value();
8864 if !matches!(val.as_str(), "open" | "admin_only" | "never") {
8865 return Err(meta.error(format!(
8866 "`access` must be \"open\", \"admin_only\" or \"never\", got {val:?}"
8867 )));
8868 }
8869 out.access = Some(val);
8870 return Ok(());
8871 }
8872 if meta.path.is_ident("admin") {
8873 let mut admin = AdminAttrs::default();
8874 meta.parse_nested_meta(|inner| {
8875 if inner.path.is_ident("list_display") {
8876 let s: LitStr = inner.value()?.parse()?;
8877 admin.list_display =
8878 Some((split_field_list(&s.value()), s.span()));
8879 return Ok(());
8880 }
8881 if inner.path.is_ident("search_fields") {
8882 let s: LitStr = inner.value()?.parse()?;
8883 admin.search_fields =
8884 Some((split_field_list(&s.value()), s.span()));
8885 return Ok(());
8886 }
8887 if inner.path.is_ident("readonly_fields") {
8888 let s: LitStr = inner.value()?.parse()?;
8889 admin.readonly_fields =
8890 Some((split_field_list(&s.value()), s.span()));
8891 return Ok(());
8892 }
8893 if inner.path.is_ident("list_per_page") {
8894 let lit: syn::LitInt = inner.value()?.parse()?;
8895 admin.list_per_page = Some(lit.base10_parse::<usize>()?);
8896 return Ok(());
8897 }
8898 if inner.path.is_ident("ordering") {
8899 let s: LitStr = inner.value()?.parse()?;
8900 admin.ordering = Some((
8901 parse_ordering_list(&s.value()),
8902 s.span(),
8903 ));
8904 return Ok(());
8905 }
8906 if inner.path.is_ident("list_filter") {
8907 let s: LitStr = inner.value()?.parse()?;
8908 admin.list_filter =
8909 Some((split_field_list(&s.value()), s.span()));
8910 return Ok(());
8911 }
8912 if inner.path.is_ident("actions") {
8913 let s: LitStr = inner.value()?.parse()?;
8914 admin.actions =
8915 Some((split_field_list(&s.value()), s.span()));
8916 return Ok(());
8917 }
8918 if inner.path.is_ident("fieldsets") {
8919 let s: LitStr = inner.value()?.parse()?;
8920 admin.fieldsets =
8921 Some((parse_fieldset_list(&s.value()), s.span()));
8922 return Ok(());
8923 }
8924 if inner.path.is_ident("list_display_links") {
8925 let s: LitStr = inner.value()?.parse()?;
8926 admin.list_display_links =
8927 Some((split_field_list(&s.value()), s.span()));
8928 return Ok(());
8929 }
8930 if inner.path.is_ident("search_help_text") {
8931 let s: LitStr = inner.value()?.parse()?;
8932 admin.search_help_text = Some(s.value());
8933 return Ok(());
8934 }
8935 if inner.path.is_ident("actions_on_top") {
8936 let lit: syn::LitBool = inner.value()?.parse()?;
8937 admin.actions_on_top = Some(lit.value);
8938 return Ok(());
8939 }
8940 if inner.path.is_ident("actions_on_bottom") {
8941 let lit: syn::LitBool = inner.value()?.parse()?;
8942 admin.actions_on_bottom = Some(lit.value);
8943 return Ok(());
8944 }
8945 if inner.path.is_ident("date_hierarchy") {
8946 let s: LitStr = inner.value()?.parse()?;
8947 admin.date_hierarchy = Some(s.value());
8948 return Ok(());
8949 }
8950 if inner.path.is_ident("prepopulated_fields") {
8951 let s: LitStr = inner.value()?.parse()?;
8952 admin.prepopulated_fields =
8953 Some((parse_prepopulated_list(&s.value()), s.span()));
8954 return Ok(());
8955 }
8956 if inner.path.is_ident("raw_id_fields") {
8957 let s: LitStr = inner.value()?.parse()?;
8958 admin.raw_id_fields =
8959 Some((split_field_list(&s.value()), s.span()));
8960 return Ok(());
8961 }
8962 if inner.path.is_ident("autocomplete_fields") {
8963 let s: LitStr = inner.value()?.parse()?;
8964 admin.autocomplete_fields =
8965 Some((split_field_list(&s.value()), s.span()));
8966 return Ok(());
8967 }
8968 if inner.path.is_ident("list_select_related") {
8969 let s: LitStr = inner.value()?.parse()?;
8970 admin.list_select_related = Some(s.value());
8971 return Ok(());
8972 }
8973 if inner.path.is_ident("formfield_overrides") {
8974 let s: LitStr = inner.value()?.parse()?;
8975 admin.formfield_overrides =
8976 Some((parse_formfield_overrides(&s.value()), s.span()));
8977 return Ok(());
8978 }
8979 Err(inner.error(
8980 "unknown admin attribute (supported: \
8981 `list_display`, `list_display_links`, \
8982 `search_fields`, `search_help_text`, \
8983 `readonly_fields`, \
8984 `list_filter`, `list_per_page`, `ordering`, `actions`, \
8985 `actions_on_top`, `actions_on_bottom`, \
8986 `date_hierarchy`, \
8987 `prepopulated_fields`, \
8988 `raw_id_fields`, \
8989 `autocomplete_fields`, \
8990 `list_select_related`, \
8991 `formfield_overrides`, \
8992 `fieldsets`)",
8993 ))
8994 })?;
8995 out.admin = Some(admin);
8996 return Ok(());
8997 }
8998 if meta.path.is_ident("manager") {
8999 return Err(meta.error(
9001 "`manager(ext = ...)` was removed (#2132): declare your own \
9002 `trait FooManagerExt: Sized { … }` and implement it for `QuerySet<Foo>`",
9003 ));
9004 }
9005 if meta.path.is_ident("manager_fn") {
9006 let s: LitStr = meta.value()?.parse()?;
9011 let name = s.value();
9012 if name.is_empty() {
9013 return Err(meta.error("`manager_fn = \"...\"` cannot be empty"));
9014 }
9015 if name == "objects" {
9016 return Err(meta.error(
9017 "`manager_fn = \"objects\"` collides with the default \
9018 accessor — pick a different name",
9019 ));
9020 }
9021 let ident = syn::Ident::new(&name, s.span());
9022 if out.manager_fns.iter().any(|prev| *prev == ident) {
9023 return Err(meta.error(format!(
9024 "duplicate `manager_fn = \"{name}\"`"
9025 )));
9026 }
9027 out.manager_fns.push(ident);
9028 return Ok(());
9029 }
9030 if meta.path.is_ident("default_order") {
9031 let s: LitStr = meta.value()?.parse()?;
9036 let raw = s.value();
9037 let span = s.span();
9038 let mut parsed: Vec<(String, bool, proc_macro2::Span)> =
9039 Vec::new();
9040 for entry in raw.split(',') {
9041 let trimmed = entry.trim();
9042 if trimmed.is_empty() {
9043 return Err(syn::Error::new(
9044 span,
9045 "`default_order = \"...\"` has an empty entry — \
9046 check for a stray comma",
9047 ));
9048 }
9049 let (desc, name) = if let Some(rest) = trimmed.strip_prefix('-') {
9050 (true, rest.trim().to_owned())
9051 } else if let Some(rest) = trimmed.strip_prefix('+') {
9052 (false, rest.trim().to_owned())
9053 } else {
9054 (false, trimmed.to_owned())
9055 };
9056 if name.is_empty() {
9057 return Err(syn::Error::new(
9058 span,
9059 "`default_order` entry has no column name after the prefix",
9060 ));
9061 }
9062 if parsed.iter().any(|(n, _, _)| *n == name) {
9063 return Err(syn::Error::new(
9064 span,
9065 format!("duplicate column `{name}` in `default_order`"),
9066 ));
9067 }
9068 parsed.push((name, desc, span));
9069 }
9070 if parsed.is_empty() {
9071 return Err(syn::Error::new(
9072 span,
9073 "`default_order = \"...\"` cannot be empty",
9074 ));
9075 }
9076 out.default_order = parsed;
9077 return Ok(());
9078 }
9079 if meta.path.is_ident("global_scope") {
9080 let span = meta.path.span();
9086 let mut scope_name: Option<String> = None;
9087 let mut apply_path: Option<syn::Path> = None;
9088 meta.parse_nested_meta(|inner| {
9089 if inner.path.is_ident("name") {
9090 let s: LitStr = inner.value()?.parse()?;
9091 let raw = s.value();
9092 if raw.trim().is_empty() {
9093 return Err(syn::Error::new(
9094 s.span(),
9095 "`global_scope(name = \"...\")` must not be empty",
9096 ));
9097 }
9098 scope_name = Some(raw);
9099 return Ok(());
9100 }
9101 if inner.path.is_ident("apply") {
9102 let p: syn::Path = inner.value()?.parse()?;
9103 apply_path = Some(p);
9104 return Ok(());
9105 }
9106 Err(inner.error(
9107 "unknown `global_scope` attribute (supported: \
9108 `name`, `apply`)",
9109 ))
9110 })?;
9111 let Some(name) = scope_name else {
9112 return Err(syn::Error::new(
9113 span,
9114 "`global_scope` requires `name = \"...\"`",
9115 ));
9116 };
9117 let Some(apply) = apply_path else {
9118 return Err(syn::Error::new(
9119 span,
9120 "`global_scope` requires `apply = fn_path`",
9121 ));
9122 };
9123 if out.global_scopes.iter().any(|s| s.name == name) {
9124 return Err(syn::Error::new(
9125 span,
9126 format!(
9127 "duplicate global scope name `{name}` — \
9128 pick a unique identifier so \
9129 `QuerySet::without_global_scope(\"{name}\")` \
9130 is unambiguous"
9131 ),
9132 ));
9133 }
9134 out.global_scopes.push(GlobalScopeAttr { name, apply });
9135 return Ok(());
9136 }
9137 if meta.path.is_ident("through") {
9138 let span = meta.path.span();
9143 let mut accessor_name: Option<String> = None;
9144 let mut far_ident: Option<syn::Ident> = None;
9145 let mut far_fk_column: Option<String> = None;
9146 let mut intermediate_ident: Option<syn::Ident> = None;
9147 let mut intermediate_fk_column: Option<String> = None;
9148 let mut intermediate_pk_column: Option<String> = None;
9149 fn parse_nonempty_string(
9150 inner: &syn::meta::ParseNestedMeta<'_>,
9151 field: &str,
9152 ) -> syn::Result<String> {
9153 let s: LitStr = inner.value()?.parse()?;
9154 let raw = s.value();
9155 let trimmed = raw.trim();
9156 if trimmed.is_empty() {
9157 return Err(syn::Error::new(
9158 s.span(),
9159 format!("`through({field} = \"...\")` must not be empty"),
9160 ));
9161 }
9162 Ok(trimmed.to_owned())
9163 }
9164 meta.parse_nested_meta(|inner| {
9165 if inner.path.is_ident("name") {
9166 accessor_name = Some(parse_nonempty_string(&inner, "name")?);
9167 return Ok(());
9168 }
9169 if inner.path.is_ident("far") {
9170 let s: LitStr = inner.value()?.parse()?;
9176 let raw = s.value();
9177 let trimmed = raw.trim();
9178 if trimmed.is_empty() {
9179 return Err(syn::Error::new(
9180 s.span(),
9181 "`through(far = \"...\")` must not be empty",
9182 ));
9183 }
9184 far_ident = Some(syn::Ident::new(trimmed, s.span()));
9185 return Ok(());
9186 }
9187 if inner.path.is_ident("far_fk_column") {
9188 far_fk_column =
9189 Some(parse_nonempty_string(&inner, "far_fk_column")?);
9190 return Ok(());
9191 }
9192 if inner.path.is_ident("intermediate") {
9193 let s: LitStr = inner.value()?.parse()?;
9194 let raw = s.value();
9195 let trimmed = raw.trim();
9196 if trimmed.is_empty() {
9197 return Err(syn::Error::new(
9198 s.span(),
9199 "`through(intermediate = \"...\")` must not be empty",
9200 ));
9201 }
9202 intermediate_ident = Some(syn::Ident::new(trimmed, s.span()));
9203 return Ok(());
9204 }
9205 if inner.path.is_ident("intermediate_fk_column") {
9206 intermediate_fk_column =
9207 Some(parse_nonempty_string(&inner, "intermediate_fk_column")?);
9208 return Ok(());
9209 }
9210 if inner.path.is_ident("intermediate_pk_column") {
9211 intermediate_pk_column =
9212 Some(parse_nonempty_string(&inner, "intermediate_pk_column")?);
9213 return Ok(());
9214 }
9215 Err(inner.error(
9216 "unknown `through` attribute (supported: \
9217 `name`, `far`, `far_fk_column`, \
9218 `intermediate`, `intermediate_fk_column`, \
9219 `intermediate_pk_column`)",
9220 ))
9221 })?;
9222 let Some(name) = accessor_name else {
9223 return Err(syn::Error::new(
9224 span,
9225 "`through` requires `name = \"...\"`",
9226 ));
9227 };
9228 let Some(far) = far_ident else {
9229 return Err(syn::Error::new(
9230 span,
9231 "`through` requires `far = \"FarModelType\"`",
9232 ));
9233 };
9234 let Some(far_fk_column) = far_fk_column else {
9235 return Err(syn::Error::new(
9236 span,
9237 "`through` requires `far_fk_column = \"<column>\"`",
9238 ));
9239 };
9240 let Some(intermediate) = intermediate_ident else {
9241 return Err(syn::Error::new(
9242 span,
9243 "`through` requires `intermediate = \"IntermediateModelType\"`",
9244 ));
9245 };
9246 let Some(intermediate_fk_column) = intermediate_fk_column else {
9247 return Err(syn::Error::new(
9248 span,
9249 "`through` requires `intermediate_fk_column = \"<column>\"`",
9250 ));
9251 };
9252 let intermediate_pk_column =
9253 intermediate_pk_column.unwrap_or_else(|| "id".to_owned());
9254 if out.through_relations.iter().any(|t| t.name == name) {
9255 return Err(syn::Error::new(
9256 span,
9257 format!(
9258 "duplicate `through(name = \"{name}\")` — \
9259 pick a unique accessor name"
9260 ),
9261 ));
9262 }
9263 out.through_relations.push(ThroughAttr {
9264 name,
9265 far,
9266 far_fk_column,
9267 intermediate,
9268 intermediate_fk_column,
9269 intermediate_pk_column,
9270 });
9271 return Ok(());
9272 }
9273 if meta.path.is_ident("reverse_has") {
9274 let span = meta.path.span();
9278 let mut accessor_name: Option<String> = None;
9279 let mut child_ident: Option<syn::Ident> = None;
9280 let mut child_fk_column: Option<String> = None;
9281 let mut self_pk_column: Option<String> = None;
9282 let mut self_pk_span: Option<proc_macro2::Span> = None;
9283 meta.parse_nested_meta(|inner| {
9284 if inner.path.is_ident("name") {
9285 let s: LitStr = inner.value()?.parse()?;
9286 let raw = s.value();
9287 if raw.trim().is_empty() {
9288 return Err(syn::Error::new(
9289 s.span(),
9290 "`reverse_has(name = \"...\")` must not be empty",
9291 ));
9292 }
9293 accessor_name = Some(raw);
9294 return Ok(());
9295 }
9296 if inner.path.is_ident("child") {
9297 let s: LitStr = inner.value()?.parse()?;
9298 let raw = s.value();
9299 let trimmed = raw.trim();
9300 if trimmed.is_empty() {
9301 return Err(syn::Error::new(
9302 s.span(),
9303 "`reverse_has(child = \"...\")` must not be empty",
9304 ));
9305 }
9306 child_ident = Some(syn::Ident::new(trimmed, s.span()));
9307 return Ok(());
9308 }
9309 if inner.path.is_ident("child_fk_column") {
9310 let s: LitStr = inner.value()?.parse()?;
9311 let raw = s.value();
9312 let trimmed = raw.trim();
9313 if trimmed.is_empty() {
9314 return Err(syn::Error::new(
9315 s.span(),
9316 "`reverse_has(child_fk_column = \"...\")` must not be empty",
9317 ));
9318 }
9319 child_fk_column = Some(trimmed.to_owned());
9320 return Ok(());
9321 }
9322 if inner.path.is_ident("self_pk_column") {
9323 let s: LitStr = inner.value()?.parse()?;
9324 let raw = s.value();
9325 let trimmed = raw.trim();
9326 if trimmed.is_empty() {
9327 return Err(syn::Error::new(
9328 s.span(),
9329 "`reverse_has(self_pk_column = \"...\")` must not be empty",
9330 ));
9331 }
9332 self_pk_column = Some(trimmed.to_owned());
9333 self_pk_span = Some(s.span());
9334 return Ok(());
9335 }
9336 Err(inner.error(
9337 "unknown `reverse_has` attribute (supported: \
9338 `name`, `child`, `child_fk_column`, \
9339 `self_pk_column`)",
9340 ))
9341 })?;
9342 let Some(name) = accessor_name else {
9343 return Err(syn::Error::new(
9344 span,
9345 "`reverse_has` requires `name = \"...\"`",
9346 ));
9347 };
9348 let Some(child) = child_ident else {
9349 return Err(syn::Error::new(
9350 span,
9351 "`reverse_has` requires `child = \"ChildModelType\"`",
9352 ));
9353 };
9354 let Some(child_fk_column) = child_fk_column else {
9355 return Err(syn::Error::new(
9356 span,
9357 "`reverse_has` requires `child_fk_column = \"<column>\"`",
9358 ));
9359 };
9360 if out.reverse_has_relations.iter().any(|r| r.name == name) {
9361 return Err(syn::Error::new(
9362 span,
9363 format!(
9364 "duplicate `reverse_has(name = \"{name}\")` — \
9365 pick a unique accessor name"
9366 ),
9367 ));
9368 }
9369 out.reverse_has_relations.push(ReverseHasAttr {
9370 name,
9371 child,
9372 child_fk_column,
9373 self_pk_column,
9374 self_pk_span,
9375 });
9376 return Ok(());
9377 }
9378 if meta.path.is_ident("generic_has") {
9379 let span = meta.path.span();
9385 let mut accessor_name: Option<String> = None;
9386 let mut child_ident: Option<syn::Ident> = None;
9387 let mut ct_column: Option<String> = None;
9388 let mut pk_column: Option<String> = None;
9389 let mut self_pk_column: Option<String> = None;
9390 let mut self_pk_span: Option<proc_macro2::Span> = None;
9391 meta.parse_nested_meta(|inner| {
9392 if inner.path.is_ident("name") {
9393 let s: LitStr = inner.value()?.parse()?;
9394 let raw = s.value();
9395 if raw.trim().is_empty() {
9396 return Err(syn::Error::new(
9397 s.span(),
9398 "`generic_has(name = \"...\")` must not be empty",
9399 ));
9400 }
9401 accessor_name = Some(raw);
9402 return Ok(());
9403 }
9404 if inner.path.is_ident("child") {
9405 let s: LitStr = inner.value()?.parse()?;
9406 let trimmed = s.value().trim().to_owned();
9407 if trimmed.is_empty() {
9408 return Err(syn::Error::new(
9409 s.span(),
9410 "`generic_has(child = \"...\")` must not be empty",
9411 ));
9412 }
9413 child_ident = Some(syn::Ident::new(&trimmed, s.span()));
9414 return Ok(());
9415 }
9416 if inner.path.is_ident("ct_column") {
9417 let s: LitStr = inner.value()?.parse()?;
9418 let trimmed = s.value().trim().to_owned();
9419 if trimmed.is_empty() {
9420 return Err(syn::Error::new(
9421 s.span(),
9422 "`generic_has(ct_column = \"...\")` must not be empty",
9423 ));
9424 }
9425 ct_column = Some(trimmed);
9426 return Ok(());
9427 }
9428 if inner.path.is_ident("pk_column") {
9429 let s: LitStr = inner.value()?.parse()?;
9430 let trimmed = s.value().trim().to_owned();
9431 if trimmed.is_empty() {
9432 return Err(syn::Error::new(
9433 s.span(),
9434 "`generic_has(pk_column = \"...\")` must not be empty",
9435 ));
9436 }
9437 pk_column = Some(trimmed);
9438 return Ok(());
9439 }
9440 if inner.path.is_ident("self_pk_column") {
9441 let s: LitStr = inner.value()?.parse()?;
9442 let trimmed = s.value().trim().to_owned();
9443 if trimmed.is_empty() {
9444 return Err(syn::Error::new(
9445 s.span(),
9446 "`generic_has(self_pk_column = \"...\")` must not be empty",
9447 ));
9448 }
9449 self_pk_column = Some(trimmed);
9450 self_pk_span = Some(s.span());
9451 return Ok(());
9452 }
9453 Err(inner.error(
9454 "unknown `generic_has` attribute (supported: \
9455 `name`, `child`, `ct_column`, `pk_column`, \
9456 `self_pk_column`)",
9457 ))
9458 })?;
9459 let Some(name) = accessor_name else {
9460 return Err(syn::Error::new(
9461 span,
9462 "`generic_has` requires `name = \"...\"`",
9463 ));
9464 };
9465 let Some(child) = child_ident else {
9466 return Err(syn::Error::new(
9467 span,
9468 "`generic_has` requires `child = \"ChildModelType\"`",
9469 ));
9470 };
9471 let ct_column = ct_column.unwrap_or_else(|| "content_type_id".to_owned());
9472 let pk_column = pk_column.unwrap_or_else(|| "object_pk".to_owned());
9473 if out.generic_has_relations.iter().any(|r| r.name == name) {
9474 return Err(syn::Error::new(
9475 span,
9476 format!(
9477 "duplicate `generic_has(name = \"{name}\")` — \
9478 pick a unique accessor name"
9479 ),
9480 ));
9481 }
9482 out.generic_has_relations.push(GenericHasAttr {
9483 name,
9484 child,
9485 ct_column,
9486 pk_column,
9487 self_pk_column,
9488 self_pk_span,
9489 });
9490 return Ok(());
9491 }
9492 if meta.path.is_ident("audit") {
9493 let mut audit = AuditAttrs::default();
9494 meta.parse_nested_meta(|inner| {
9495 if inner.path.is_ident("track") {
9496 let s: LitStr = inner.value()?.parse()?;
9497 audit.track =
9498 Some((split_field_list(&s.value()), s.span()));
9499 return Ok(());
9500 }
9501 Err(inner.error(
9502 "unknown audit attribute (supported: `track`)",
9503 ))
9504 })?;
9505 out.audit = Some(audit);
9506 return Ok(());
9507 }
9508 if meta.path.is_ident("permissions") {
9509 if let Ok(v) = meta.value() {
9514 let lit: syn::LitBool = v.parse()?;
9515 out.permissions = lit.value;
9516 } else {
9517 out.permissions = true;
9518 }
9519 return Ok(());
9520 }
9521 if meta.path.is_ident("view") {
9522 if let Ok(v) = meta.value() {
9528 let lit: syn::LitBool = v.parse()?;
9529 out.is_view = lit.value;
9530 } else {
9531 out.is_view = true;
9532 }
9533 return Ok(());
9534 }
9535 if meta.path.is_ident("managed") {
9536 let v = meta.value()?;
9544 let lit: syn::LitBool = v.parse()?;
9545 out.managed = lit.value;
9546 return Ok(());
9547 }
9548 if meta.path.is_ident("verbose_name") {
9549 let s: LitStr = meta.value()?.parse()?;
9550 out.verbose_name = Some(s.value());
9551 return Ok(());
9552 }
9553 if meta.path.is_ident("verbose_name_plural") {
9554 let s: LitStr = meta.value()?.parse()?;
9555 out.verbose_name_plural = Some(s.value());
9556 return Ok(());
9557 }
9558 if meta.path.is_ident("db_table_comment") {
9559 let s: LitStr = meta.value()?.parse()?;
9562 out.db_table_comment = Some(s.value());
9563 return Ok(());
9564 }
9565 if meta.path.is_ident("proxy") {
9566 let value = if meta.input.peek(syn::Token![=]) {
9575 meta.value()?.parse::<syn::LitBool>()?.value
9576 } else {
9577 true
9578 };
9579 out.proxy = value;
9580 return Ok(());
9581 }
9582 if meta.path.is_ident("order_with_respect_to") {
9583 let s: LitStr = meta.value()?.parse()?;
9597 let raw = s.value();
9598 if raw.is_empty() {
9599 return Err(syn::Error::new(
9600 s.span(),
9601 "`order_with_respect_to` must be a non-empty FK field name",
9602 ));
9603 }
9604 let valid = raw
9605 .chars()
9606 .all(|c| c == '_' || c.is_ascii_alphanumeric())
9607 && !raw.chars().next().is_some_and(|c| c.is_ascii_digit());
9608 if !valid {
9609 return Err(syn::Error::new(
9610 s.span(),
9611 format!(
9612 "`order_with_respect_to` must be a valid Rust \
9613 identifier (letters / digits / underscores, \
9614 not starting with a digit); got `{raw}`"
9615 ),
9616 ));
9617 }
9618 out.order_with_respect_to = Some(raw);
9619 return Ok(());
9620 }
9621 if meta.path.is_ident("required_db_features") {
9622 let s: LitStr = meta.value()?.parse()?;
9637 let raw = s.value();
9638 let features: Vec<String> = raw
9639 .split(',')
9640 .map(str::trim)
9641 .filter(|s| !s.is_empty())
9642 .map(str::to_owned)
9643 .collect();
9644 if features.is_empty() {
9645 return Err(syn::Error::new(
9646 s.span(),
9647 "`required_db_features` must list at least one \
9648 comma-separated capability token",
9649 ));
9650 }
9651 out.required_db_features = features;
9652 return Ok(());
9653 }
9654 if meta.path.is_ident("required_db_vendor") {
9655 let s: LitStr = meta.value()?.parse()?;
9663 let raw = s.value().to_ascii_lowercase();
9664 match raw.as_str() {
9665 "postgresql" | "postgres" | "pg" => {
9666 out.required_db_vendor = Some("postgres".to_owned());
9667 }
9668 "mysql" | "mariadb" => {
9669 out.required_db_vendor = Some("mysql".to_owned());
9670 }
9671 "sqlite" | "sqlite3" => {
9672 out.required_db_vendor = Some("sqlite".to_owned());
9673 }
9674 _ => {
9675 return Err(syn::Error::new(
9676 s.span(),
9677 format!(
9678 "unknown required_db_vendor `{raw}` — \
9679 expected `postgres` (aliases: `postgresql`, `pg`), \
9680 `mysql` (alias: `mariadb`), or `sqlite` \
9681 (alias: `sqlite3`)"
9682 ),
9683 ));
9684 }
9685 }
9686 return Ok(());
9687 }
9688 if meta.path.is_ident("base_manager_name") {
9689 let s: LitStr = meta.value()?.parse()?;
9698 let raw = s.value();
9699 if raw.is_empty() {
9700 return Err(syn::Error::new(
9701 s.span(),
9702 "`base_manager_name` must be a non-empty string",
9703 ));
9704 }
9705 let valid = raw
9706 .chars()
9707 .all(|c| c == '_' || c.is_ascii_alphanumeric())
9708 && !raw.chars().next().is_some_and(|c| c.is_ascii_digit());
9709 if !valid {
9710 return Err(syn::Error::new(
9711 s.span(),
9712 format!(
9713 "`base_manager_name` must be a valid Rust \
9714 identifier (letters / digits / underscores, \
9715 not starting with a digit); got `{raw}`"
9716 ),
9717 ));
9718 }
9719 out.base_manager_name = Some(raw);
9720 return Ok(());
9721 }
9722 if meta.path.is_ident("default_related_name") {
9723 let s: LitStr = meta.value()?.parse()?;
9734 let raw = s.value();
9735 if raw.is_empty() {
9736 return Err(syn::Error::new(
9737 s.span(),
9738 "`default_related_name` must be a non-empty string",
9739 ));
9740 }
9741 let valid = raw
9742 .chars()
9743 .all(|c| c == '_' || c.is_ascii_lowercase() || c.is_ascii_digit())
9744 && !raw.chars().next().is_some_and(|c| c.is_ascii_digit());
9745 if !valid {
9746 return Err(syn::Error::new(
9747 s.span(),
9748 format!(
9749 "`default_related_name` must be snake_case ASCII \
9750 (lowercase letters / digits / underscores, not \
9751 starting with a digit); got `{raw}`"
9752 ),
9753 ));
9754 }
9755 out.default_related_name = Some(raw);
9756 return Ok(());
9757 }
9758 if meta.path.is_ident("extra_permissions") {
9759 let s: LitStr = meta.value()?.parse()?;
9761 let raw = s.value();
9762 let mut pairs = Vec::new();
9763 for entry in raw.split(',') {
9764 let entry = entry.trim();
9765 if entry.is_empty() {
9766 continue;
9767 }
9768 let (codename, label) = match entry.split_once(':') {
9769 Some((c, l)) => (c.trim().to_owned(), l.trim().to_owned()),
9770 None => (entry.to_owned(), entry.to_owned()),
9771 };
9772 if codename.is_empty() {
9773 return Err(meta.error(
9774 "`extra_permissions` entries must be `codename:label` pairs",
9775 ));
9776 }
9777 pairs.push((codename, label));
9778 }
9779 if pairs.is_empty() {
9780 return Err(meta
9781 .error("`extra_permissions = \"…\"` must list at least one pair"));
9782 }
9783 out.extra_permissions = pairs;
9784 return Ok(());
9785 }
9786 if meta.path.is_ident("default_permissions") {
9787 let s: LitStr = meta.value()?.parse()?;
9790 let raw = s.value();
9791 let mut actions: Vec<String> = Vec::new();
9792 for entry in raw.split(',') {
9793 let action = entry.trim().to_ascii_lowercase();
9794 if action.is_empty() {
9795 continue;
9796 }
9797 match action.as_str() {
9798 "add" | "change" | "delete" | "view" => {}
9799 other => {
9800 return Err(syn::Error::new(
9801 s.span(),
9802 format!(
9803 "unknown default_permissions action `{other}` — \
9804 expected one of `add`, `change`, `delete`, `view`"
9805 ),
9806 ));
9807 }
9808 }
9809 if !actions.contains(&action) {
9810 actions.push(action);
9811 }
9812 }
9813 if actions.is_empty() {
9814 return Err(syn::Error::new(
9815 s.span(),
9816 "`default_permissions = \"…\"` must list at least one action; \
9817 use `permissions = false` on the container if you want NO \
9818 permissions seeded for this model.",
9819 ));
9820 }
9821 out.default_permissions = actions;
9822 return Ok(());
9823 }
9824 if meta.path.is_ident("get_latest_by") {
9825 let s: LitStr = meta.value()?.parse()?;
9827 let raw = s.value();
9828 let trimmed = raw.trim();
9829 if trimmed.is_empty() {
9830 return Err(meta.error("`get_latest_by` must name a column"));
9831 }
9832 let (col, desc) = if let Some(stripped) = trimmed.strip_prefix('-') {
9833 (stripped.to_owned(), true)
9834 } else if let Some(stripped) = trimmed.strip_prefix('+') {
9835 (stripped.to_owned(), false)
9836 } else {
9837 (trimmed.to_owned(), false)
9838 };
9839 if col.is_empty() {
9840 return Err(meta.error("`get_latest_by` must name a column"));
9841 }
9842 out.get_latest_by = Some((col, desc, s.span()));
9843 return Ok(());
9844 }
9845 if meta.path.is_ident("unique_together") {
9846 let (columns, name) = parse_together_attr(&meta, "unique_together", written)?;
9855 out.indexes.push(IndexAttr {
9856 span: meta.path.span(),
9857 name,
9858 columns,
9859 unique: true,
9860 method: "btree".to_owned(),
9861 where_clause: None,
9862 include: Vec::new(),
9863 });
9864 return Ok(());
9865 }
9866 if meta.path.is_ident("index_together") {
9867 let (columns, name) = parse_together_attr(&meta, "index_together", written)?;
9873 out.indexes.push(IndexAttr {
9874 span: meta.path.span(),
9875 name,
9876 columns,
9877 unique: false,
9878 method: "btree".to_owned(),
9879 where_clause: None,
9880 include: Vec::new(),
9881 });
9882 return Ok(());
9883 }
9884 if meta.path.is_ident("unique_when") {
9885 let mut columns: Option<Vec<String>> = None;
9899 let mut condition: Option<String> = None;
9900 let mut name: Option<String> = None;
9901 let mut include: Vec<String> = Vec::new();
9902 meta.parse_nested_meta(|inner| {
9903 if inner.path.is_ident("columns") {
9904 let s: LitStr = inner.value()?.parse()?;
9905 columns = Some(split_field_list(&s.value()));
9906 return Ok(());
9907 }
9908 if inner.path.is_ident("condition") {
9909 let s: LitStr = inner.value()?.parse()?;
9910 condition = Some(s.value());
9911 return Ok(());
9912 }
9913 if inner.path.is_ident("name") {
9914 let s: LitStr = inner.value()?.parse()?;
9915 name = Some(s.value());
9916 return Ok(());
9917 }
9918 if inner.path.is_ident("include") {
9919 let s: LitStr = inner.value()?.parse()?;
9923 include = split_field_list(&s.value());
9924 return Ok(());
9925 }
9926 Err(inner.error(
9927 "unknown unique_when attribute (supported: \
9928 `columns = \"...\"`, `condition = \"...\"`, \
9929 `name = \"...\"`, `include = \"...\"`)",
9930 ))
9931 })?;
9932 let columns = columns.ok_or_else(|| {
9933 meta.error("`unique_when(...)` requires `columns = \"...\"`")
9934 })?;
9935 let condition = condition.ok_or_else(|| {
9936 meta.error("`unique_when(...)` requires `condition = \"...\"`")
9937 })?;
9938 if columns.is_empty() {
9939 return Err(meta.error("`unique_when(columns = \"\")` is empty"));
9940 }
9941 out.indexes.push(IndexAttr {
9942 span: meta.path.span(),
9943 name,
9944 columns,
9945 unique: true,
9946 method: "btree".to_owned(),
9947 where_clause: Some(condition),
9948 include,
9949 });
9950 return Ok(());
9951 }
9952 if meta.path.is_ident("index_when") {
9953 let mut columns: Option<Vec<String>> = None;
9970 let mut condition: Option<String> = None;
9971 let mut name: Option<String> = None;
9972 let mut method: String = "btree".to_owned();
9973 let mut include: Vec<String> = Vec::new();
9974 meta.parse_nested_meta(|inner| {
9975 if inner.path.is_ident("columns") {
9976 let s: LitStr = inner.value()?.parse()?;
9977 columns = Some(split_field_list(&s.value()));
9978 return Ok(());
9979 }
9980 if inner.path.is_ident("condition") {
9981 let s: LitStr = inner.value()?.parse()?;
9982 condition = Some(s.value());
9983 return Ok(());
9984 }
9985 if inner.path.is_ident("name") {
9986 let s: LitStr = inner.value()?.parse()?;
9987 name = Some(s.value());
9988 return Ok(());
9989 }
9990 if inner.path.is_ident("method") {
9991 let s: LitStr = inner.value()?.parse()?;
9992 method = s.value();
9993 return Ok(());
9994 }
9995 if inner.path.is_ident("include") {
9996 let s: LitStr = inner.value()?.parse()?;
10000 include = split_field_list(&s.value());
10001 return Ok(());
10002 }
10003 Err(inner.error(
10004 "unknown index_when attribute (supported: \
10005 `columns = \"...\"`, `condition = \"...\"`, \
10006 `name = \"...\"`, `method = \"btree|gin|gist|...\"`, \
10007 `include = \"...\"`)",
10008 ))
10009 })?;
10010 let columns = columns
10011 .ok_or_else(|| meta.error("`index_when(...)` requires `columns = \"...\"`"))?;
10012 let condition = condition.ok_or_else(|| {
10013 meta.error("`index_when(...)` requires `condition = \"...\"`")
10014 })?;
10015 if columns.is_empty() {
10016 return Err(meta.error("`index_when(columns = \"\")` is empty"));
10017 }
10018 out.indexes.push(IndexAttr {
10019 span: meta.path.span(),
10020 name,
10021 columns,
10022 unique: false,
10023 method,
10024 where_clause: Some(condition),
10025 include,
10026 });
10027 return Ok(());
10028 }
10029 if meta.path.is_ident("index") {
10030 let cols_lit: LitStr;
10041 let mut unique = false;
10042 let mut name: Option<String> = None;
10043 let mut method = "btree".to_owned();
10044 if meta.input.peek(syn::token::Paren) {
10045 let content;
10046 syn::parenthesized!(content in meta.input);
10047 cols_lit = content.parse()?;
10048 while content.peek(syn::Token![,]) {
10049 content.parse::<syn::Token![,]>()?;
10050 if content.is_empty() {
10051 break;
10052 }
10053 let flag: syn::Ident = content.parse()?;
10054 if flag == "unique" {
10055 unique = true;
10056 } else if flag == "name" {
10057 content.parse::<syn::Token![=]>()?;
10058 let s: LitStr = content.parse()?;
10059 name = Some(s.value());
10060 } else if flag == "method" {
10061 content.parse::<syn::Token![=]>()?;
10062 let s: LitStr = content.parse()?;
10063 let v = s.value();
10064 match v.as_str() {
10065 "btree" | "gin" | "gist" | "brin" | "spgist" | "hash"
10066 | "bloom" => method = v,
10067 other => {
10068 return Err(syn::Error::new(
10069 s.span(),
10070 format!("unknown index method `{other}` (supported: btree, gin, gist, brin, spgist, hash, bloom)"),
10071 ));
10072 }
10073 }
10074 } else {
10075 return Err(syn::Error::new(
10076 flag.span(),
10077 "unknown index flag (supported: `unique`, `name`, `method`)",
10078 ));
10079 }
10080 }
10081 } else {
10082 cols_lit = meta.value()?.parse()?;
10083 }
10084 let columns = split_field_list(&cols_lit.value());
10085 out.indexes.push(IndexAttr {
10086 span: meta.path.span(),
10087 name,
10088 columns,
10089 unique,
10090 method,
10091 where_clause: None,
10092 include: Vec::new(),
10093 });
10094 return Ok(());
10095 }
10096 if meta.path.is_ident("check") {
10097 let mut name: Option<String> = None;
10099 let mut expr: Option<String> = None;
10100 meta.parse_nested_meta(|inner| {
10101 if inner.path.is_ident("name") {
10102 let s: LitStr = inner.value()?.parse()?;
10103 name = Some(s.value());
10104 return Ok(());
10105 }
10106 if inner.path.is_ident("expr") {
10107 let s: LitStr = inner.value()?.parse()?;
10108 expr = Some(s.value());
10109 return Ok(());
10110 }
10111 Err(inner.error("unknown check attribute (supported: `name`, `expr`)"))
10112 })?;
10113 let name = name.ok_or_else(|| meta.error("check requires `name = \"...\"`"))?;
10114 let expr = expr.ok_or_else(|| meta.error("check requires `expr = \"...\"`"))?;
10115 out.checks.push(CheckAttr { name, expr });
10116 return Ok(());
10117 }
10118 if meta.path.is_ident("exclude") {
10119 let mut name: Option<String> = None;
10123 let mut using: Option<String> = None;
10124 let mut elements_raw: Option<(String, proc_macro2::Span)> = None;
10125 let mut where_clause: Option<String> = None;
10126 meta.parse_nested_meta(|inner| {
10127 if inner.path.is_ident("name") {
10128 let s: LitStr = inner.value()?.parse()?;
10129 name = Some(s.value());
10130 return Ok(());
10131 }
10132 if inner.path.is_ident("using") {
10133 let s: LitStr = inner.value()?.parse()?;
10134 using = Some(s.value());
10135 return Ok(());
10136 }
10137 if inner.path.is_ident("elements") {
10138 let s: LitStr = inner.value()?.parse()?;
10139 elements_raw = Some((s.value(), s.span()));
10140 return Ok(());
10141 }
10142 if inner.path.is_ident("where") || inner.path.is_ident("where_clause") {
10143 let s: LitStr = inner.value()?.parse()?;
10144 where_clause = Some(s.value());
10145 return Ok(());
10146 }
10147 Err(inner.error(
10148 "unknown exclude attribute (supported: `name`, `using`, `elements`, `where`)",
10149 ))
10150 })?;
10151 let name = name.ok_or_else(|| meta.error("exclude requires `name = \"...\"`"))?;
10152 let using = using.unwrap_or_else(|| "gist".to_owned());
10153 let (elements_str, elements_span) = elements_raw.ok_or_else(|| {
10154 meta.error(
10155 "exclude requires `elements = \"col WITH op, col WITH op\"`",
10156 )
10157 })?;
10158 let mut elements: Vec<(String, String)> = Vec::new();
10160 for pair in elements_str.split(',') {
10161 let pair = pair.trim();
10162 if pair.is_empty() {
10163 continue;
10164 }
10165 let mut split = pair.splitn(2, |c: char| c.is_whitespace());
10166 let col = split.next().unwrap_or("").trim();
10167 let rest = split.next().unwrap_or("").trim();
10168 let rest_lc = rest.to_ascii_lowercase();
10170 let op = rest_lc
10171 .strip_prefix("with")
10172 .map(|r| r.trim_start())
10173 .filter(|r| !r.is_empty())
10174 .map(|_| {
10175 rest[4..].trim_start().to_owned()
10178 });
10179 let Some(op) = op else {
10180 return Err(syn::Error::new(
10181 elements_span,
10182 format!(
10183 "exclude elements: `{pair}` must be `<col> WITH <op>` \
10184 (e.g. `room_id WITH =` or `during WITH &&`)"
10185 ),
10186 ));
10187 };
10188 if col.is_empty() || op.is_empty() {
10189 return Err(syn::Error::new(
10190 elements_span,
10191 format!(
10192 "exclude elements: `{pair}` must be `<col> WITH <op>` \
10193 (both sides non-empty)"
10194 ),
10195 ));
10196 }
10197 elements.push((col.to_owned(), op));
10198 }
10199 if elements.is_empty() {
10200 return Err(syn::Error::new(
10201 elements_span,
10202 "exclude requires at least one `col WITH op` element",
10203 ));
10204 }
10205 out.excludes.push(ExcludeAttr {
10206 name,
10207 using,
10208 elements,
10209 where_clause,
10210 });
10211 return Ok(());
10212 }
10213 if meta.path.is_ident("generic_fk") {
10214 let mut gfk = GenericFkAttr {
10215 name: String::new(),
10216 ct_column: String::new(),
10217 pk_column: String::new(),
10218 };
10219 meta.parse_nested_meta(|inner| {
10220 if inner.path.is_ident("name") {
10221 let s: LitStr = inner.value()?.parse()?;
10222 gfk.name = s.value();
10223 return Ok(());
10224 }
10225 if inner.path.is_ident("ct_column") {
10226 let s: LitStr = inner.value()?.parse()?;
10227 gfk.ct_column = s.value();
10228 return Ok(());
10229 }
10230 if inner.path.is_ident("pk_column") {
10231 let s: LitStr = inner.value()?.parse()?;
10232 gfk.pk_column = s.value();
10233 return Ok(());
10234 }
10235 Err(inner.error(
10236 "unknown generic_fk attribute (supported: `name`, `ct_column`, `pk_column`)",
10237 ))
10238 })?;
10239 if gfk.name.is_empty() {
10240 return Err(meta.error("generic_fk requires `name = \"...\"`"));
10241 }
10242 if gfk.ct_column.is_empty() {
10243 return Err(meta.error("generic_fk requires `ct_column = \"...\"`"));
10244 }
10245 if gfk.pk_column.is_empty() {
10246 return Err(meta.error("generic_fk requires `pk_column = \"...\"`"));
10247 }
10248 out.generic_fks.push(gfk);
10249 return Ok(());
10250 }
10251 if meta.path.is_ident("fk_composite") {
10252 let mut fk = CompositeFkAttr {
10253 name: String::new(),
10254 to: String::new(),
10255 from: Vec::new(),
10256 on: Vec::new(),
10257 };
10258 meta.parse_nested_meta(|inner| {
10259 if inner.path.is_ident("name") {
10260 let s: LitStr = inner.value()?.parse()?;
10261 fk.name = s.value();
10262 return Ok(());
10263 }
10264 if inner.path.is_ident("to") {
10265 let s: LitStr = inner.value()?.parse()?;
10266 fk.to = s.value();
10267 return Ok(());
10268 }
10269 if inner.path.is_ident("on") || inner.path.is_ident("from") {
10272 let value = inner.value()?;
10273 let content;
10274 syn::parenthesized!(content in value);
10275 let lits: syn::punctuated::Punctuated<syn::LitStr, syn::Token![,]> =
10276 content.parse_terminated(
10277 |p| p.parse::<syn::LitStr>(),
10278 syn::Token![,],
10279 )?;
10280 let cols: Vec<String> = lits.iter().map(syn::LitStr::value).collect();
10281 if inner.path.is_ident("on") {
10282 fk.on = cols;
10283 } else {
10284 fk.from = cols;
10285 }
10286 return Ok(());
10287 }
10288 Err(inner.error(
10289 "unknown fk_composite attribute (supported: `name`, `to`, `on`, `from`)",
10290 ))
10291 })?;
10292 if fk.name.is_empty() {
10293 return Err(meta.error("fk_composite requires `name = \"...\"`"));
10294 }
10295 if fk.to.is_empty() {
10296 return Err(meta.error("fk_composite requires `to = \"...\"`"));
10297 }
10298 if fk.from.is_empty() || fk.on.is_empty() {
10299 return Err(meta.error(
10300 "fk_composite requires non-empty `from = (...)` and `on = (...)` tuples",
10301 ));
10302 }
10303 if fk.from.len() != fk.on.len() {
10304 return Err(meta.error(format!(
10305 "fk_composite `from` ({} cols) and `on` ({} cols) must be the same length",
10306 fk.from.len(),
10307 fk.on.len(),
10308 )));
10309 }
10310 out.composite_fks.push(fk);
10311 return Ok(());
10312 }
10313 if meta.path.is_ident("m2m") {
10314 let mut m2m = M2MAttr {
10315 name: String::new(),
10316 to: String::new(),
10317 through: String::new(),
10318 src: String::new(),
10319 dst: String::new(),
10320 auto_create: true,
10321 };
10322 meta.parse_nested_meta(|inner| {
10323 if inner.path.is_ident("name") {
10324 let s: LitStr = inner.value()?.parse()?;
10325 m2m.name = s.value();
10326 return Ok(());
10327 }
10328 if inner.path.is_ident("to") {
10329 let s: LitStr = inner.value()?.parse()?;
10330 m2m.to = s.value();
10331 return Ok(());
10332 }
10333 if inner.path.is_ident("through") {
10334 let s: LitStr = inner.value()?.parse()?;
10335 m2m.through = s.value();
10336 return Ok(());
10337 }
10338 if inner.path.is_ident("src") {
10339 let s: LitStr = inner.value()?.parse()?;
10340 m2m.src = s.value();
10341 return Ok(());
10342 }
10343 if inner.path.is_ident("dst") {
10344 let s: LitStr = inner.value()?.parse()?;
10345 m2m.dst = s.value();
10346 return Ok(());
10347 }
10348 if inner.path.is_ident("auto_create") {
10349 let lit: syn::LitBool = inner.value()?.parse()?;
10350 m2m.auto_create = lit.value;
10351 return Ok(());
10352 }
10353 Err(inner.error("unknown m2m attribute (supported: `name`, `to`, `through`, `src`, `dst`, `auto_create`)"))
10354 })?;
10355 if m2m.name.is_empty() {
10356 return Err(meta.error("m2m requires `name = \"...\"`"));
10357 }
10358 if m2m.to.is_empty() {
10359 return Err(meta.error("m2m requires `to = \"...\"`"));
10360 }
10361 if m2m.through.is_empty() {
10362 return Err(meta.error("m2m requires `through = \"...\"`"));
10363 }
10364 if m2m.src.is_empty() {
10365 return Err(meta.error("m2m requires `src = \"...\"`"));
10366 }
10367 if m2m.dst.is_empty() {
10368 return Err(meta.error("m2m requires `dst = \"...\"`"));
10369 }
10370 out.m2m.push(m2m);
10371 return Ok(());
10372 }
10373 if meta.path.is_ident("generic_m2m") {
10374 let mut gm = GenericM2MAttr {
10375 name: String::new(),
10376 through: String::new(),
10377 pk_column: String::new(),
10378 ct_column: String::new(),
10379 related_column: String::new(),
10380 };
10381 meta.parse_nested_meta(|inner| {
10382 let field = |inner: &syn::meta::ParseNestedMeta| -> syn::Result<String> {
10383 let s: LitStr = inner.value()?.parse()?;
10384 Ok(s.value())
10385 };
10386 if inner.path.is_ident("name") {
10387 gm.name = field(&inner)?;
10388 return Ok(());
10389 }
10390 if inner.path.is_ident("through") {
10391 gm.through = field(&inner)?;
10392 return Ok(());
10393 }
10394 if inner.path.is_ident("pk_column") {
10395 gm.pk_column = field(&inner)?;
10396 return Ok(());
10397 }
10398 if inner.path.is_ident("ct_column") {
10399 gm.ct_column = field(&inner)?;
10400 return Ok(());
10401 }
10402 if inner.path.is_ident("related_column") {
10403 gm.related_column = field(&inner)?;
10404 return Ok(());
10405 }
10406 Err(inner.error("unknown generic_m2m attribute (supported: `name`, `through`, `pk_column`, `ct_column`, `related_column`)"))
10407 })?;
10408 for (val, label) in [
10409 (&gm.name, "name"),
10410 (&gm.through, "through"),
10411 (&gm.pk_column, "pk_column"),
10412 (&gm.ct_column, "ct_column"),
10413 (&gm.related_column, "related_column"),
10414 ] {
10415 if val.is_empty() {
10416 return Err(meta.error(format!("generic_m2m requires `{label} = \"...\"`")));
10417 }
10418 }
10419 out.generic_m2m.push(gm);
10420 return Ok(());
10421 }
10422 Err(unknown_key_error(&meta, "container", CONTAINER_ATTR_KEYS))
10423 })?;
10424 }
10425 Ok(out)
10426}
10427
10428fn split_field_list(raw: &str) -> Vec<String> {
10432 raw.split(',')
10433 .map(str::trim)
10434 .filter(|s| !s.is_empty())
10435 .map(str::to_owned)
10436 .collect()
10437}
10438
10439fn parse_together_attr(
10447 meta: &syn::meta::ParseNestedMeta<'_>,
10448 attr: &str,
10449 written: &str,
10450) -> syn::Result<(Vec<String>, Option<String>)> {
10451 if meta.input.peek(syn::Token![=]) {
10454 let cols_lit: LitStr = meta.value()?.parse()?;
10455 let columns = split_field_list(&cols_lit.value());
10456 check_together_columns(meta, attr, written, &columns)?;
10457 return Ok((columns, None));
10458 }
10459 let mut columns: Option<Vec<String>> = None;
10460 let mut name: Option<String> = None;
10461 meta.parse_nested_meta(|inner| {
10462 if inner.path.is_ident("columns") {
10463 let s: LitStr = inner.value()?.parse()?;
10464 columns = Some(split_field_list(&s.value()));
10465 return Ok(());
10466 }
10467 if inner.path.is_ident("name") {
10468 let s: LitStr = inner.value()?.parse()?;
10469 name = Some(s.value());
10470 return Ok(());
10471 }
10472 Err(inner.error("unknown sub-attribute (supported: `columns`, `name`)"))
10473 })?;
10474 let columns = columns.ok_or_else(|| {
10475 meta.error(format!(
10476 "{attr}(...) requires a `columns = \"col1, col2\"` argument",
10477 ))
10478 })?;
10479 check_together_columns(meta, attr, written, &columns)?;
10480 Ok((columns, name))
10481}
10482
10483fn check_together_columns(
10484 meta: &syn::meta::ParseNestedMeta<'_>,
10485 attr: &str,
10486 written: &str,
10487 columns: &[String],
10488) -> syn::Result<()> {
10489 if columns.len() < 2 {
10490 let flag = if attr == "unique_together" {
10491 "unique"
10492 } else {
10493 "index"
10494 };
10495 let single = format!("#[{written}({flag})] on the field");
10496 return Err(meta.error(format!(
10497 "{attr} expects two or more columns; for a single-column equivalent use {single}",
10498 )));
10499 }
10500 Ok(())
10501}
10502
10503fn parse_fieldset_list(raw: &str) -> Vec<(String, Vec<String>)> {
10512 raw.split('|')
10513 .map(str::trim)
10514 .filter(|s| !s.is_empty())
10515 .map(|section| {
10516 let (title, rest) = match section.split_once(':') {
10518 Some((title, rest)) if !title.contains(',') => (title.trim().to_owned(), rest),
10519 _ => (String::new(), section),
10520 };
10521 let fields = split_field_list(rest);
10522 (title, fields)
10523 })
10524 .collect()
10525}
10526
10527fn parse_prepopulated_list(raw: &str) -> Vec<(String, Vec<String>)> {
10532 raw.split(',')
10533 .map(str::trim)
10534 .filter(|s| !s.is_empty())
10535 .filter_map(|entry| {
10536 let (target, sources_raw) = entry.split_once(':')?;
10537 let target = target.trim().to_owned();
10538 if target.is_empty() {
10539 return None;
10540 }
10541 let sources: Vec<String> = sources_raw
10542 .split('+')
10543 .map(|s| s.trim().to_owned())
10544 .filter(|s| !s.is_empty())
10545 .collect();
10546 if sources.is_empty() {
10547 return None;
10548 }
10549 Some((target, sources))
10550 })
10551 .collect()
10552}
10553
10554fn parse_formfield_overrides(raw: &str) -> Vec<(String, String)> {
10560 raw.split(',')
10561 .map(str::trim)
10562 .filter(|s| !s.is_empty())
10563 .filter_map(|entry| {
10564 let (field, widget) = entry.split_once(':')?;
10565 let field = field.trim().to_owned();
10566 let widget = widget.trim().to_owned();
10567 if field.is_empty() || widget.is_empty() {
10568 return None;
10569 }
10570 Some((field, widget))
10571 })
10572 .collect()
10573}
10574
10575fn parse_ordering_list(raw: &str) -> Vec<(String, bool)> {
10578 raw.split(',')
10579 .map(str::trim)
10580 .filter(|s| !s.is_empty())
10581 .map(|spec| {
10582 spec.strip_prefix('-')
10583 .map_or((spec.to_owned(), false), |rest| {
10584 (rest.trim().to_owned(), true)
10585 })
10586 })
10587 .collect()
10588}
10589
10590struct FieldAttrs {
10591 column: Option<String>,
10592 primary_key: bool,
10593 fk: Option<String>,
10594 o2o: Option<String>,
10595 on: Option<String>,
10596 on_delete: Option<String>,
10606 related_name: Option<String>,
10614 max_length: Option<u32>,
10615 vector_dims: Option<u32>,
10619 geometry_srid: Option<u32>,
10623 min: Option<i64>,
10624 max: Option<i64>,
10625 default: Option<String>,
10626 auto_uuid: bool,
10632 default_uuid_v7: bool,
10643 auto_now_add: bool,
10648 auto_now: bool,
10654 soft_delete: bool,
10659 unique: bool,
10662 index: bool,
10666 index_unique: bool,
10667 index_name: Option<String>,
10668 index_method: String,
10671 generated_as: Option<String>,
10677 help_text: Option<String>,
10682 choices: Option<Vec<(String, String)>>,
10690 db_comment: Option<String>,
10696 verbose_name: Option<String>,
10702 editable: bool,
10709 blank: bool,
10713 case_insensitive: bool,
10718 validators: Vec<syn::Ident>,
10724 secret: Option<bool>,
10726}
10727
10728const FIELD_ATTR_KEYS: &[&str] = &[
10730 "column",
10731 "primary_key",
10732 "fk",
10733 "o2o",
10734 "on",
10735 "on_delete",
10736 "related_name",
10737 "max_length",
10738 "vector",
10739 "geometry",
10740 "min",
10741 "max",
10742 "default",
10743 "generated_as",
10744 "help_text",
10745 "choices",
10746 "db_comment",
10747 "verbose_name",
10748 "editable",
10749 "secret",
10750 "blank",
10751 "citext",
10752 "validators",
10753 "auto_uuid",
10754 "default_uuid_v7",
10755 "auto_now_add",
10756 "auto_now",
10757 "soft_delete",
10758 "unique",
10759 "index",
10760];
10761
10762const CONTAINER_ATTR_KEYS: &[&str] = &[
10764 "table",
10765 "display",
10766 "app",
10767 "scope",
10768 "access",
10769 "admin",
10770 "manager",
10771 "manager_fn",
10772 "default_order",
10773 "global_scope",
10774 "through",
10775 "reverse_has",
10776 "generic_has",
10777 "audit",
10778 "permissions",
10779 "view",
10780 "managed",
10781 "verbose_name",
10782 "verbose_name_plural",
10783 "db_table_comment",
10784 "proxy",
10785 "order_with_respect_to",
10786 "required_db_features",
10787 "required_db_vendor",
10788 "base_manager_name",
10789 "default_related_name",
10790 "extra_permissions",
10791 "default_permissions",
10792 "get_latest_by",
10793 "unique_together",
10794 "index_together",
10795 "unique_when",
10796 "index_when",
10797 "index",
10798 "check",
10799 "exclude",
10800 "generic_fk",
10801 "fk_composite",
10802 "m2m",
10803 "generic_m2m",
10804];
10805
10806fn parse_flag(meta: &syn::meta::ParseNestedMeta<'_>, key: &str) -> syn::Result<()> {
10809 if meta.input.peek(syn::Token![=]) {
10810 let lit: syn::LitBool = meta.value()?.parse()?;
10811 if !lit.value {
10812 return Err(syn::Error::new(
10813 lit.span(),
10814 format!("`{key} = false` is the default; remove the attribute"),
10815 ));
10816 }
10817 }
10818 Ok(())
10819}
10820
10821fn unknown_key_error(
10823 meta: &syn::meta::ParseNestedMeta<'_>,
10824 what: &str,
10825 keys: &[&str],
10826) -> syn::Error {
10827 let path = &meta.path;
10828 let got = path
10829 .get_ident()
10830 .map_or_else(|| quote!(#path).to_string(), ToString::to_string);
10831 let mut msg = format!("unknown rustango {what} attribute `{got}`");
10832 if let Some(near) = keys
10833 .iter()
10834 .map(|k| (edit_distance(&got, k), *k))
10835 .filter(|(d, k)| *d <= 2.max(k.len() / 4))
10836 .min_by_key(|(d, _)| *d)
10837 .map(|(_, k)| k)
10838 {
10839 msg.push_str(&format!("; did you mean `{near}`?"));
10840 }
10841 msg.push_str(&format!(" Supported: {}", keys.join(", ")));
10842 meta.error(msg)
10843}
10844
10845fn edit_distance(a: &str, b: &str) -> usize {
10847 let b: Vec<char> = b.chars().collect();
10848 let mut prev: Vec<usize> = (0..=b.len()).collect();
10849 for (i, ca) in a.chars().enumerate() {
10850 let mut cur = vec![i + 1];
10851 for (j, cb) in b.iter().enumerate() {
10852 let sub = prev[j] + usize::from(ca != *cb);
10853 cur.push(sub.min(prev[j + 1] + 1).min(cur[j] + 1));
10854 }
10855 prev = cur;
10856 }
10857 prev[b.len()]
10858}
10859
10860fn parse_field_attrs(field: &syn::Field) -> syn::Result<FieldAttrs> {
10861 let mut out = FieldAttrs {
10862 column: None,
10863 primary_key: false,
10864 fk: None,
10865 o2o: None,
10866 on: None,
10867 on_delete: None,
10868 related_name: None,
10869 max_length: None,
10870 vector_dims: None,
10871 geometry_srid: None,
10872 min: None,
10873 max: None,
10874 default: None,
10875 auto_uuid: false,
10876 default_uuid_v7: false,
10877 auto_now_add: false,
10878 auto_now: false,
10879 soft_delete: false,
10880 unique: false,
10881 index: false,
10882 index_unique: false,
10883 index_name: None,
10884 index_method: "btree".to_owned(),
10885 generated_as: None,
10886 help_text: None,
10887 choices: None,
10888 db_comment: None,
10889 verbose_name: None,
10890 editable: true,
10891 blank: false,
10892 case_insensitive: false,
10893 validators: Vec::new(),
10894 secret: None,
10895 };
10896 for attr in model_attrs(&field.attrs)? {
10897 attr.parse_nested_meta(|meta| {
10898 if meta.path.is_ident("column") {
10899 let s: LitStr = meta.value()?.parse()?;
10900 let name = s.value();
10901 validate_sql_identifier(&name, "column", s.span())?;
10902 out.column = Some(name);
10903 return Ok(());
10904 }
10905 if meta.path.is_ident("primary_key") {
10906 parse_flag(&meta, "primary_key")?;
10907 out.primary_key = true;
10908 return Ok(());
10909 }
10910 if meta.path.is_ident("fk") {
10911 let s: LitStr = meta.value()?.parse()?;
10912 out.fk = Some(s.value());
10913 return Ok(());
10914 }
10915 if meta.path.is_ident("o2o") {
10916 let s: LitStr = meta.value()?.parse()?;
10917 out.o2o = Some(s.value());
10918 return Ok(());
10919 }
10920 if meta.path.is_ident("on") {
10921 let s: LitStr = meta.value()?.parse()?;
10922 out.on = Some(s.value());
10923 return Ok(());
10924 }
10925 if meta.path.is_ident("on_delete") {
10926 let s: LitStr = meta.value()?.parse()?;
10927 let raw = s.value();
10928 let normalized = raw.trim().to_ascii_lowercase();
10929 match normalized.as_str() {
10932 "cascade" | "restrict" | "set_null" | "set_default" | "no_action" => {}
10933 _ => {
10934 return Err(syn::Error::new(
10935 s.span(),
10936 format!(
10937 "unknown on_delete action `{raw}`; expected one of \
10938 `cascade`, `restrict`, `set_null`, `set_default`, `no_action`"
10939 ),
10940 ));
10941 }
10942 }
10943 out.on_delete = Some(normalized);
10944 return Ok(());
10945 }
10946 if meta.path.is_ident("related_name") {
10947 let s: LitStr = meta.value()?.parse()?;
10948 let raw = s.value();
10949 if raw.trim().is_empty() {
10950 return Err(syn::Error::new(
10951 s.span(),
10952 "`related_name` must be a non-empty identifier",
10953 ));
10954 }
10955 let snake = raw
10960 .chars()
10961 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
10962 && !raw.starts_with(char::is_numeric);
10963 if !snake && raw != NO_REVERSE_ACCESSOR {
10964 return Err(syn::Error::new(
10965 s.span(),
10966 "`related_name` must be snake_case ASCII (lowercase letters, \
10967 digits, underscores; no leading digit), or \"+\" for none",
10968 ));
10969 }
10970 out.related_name = Some(raw);
10971 return Ok(());
10972 }
10973 if meta.path.is_ident("max_length") {
10974 let lit: syn::LitInt = meta.value()?.parse()?;
10975 out.max_length = Some(lit.base10_parse::<u32>()?);
10976 return Ok(());
10977 }
10978 if meta.path.is_ident("vector") {
10982 meta.parse_nested_meta(|inner| {
10983 if inner.path.is_ident("dims") {
10984 let lit: syn::LitInt = inner.value()?.parse()?;
10985 out.vector_dims = Some(lit.base10_parse::<u32>()?);
10986 return Ok(());
10987 }
10988 Err(inner.error("unknown `vector` attribute (supported: `dims`)"))
10989 })?;
10990 return Ok(());
10991 }
10992 if meta.path.is_ident("geometry") {
10995 meta.parse_nested_meta(|inner| {
10996 if inner.path.is_ident("srid") {
10997 let lit: syn::LitInt = inner.value()?.parse()?;
10998 out.geometry_srid = Some(lit.base10_parse::<u32>()?);
10999 return Ok(());
11000 }
11001 Err(inner.error("unknown `geometry` attribute (supported: `srid`)"))
11002 })?;
11003 return Ok(());
11004 }
11005 if meta.path.is_ident("min") {
11006 out.min = Some(parse_signed_i64(&meta)?);
11007 return Ok(());
11008 }
11009 if meta.path.is_ident("max") {
11010 out.max = Some(parse_signed_i64(&meta)?);
11011 return Ok(());
11012 }
11013 if meta.path.is_ident("default") {
11014 let s: LitStr = meta.value()?.parse()?;
11015 out.default = Some(s.value());
11016 return Ok(());
11017 }
11018 if meta.path.is_ident("generated_as") {
11019 let s: LitStr = meta.value()?.parse()?;
11020 out.generated_as = Some(s.value());
11021 return Ok(());
11022 }
11023 if meta.path.is_ident("help_text") {
11024 let s: LitStr = meta.value()?.parse()?;
11025 out.help_text = Some(s.value());
11026 return Ok(());
11027 }
11028 if meta.path.is_ident("choices") {
11029 let s: LitStr = meta.value()?.parse()?;
11030 let raw = s.value();
11031 let mut pairs: Vec<(String, String)> = Vec::new();
11032 for chunk in raw.split(',') {
11033 let chunk = chunk.trim();
11034 if chunk.is_empty() {
11035 continue;
11036 }
11037 let (value, label) = match chunk.split_once(':') {
11038 Some((v, l)) => (v.trim().to_owned(), l.trim().to_owned()),
11039 None => (chunk.to_owned(), chunk.to_owned()),
11040 };
11041 if value.is_empty() {
11042 return Err(syn::Error::new(
11043 s.span(),
11044 "`choices` entry has empty value before `:`",
11045 ));
11046 }
11047 pairs.push((value, label));
11048 }
11049 if pairs.is_empty() {
11050 return Err(syn::Error::new(
11051 s.span(),
11052 "`choices = \"…\"` must contain at least one value",
11053 ));
11054 }
11055 out.choices = Some(pairs);
11056 return Ok(());
11057 }
11058 if meta.path.is_ident("db_comment") {
11059 let s: LitStr = meta.value()?.parse()?;
11060 out.db_comment = Some(s.value());
11061 return Ok(());
11062 }
11063 if meta.path.is_ident("verbose_name") {
11064 let s: LitStr = meta.value()?.parse()?;
11065 out.verbose_name = Some(s.value());
11066 return Ok(());
11067 }
11068 if meta.path.is_ident("secret") {
11069 out.secret = Some(if let Ok(v) = meta.value() {
11071 v.parse::<syn::LitBool>()?.value
11072 } else {
11073 true
11074 });
11075 return Ok(());
11076 }
11077 if meta.path.is_ident("editable") {
11078 if let Ok(v) = meta.value() {
11083 let lit: syn::LitBool = v.parse()?;
11084 out.editable = lit.value;
11085 } else {
11086 out.editable = true;
11087 }
11088 return Ok(());
11089 }
11090 if meta.path.is_ident("blank") {
11091 if let Ok(v) = meta.value() {
11095 let lit: syn::LitBool = v.parse()?;
11096 out.blank = lit.value;
11097 } else {
11098 out.blank = true;
11099 }
11100 return Ok(());
11101 }
11102 if meta.path.is_ident("citext") {
11103 if let Ok(v) = meta.value() {
11112 let lit: syn::LitBool = v.parse()?;
11113 out.case_insensitive = lit.value;
11114 } else {
11115 out.case_insensitive = true;
11116 }
11117 return Ok(());
11118 }
11119 if meta.path.is_ident("validators") {
11120 let s: LitStr = meta.value()?.parse()?;
11121 let raw = s.value();
11122 out.validators = raw
11123 .split(',')
11124 .map(str::trim)
11125 .filter(|s| !s.is_empty())
11126 .map(|name| {
11127 let mut ident = syn::parse_str::<syn::Ident>(name).map_err(|_| {
11128 syn::Error::new(s.span(), format!("unknown validator `{name}`"))
11129 })?;
11130 ident.set_span(s.span());
11131 Ok(ident)
11132 })
11133 .collect::<syn::Result<_>>()?;
11134 if out.validators.is_empty() {
11135 return Err(syn::Error::new(
11136 s.span(),
11137 "`validators = \"…\"` must list at least one name",
11138 ));
11139 }
11140 return Ok(());
11141 }
11142 if meta.path.is_ident("auto_uuid") {
11143 parse_flag(&meta, "auto_uuid")?;
11144 out.auto_uuid = true;
11145 out.primary_key = true;
11149 if out.default.is_none() {
11150 out.default = Some("gen_random_uuid()".into());
11151 }
11152 return Ok(());
11153 }
11154 if meta.path.is_ident("default_uuid_v7") {
11155 parse_flag(&meta, "default_uuid_v7")?;
11156 out.default_uuid_v7 = true;
11162 out.primary_key = true;
11163 return Ok(());
11164 }
11165 if meta.path.is_ident("auto_now_add") {
11166 parse_flag(&meta, "auto_now_add")?;
11167 out.auto_now_add = true;
11168 if out.default.is_none() {
11169 out.default = Some("now()".into());
11170 }
11171 return Ok(());
11172 }
11173 if meta.path.is_ident("auto_now") {
11174 parse_flag(&meta, "auto_now")?;
11175 out.auto_now = true;
11176 if out.default.is_none() {
11177 out.default = Some("now()".into());
11178 }
11179 return Ok(());
11180 }
11181 if meta.path.is_ident("soft_delete") {
11182 parse_flag(&meta, "soft_delete")?;
11183 out.soft_delete = true;
11184 return Ok(());
11185 }
11186 if meta.path.is_ident("unique") {
11187 parse_flag(&meta, "unique")?;
11188 out.unique = true;
11189 return Ok(());
11190 }
11191 if meta.path.is_ident("index") {
11192 parse_flag(&meta, "index")?;
11193 out.index = true;
11194 if meta.input.peek(syn::token::Paren) {
11196 meta.parse_nested_meta(|inner| {
11197 if inner.path.is_ident("unique") {
11198 out.index_unique = true;
11199 return Ok(());
11200 }
11201 if inner.path.is_ident("name") {
11202 let s: LitStr = inner.value()?.parse()?;
11203 out.index_name = Some(s.value());
11204 return Ok(());
11205 }
11206 if inner.path.is_ident("method") {
11207 let s: LitStr = inner.value()?.parse()?;
11208 let v = s.value();
11209 match v.as_str() {
11210 "btree" | "gin" | "gist" | "brin" | "spgist" | "hash" | "bloom" => {
11211 out.index_method = v;
11212 }
11213 other => {
11214 return Err(inner.error(format!(
11215 "unknown index method `{other}` (supported: btree, gin, gist, brin, spgist, hash, bloom)",
11216 )));
11217 }
11218 }
11219 return Ok(());
11220 }
11221 Err(inner.error(
11222 "unknown index sub-attribute (supported: `unique`, `name`, `method`)",
11223 ))
11224 })?;
11225 }
11226 return Ok(());
11227 }
11228 Err(unknown_key_error(&meta, "field", FIELD_ATTR_KEYS))
11229 })?;
11230 }
11231 Ok(out)
11232}
11233
11234fn parse_signed_i64(meta: &syn::meta::ParseNestedMeta<'_>) -> syn::Result<i64> {
11236 let expr: syn::Expr = meta.value()?.parse()?;
11237 match expr {
11238 syn::Expr::Lit(syn::ExprLit {
11239 lit: syn::Lit::Int(lit),
11240 ..
11241 }) => lit.base10_parse::<i64>(),
11242 syn::Expr::Unary(syn::ExprUnary {
11243 op: syn::UnOp::Neg(_),
11244 expr,
11245 ..
11246 }) => {
11247 if let syn::Expr::Lit(syn::ExprLit {
11248 lit: syn::Lit::Int(lit),
11249 ..
11250 }) = *expr
11251 {
11252 let v: i64 = lit.base10_parse()?;
11253 Ok(-v)
11254 } else {
11255 Err(syn::Error::new_spanned(expr, "expected integer literal"))
11256 }
11257 }
11258 other => Err(syn::Error::new_spanned(
11259 other,
11260 "expected integer literal (signed)",
11261 )),
11262 }
11263}
11264
11265struct FieldInfo<'a> {
11266 ident: &'a syn::Ident,
11267 column: String,
11268 primary_key: bool,
11269 auto: bool,
11273 value_ty: &'a Type,
11276 field_type_tokens: TokenStream2,
11278 schema: TokenStream2,
11279 from_row_init: TokenStream2,
11280 from_aliased_row_init: TokenStream2,
11286 from_row_init_my: TokenStream2,
11289 from_aliased_row_init_my: TokenStream2,
11290 uuid_column: bool,
11292 fk_inner: Option<Type>,
11296 fk_pk_kind: DetectedKind,
11302 nullable: bool,
11310 auto_now: bool,
11316 auto_now_add: bool,
11322 soft_delete: bool,
11327 generated_as: Option<String>,
11332 default_uuid_v7: bool,
11339 related_name: Option<String>,
11344}
11345
11346fn validate_table_name(name: &str, span: proc_macro2::Span) -> syn::Result<()> {
11360 validate_sql_identifier(name, "table", span)
11361}
11362
11363fn validate_sql_identifier(name: &str, kind: &str, span: proc_macro2::Span) -> syn::Result<()> {
11368 if name.is_empty() {
11369 return Err(syn::Error::new(
11370 span,
11371 format!("`{kind} = \"\"` is not a valid SQL identifier"),
11372 ));
11373 }
11374 let mut chars = name.chars();
11375 let first = chars.next().unwrap();
11376 if !(first.is_ascii_alphabetic() || first == '_') {
11377 return Err(syn::Error::new(
11378 span,
11379 format!("{kind} name `{name}` must start with a letter or underscore (got {first:?})"),
11380 ));
11381 }
11382 for c in chars {
11383 if !(c.is_ascii_alphanumeric() || c == '_') {
11384 return Err(syn::Error::new(
11385 span,
11386 format!(
11387 "{kind} name `{name}` contains invalid character {c:?} — \
11388 SQL identifiers must match `[a-zA-Z_][a-zA-Z0-9_]*`. \
11389 Hyphens in particular break FK / index name derivation \
11390 downstream; use underscores instead (e.g. `{}`)",
11391 name.replace(|x: char| !x.is_ascii_alphanumeric() && x != '_', "_"),
11392 ),
11393 ));
11394 }
11395 }
11396 Ok(())
11397}
11398
11399fn process_field<'a>(field: &'a syn::Field, table: &str) -> syn::Result<FieldInfo<'a>> {
11400 let root = rustango_root();
11401 let attrs = parse_field_attrs(field)?;
11402 let ident = field
11403 .ident
11404 .as_ref()
11405 .ok_or_else(|| syn::Error::new(field.span(), "tuple structs are not supported"))?;
11406 let name = ident_name(ident);
11407 let column = attrs.column.clone().unwrap_or_else(|| name.clone());
11408 let primary_key = attrs.primary_key;
11409 let DetectedType {
11410 kind,
11411 nullable,
11412 auto: detected_auto,
11413 fk_inner,
11414 } = detect_type(&field.ty)?;
11415 check_bound_compatibility(field, &attrs, kind)?;
11416 let auto = detected_auto;
11417 if attrs.auto_uuid {
11423 if kind != DetectedKind::Uuid {
11424 return Err(syn::Error::new_spanned(
11425 field,
11426 "`#[rustango(auto_uuid)]` requires the field type to be \
11427 `Auto<uuid::Uuid>`",
11428 ));
11429 }
11430 if !detected_auto {
11431 return Err(syn::Error::new_spanned(
11432 field,
11433 "`#[rustango(auto_uuid)]` requires the field type to be \
11434 wrapped in `Auto<...>` so the macro skips the column on \
11435 INSERT and the DB DEFAULT (`gen_random_uuid()`) fires",
11436 ));
11437 }
11438 }
11439 if attrs.default_uuid_v7 {
11440 if kind != DetectedKind::Uuid {
11441 return Err(syn::Error::new_spanned(
11442 field,
11443 "`#[rustango(default_uuid_v7)]` requires the field type to be \
11444 `Auto<uuid::Uuid>`",
11445 ));
11446 }
11447 if !detected_auto {
11448 return Err(syn::Error::new_spanned(
11449 field,
11450 "`#[rustango(default_uuid_v7)]` requires the field type to be \
11451 wrapped in `Auto<...>` so the macro can detect the \
11452 unset-vs-set state and fill a fresh UUIDv7 before INSERT",
11453 ));
11454 }
11455 if attrs.auto_uuid {
11456 return Err(syn::Error::new_spanned(
11457 field,
11458 "`#[rustango(default_uuid_v7)]` is mutually exclusive with \
11459 `#[rustango(auto_uuid)]` — the former generates the UUID \
11460 Rust-side, the latter relies on the DB's `gen_random_uuid()`. \
11461 Pick one.",
11462 ));
11463 }
11464 }
11465 if attrs.auto_now_add || attrs.auto_now {
11466 if kind != DetectedKind::DateTime {
11467 return Err(syn::Error::new_spanned(
11468 field,
11469 "`#[rustango(auto_now_add)]` / `#[rustango(auto_now)]` require \
11470 the field type to be `Auto<chrono::DateTime<chrono::Utc>>`",
11471 ));
11472 }
11473 if !detected_auto {
11474 return Err(syn::Error::new_spanned(
11475 field,
11476 "`#[rustango(auto_now_add)]` / `#[rustango(auto_now)]` require \
11477 the field type to be wrapped in `Auto<...>` so the macro skips \
11478 the column on INSERT and the DB DEFAULT (`now()`) fires",
11479 ));
11480 }
11481 }
11482 if attrs.soft_delete && !(kind == DetectedKind::DateTime && nullable) {
11483 return Err(syn::Error::new_spanned(
11484 field,
11485 "`#[rustango(soft_delete)]` requires the field type to be \
11486 `Option<chrono::DateTime<chrono::Utc>>`",
11487 ));
11488 }
11489 let is_mixin_auto =
11490 attrs.auto_uuid || attrs.default_uuid_v7 || attrs.auto_now_add || attrs.auto_now;
11491 if detected_auto && !primary_key && !is_mixin_auto {
11492 return Err(syn::Error::new_spanned(
11493 field,
11494 "`Auto<T>` is only valid on a `#[rustango(primary_key)]` field, \
11495 or on a field carrying one of `auto_uuid`, `auto_now_add`, or \
11496 `auto_now`",
11497 ));
11498 }
11499 if detected_auto && attrs.default.is_some() && !is_mixin_auto {
11500 return Err(syn::Error::new_spanned(
11501 field,
11502 "`#[rustango(default = \"…\")]` is redundant on an `Auto<T>` field — \
11503 SERIAL / BIGSERIAL already supplies a default sequence.",
11504 ));
11505 }
11506 if fk_inner.is_some() && primary_key {
11507 return Err(syn::Error::new_spanned(
11508 field,
11509 "`ForeignKey<T>` is not allowed on a primary-key field — \
11510 a row's PK is its own identity, not a reference to a parent.",
11511 ));
11512 }
11513 if attrs.generated_as.is_some() {
11514 if primary_key {
11515 return Err(syn::Error::new_spanned(
11516 field,
11517 "`#[rustango(generated_as = \"…\")]` is not allowed on a \
11518 primary-key field — a PK must be writable so the row \
11519 has an identity at INSERT time.",
11520 ));
11521 }
11522 if attrs.default.is_some() {
11523 return Err(syn::Error::new_spanned(
11524 field,
11525 "`#[rustango(generated_as = \"…\")]` cannot combine with \
11526 `default = \"…\"` — Postgres rejects DEFAULT on \
11527 generated columns. The expression IS the default.",
11528 ));
11529 }
11530 if detected_auto {
11531 return Err(syn::Error::new_spanned(
11532 field,
11533 "`#[rustango(generated_as = \"…\")]` is not allowed on \
11534 an `Auto<T>` field — generated columns are computed \
11535 by the DB, not server-assigned via a sequence. Use a \
11536 plain Rust type (e.g. `f64`).",
11537 ));
11538 }
11539 if fk_inner.is_some() {
11540 return Err(syn::Error::new_spanned(
11541 field,
11542 "`#[rustango(generated_as = \"…\")]` is not allowed on a \
11543 ForeignKey field.",
11544 ));
11545 }
11546 }
11547 let relation = relation_tokens(field, &attrs, fk_inner, table)?;
11548 let column_lit = column.as_str();
11549 let field_type_tokens = if kind == DetectedKind::Vector {
11554 let root = rustango_root();
11555 let dims = attrs.vector_dims.unwrap_or(0);
11556 quote!(#root::core::FieldType::Vector(#dims))
11557 } else if kind == DetectedKind::Geometry {
11558 let root = rustango_root();
11562 let srid = attrs.geometry_srid.unwrap_or(0);
11563 quote!(#root::core::FieldType::Geometry(#srid))
11564 } else {
11565 kind.variant_tokens()
11566 };
11567 let max_length = optional_u32(attrs.max_length);
11568 let min = optional_i64(attrs.min);
11569 let max = optional_i64(attrs.max);
11570 let default = optional_str(attrs.default.as_deref());
11571
11572 let unique = attrs.unique;
11573 let generated_as = optional_str(attrs.generated_as.as_deref());
11574 let help_text = optional_str(attrs.help_text.as_deref());
11575 let choices = optional_choices(attrs.choices.as_deref());
11576 let db_comment = optional_str(attrs.db_comment.as_deref());
11577 let verbose_name = optional_str(attrs.verbose_name.as_deref());
11578 let editable = attrs.editable;
11579 let secret = match attrs.secret {
11580 Some(v) => quote!(::core::option::Option::Some(#v)),
11581 None => quote!(::core::option::Option::None),
11582 };
11583 let blank = attrs.blank;
11584 let case_insensitive = attrs.case_insensitive;
11585 let validators = &attrs.validators;
11587 if attrs.on_delete.is_some() && attrs.fk.is_none() && attrs.o2o.is_none() && fk_inner.is_none()
11588 {
11589 return Err(syn::Error::new_spanned(
11590 field,
11591 "`#[rustango(on_delete = \"…\")]` requires a `ForeignKey<T>` field \
11592 or `fk = \"<table>\"` / `o2o = \"<table>\"` on the same field — it \
11593 has no meaning on a non-FK column.",
11594 ));
11595 }
11596 if attrs.on_delete.as_deref() == Some("set_null") && !nullable {
11597 return Err(syn::Error::new_spanned(
11598 field,
11599 "`on_delete = \"set_null\"` needs a nullable column: use \
11600 `Option<ForeignKey<T>>` or `Option<…>` (spelled out; a type \
11601 alias for `Option` is not seen).",
11602 ));
11603 }
11604 let fk_on_delete = match attrs.on_delete.as_deref() {
11605 None => quote!(::core::option::Option::None),
11606 Some(action) => {
11607 let variant = match action {
11608 "cascade" => quote!(Cascade),
11609 "restrict" => quote!(Restrict),
11610 "set_null" => quote!(SetNull),
11611 "set_default" => quote!(SetDefault),
11612 "no_action" => quote!(NoAction),
11613 other => unreachable!("on_delete `{other}` should have been rejected at parse"),
11615 };
11616 quote!(::core::option::Option::Some(
11617 #root::core::OnDeleteAction::#variant
11618 ))
11619 }
11620 };
11621 let auto_now_flag = attrs.auto_now;
11622 let cast = match cast_type(&field.ty) {
11623 Some(ty) => quote!(::core::option::Option::Some(<#ty>::FIELD_CAST)),
11624 None => quote!(::core::option::Option::None),
11625 };
11626 let sealed_guard = match (attrs.secret, cast_type(&field.ty)) {
11628 (Some(false), Some(ty)) => quote! {
11629 const _: () = ::core::assert!(
11630 !::core::matches!(<#ty>::FIELD_CAST, #root::core::FieldCast::Sealed),
11631 "`#[rustango(secret = false)]` on a sealed cast: it is always a credential",
11632 );
11633 },
11634 _ => quote!(),
11635 };
11636 let schema = quote! {{
11637 #sealed_guard
11638 #root::core::FieldSchema::from_parts(#root::core::FieldSchemaParts {
11639 name: #name,
11640 column: #column_lit,
11641 ty: #field_type_tokens,
11642 nullable: #nullable,
11643 primary_key: #primary_key,
11644 relation: #relation,
11645 max_length: #max_length,
11646 min: #min,
11647 max: #max,
11648 default: #default,
11649 auto: #auto,
11650 auto_now: #auto_now_flag,
11651 unique: #unique,
11652 generated_as: #generated_as,
11653 help_text: #help_text,
11654 choices: #choices,
11655 db_comment: #db_comment,
11656 verbose_name: #verbose_name,
11657 editable: #editable,
11658 blank: #blank,
11659 case_insensitive: #case_insensitive,
11660 fk_on_delete: #fk_on_delete,
11661 validators: &[ #( #root::core::__validator_names::#validators ),* ],
11662 cast: #cast,
11663 secret: #secret,
11664 })
11665 }};
11666
11667 let decimal_column = kind == DetectedKind::Decimal && !detected_auto && fk_inner.is_none();
11669 let (from_row_init, from_aliased_row_init) = if decimal_column {
11670 (
11671 quote! { #ident: #root::sql::try_get_flat(row, #column_lit)? },
11672 quote! {
11673 #ident: #root::sql::try_get_flat(
11674 row,
11675 #root::core::joins::joined_label(prefix, #column_lit).as_str(),
11676 )?
11677 },
11678 )
11679 } else {
11680 (
11681 quote! { #ident: #root::sql::sqlx::Row::try_get(row, #column_lit)? },
11682 quote! {
11683 #ident: #root::sql::sqlx::Row::try_get(
11684 row,
11685 #root::core::joins::joined_label(prefix, #column_lit).as_str(),
11686 )?
11687 },
11688 )
11689 };
11690 let uuid_column = kind == DetectedKind::Uuid && !detected_auto && fk_inner.is_none();
11693 let (from_row_init_my, from_aliased_row_init_my) = if uuid_column {
11694 (
11695 quote! { #ident: #root::sql::try_get_flat_my(row, #column_lit)? },
11696 quote! {
11697 #ident: #root::sql::try_get_flat_my(
11698 row,
11699 #root::core::joins::joined_label(prefix, #column_lit).as_str(),
11700 )?
11701 },
11702 )
11703 } else {
11704 (from_row_init.clone(), from_aliased_row_init.clone())
11705 };
11706
11707 Ok(FieldInfo {
11708 ident,
11709 column,
11710 primary_key,
11711 auto,
11712 value_ty: &field.ty,
11713 field_type_tokens,
11714 schema,
11715 from_row_init,
11716 from_aliased_row_init,
11717 from_row_init_my,
11718 from_aliased_row_init_my,
11719 uuid_column,
11720 fk_inner: fk_inner.cloned(),
11721 fk_pk_kind: kind,
11722 nullable,
11723 auto_now: attrs.auto_now,
11724 auto_now_add: attrs.auto_now_add,
11725 soft_delete: attrs.soft_delete,
11726 generated_as: attrs.generated_as.clone(),
11727 default_uuid_v7: attrs.default_uuid_v7,
11728 related_name: attrs.related_name.clone(),
11729 })
11730}
11731
11732fn check_bound_compatibility(
11733 field: &syn::Field,
11734 attrs: &FieldAttrs,
11735 kind: DetectedKind,
11736) -> syn::Result<()> {
11737 if attrs.max_length.is_some() && kind != DetectedKind::String {
11738 return Err(syn::Error::new_spanned(
11739 field,
11740 "`max_length` is only valid on `String` fields (or `Option<String>`)",
11741 ));
11742 }
11743 if attrs.case_insensitive && kind != DetectedKind::String {
11745 return Err(syn::Error::new_spanned(
11746 field,
11747 "`citext` is only valid on `String` fields (or `Option<String>`)",
11748 ));
11749 }
11750 if attrs.vector_dims.is_some() && kind != DetectedKind::Vector {
11751 return Err(syn::Error::new_spanned(
11752 field,
11753 "`vector(dims = …)` is only valid on a pgvector `Vector` field",
11754 ));
11755 }
11756 if attrs.geometry_srid.is_some() && kind != DetectedKind::Geometry {
11757 return Err(syn::Error::new_spanned(
11758 field,
11759 "`geometry(srid = …)` is only valid on a PostGIS `Point` field",
11760 ));
11761 }
11762 if attrs.choices.is_some() && kind != DetectedKind::String {
11763 return Err(syn::Error::new_spanned(
11764 field,
11765 "`choices` is only valid on `String` fields (or `Option<String>`) — \
11766 integer-valued enumerations should be modeled with a Rust enum and \
11767 custom (de)serializer for now",
11768 ));
11769 }
11770 if !attrs.validators.is_empty() && kind != DetectedKind::String {
11772 return Err(syn::Error::new_spanned(
11773 field,
11774 "`validators` is only valid on `String` fields (or `Option<String>`)",
11775 ));
11776 }
11777 if (attrs.min.is_some() || attrs.max.is_some()) && !kind.is_integer() {
11778 return Err(syn::Error::new_spanned(
11779 field,
11780 "`min` / `max` are only valid on integer fields (`i32`, `i64`, optionally Option-wrapped)",
11781 ));
11782 }
11783 if let (Some(min), Some(max)) = (attrs.min, attrs.max) {
11784 if min > max {
11785 return Err(syn::Error::new_spanned(
11786 field,
11787 format!("`min` ({min}) is greater than `max` ({max})"),
11788 ));
11789 }
11790 }
11791 if let Some(default) = &attrs.default {
11792 check_default_expr(default, kind).map_err(|msg| syn::Error::new_spanned(field, msg))?;
11793 }
11794 Ok(())
11795}
11796
11797const DEFAULT_KEYWORDS: &[&str] = &[
11800 "NULL",
11801 "TRUE",
11802 "FALSE",
11803 "CURRENT_TIMESTAMP",
11804 "CURRENT_DATE",
11805 "CURRENT_TIME",
11806 "LOCALTIMESTAMP",
11807 "LOCALTIME",
11808];
11809
11810fn check_default_expr(default: &str, kind: DetectedKind) -> Result<(), String> {
11814 let expr = default.trim();
11815 let is_word = expr.starts_with(|c: char| c.is_ascii_alphabetic() || c == '_')
11816 && expr
11817 .chars()
11818 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == ' ');
11819 let upper = expr.to_ascii_uppercase();
11820 if is_word && ["CURRENT_USER", "SESSION_USER", "USER"].contains(&upper.as_str()) {
11821 return Err(format!(
11822 "`default = \"{expr}\"` works on Postgres only: MySQL refuses it as a default \
11823 and SQLite stores the text `{expr}`; set the value in code instead"
11824 ));
11825 }
11826 if is_word && !DEFAULT_KEYWORDS.contains(&upper.as_str()) {
11827 return Err(format!(
11828 "`default = \"{expr}\"` is raw SQL, so `{expr}` is read as a column name; \
11829 for the text '{expr}' write `default = \"'{expr}'\"`"
11830 ));
11831 }
11832 let numeric = kind.is_integer()
11833 || matches!(
11834 kind,
11835 DetectedKind::F32 | DetectedKind::F64 | DetectedKind::Decimal
11836 );
11837 if numeric {
11838 let unquoted = expr
11839 .strip_prefix('\'')
11840 .and_then(|e| e.strip_suffix('\''))
11841 .unwrap_or(expr);
11842 let is_number = if kind.is_integer() {
11843 unquoted.parse::<i64>().is_ok()
11844 } else {
11845 unquoted.parse::<f64>().is_ok_and(f64::is_finite)
11846 };
11847 let is_literal = is_word || expr.is_empty() || expr.starts_with('\'');
11850 if is_literal && !is_number && upper != "NULL" {
11851 return Err(format!(
11852 "`default = \"{expr}\"` is not a number, but the field is numeric"
11853 ));
11854 }
11855 }
11856 Ok(())
11857}
11858
11859fn const_slice(ty: &TokenStream2, items: impl Iterator<Item = TokenStream2>) -> TokenStream2 {
11862 quote!({
11863 const ITEMS: &[#ty] = &[ #(#items),* ];
11864 ITEMS
11865 })
11866}
11867
11868fn optional_u32(value: Option<u32>) -> TokenStream2 {
11869 if let Some(v) = value {
11870 quote!(::core::option::Option::Some(#v))
11871 } else {
11872 quote!(::core::option::Option::None)
11873 }
11874}
11875
11876fn optional_i64(value: Option<i64>) -> TokenStream2 {
11877 if let Some(v) = value {
11878 quote!(::core::option::Option::Some(#v))
11879 } else {
11880 quote!(::core::option::Option::None)
11881 }
11882}
11883
11884fn optional_str(value: Option<&str>) -> TokenStream2 {
11885 if let Some(v) = value {
11886 quote!(::core::option::Option::Some(#v))
11887 } else {
11888 quote!(::core::option::Option::None)
11889 }
11890}
11891
11892fn optional_choices(pairs: Option<&[(String, String)]>) -> TokenStream2 {
11893 let Some(pairs) = pairs else {
11894 return quote!(::core::option::Option::None);
11895 };
11896 let entries = pairs.iter().map(|(v, l)| quote!((#v, #l)));
11897 quote!(::core::option::Option::Some(&[#(#entries),*]))
11898}
11899
11900fn relation_tokens(
11901 field: &syn::Field,
11902 attrs: &FieldAttrs,
11903 fk_inner: Option<&syn::Type>,
11904 table: &str,
11905) -> syn::Result<TokenStream2> {
11906 let root = rustango_root();
11907 if let Some(inner) = fk_inner {
11908 if attrs.fk.is_some() || attrs.o2o.is_some() {
11909 return Err(syn::Error::new_spanned(
11910 field,
11911 "`ForeignKey<T>` already declares the FK target via the type parameter — \
11912 remove the `fk = \"…\"` / `o2o = \"…\"` attribute.",
11913 ));
11914 }
11915 let on = match attrs.on.as_deref() {
11917 Some(on) => quote!(#on),
11918 None => quote! {
11919 match <#inner as #root::core::Model>::SCHEMA.pk_column() {
11920 ::core::option::Option::Some(c) => c,
11921 ::core::option::Option::None => "id",
11922 }
11923 },
11924 };
11925 return Ok(quote! {
11926 ::core::option::Option::Some(#root::core::Relation::fk(
11927 <#inner as #root::core::Model>::SCHEMA.table,
11928 #on,
11929 ))
11930 });
11931 }
11932 match (&attrs.fk, &attrs.o2o) {
11933 (Some(_), Some(_)) => Err(syn::Error::new_spanned(
11934 field,
11935 "`fk` and `o2o` are mutually exclusive",
11936 )),
11937 (Some(to), None) => {
11938 let on = attrs.on.as_deref().unwrap_or("id");
11939 let resolved = if to == "self" { table } else { to };
11945 Ok(quote! {
11946 ::core::option::Option::Some(#root::core::Relation::fk(#resolved, #on))
11947 })
11948 }
11949 (None, Some(to)) => {
11950 let on = attrs.on.as_deref().unwrap_or("id");
11951 let resolved = if to == "self" { table } else { to };
11952 Ok(quote! {
11953 ::core::option::Option::Some(#root::core::Relation::o2o(#resolved, #on))
11954 })
11955 }
11956 (None, None) => {
11957 if attrs.on.is_some() {
11958 return Err(syn::Error::new_spanned(
11959 field,
11960 "`on` requires `fk` or `o2o`",
11961 ));
11962 }
11963 Ok(quote!(::core::option::Option::None))
11964 }
11965 }
11966}
11967
11968#[derive(Clone, Copy, PartialEq, Eq)]
11972enum DetectedKind {
11973 I16,
11974 I32,
11975 I64,
11976 F32,
11977 F64,
11978 Bool,
11979 String,
11980 DateTime,
11981 Date,
11982 Time,
11983 Uuid,
11984 Json,
11985 Decimal,
11986 Binary,
11987 ArrayText,
11989 ArrayInt,
11991 ArrayBigInt,
11993 RangeInt,
11995 RangeBigInt,
11997 RangeNumeric,
11999 RangeDate,
12001 RangeDateTime,
12003 HStore,
12005 Vector,
12009 Geometry,
12013}
12014
12015impl DetectedKind {
12016 fn variant_tokens(self) -> TokenStream2 {
12017 let root = rustango_root();
12018 match self {
12019 Self::I16 => quote!(#root::core::FieldType::I16),
12020 Self::I32 => quote!(#root::core::FieldType::I32),
12021 Self::I64 => quote!(#root::core::FieldType::I64),
12022 Self::F32 => quote!(#root::core::FieldType::F32),
12023 Self::F64 => quote!(#root::core::FieldType::F64),
12024 Self::Bool => quote!(#root::core::FieldType::Bool),
12025 Self::String => quote!(#root::core::FieldType::String),
12026 Self::DateTime => quote!(#root::core::FieldType::DateTime),
12027 Self::Date => quote!(#root::core::FieldType::Date),
12028 Self::Time => quote!(#root::core::FieldType::Time),
12029 Self::Uuid => quote!(#root::core::FieldType::Uuid),
12030 Self::Json => quote!(#root::core::FieldType::Json),
12031 Self::Decimal => quote!(#root::core::FieldType::Decimal),
12032 Self::Binary => quote!(#root::core::FieldType::Binary),
12033 Self::ArrayText => {
12034 quote!(#root::core::FieldType::Array(#root::core::ArrayElem::Text))
12035 }
12036 Self::ArrayInt => {
12037 quote!(#root::core::FieldType::Array(#root::core::ArrayElem::Int))
12038 }
12039 Self::ArrayBigInt => {
12040 quote!(#root::core::FieldType::Array(#root::core::ArrayElem::BigInt))
12041 }
12042 Self::RangeInt => {
12043 quote!(#root::core::FieldType::Range(#root::core::RangeElem::Int))
12044 }
12045 Self::RangeBigInt => {
12046 quote!(#root::core::FieldType::Range(#root::core::RangeElem::BigInt))
12047 }
12048 Self::RangeNumeric => {
12049 quote!(#root::core::FieldType::Range(#root::core::RangeElem::Numeric))
12050 }
12051 Self::RangeDate => {
12052 quote!(#root::core::FieldType::Range(#root::core::RangeElem::Date))
12053 }
12054 Self::RangeDateTime => {
12055 quote!(#root::core::FieldType::Range(#root::core::RangeElem::DateTime))
12056 }
12057 Self::HStore => quote!(#root::core::FieldType::HStore),
12058 Self::Vector => quote!(#root::core::FieldType::Vector(0)),
12061 Self::Geometry => quote!(#root::core::FieldType::Geometry(0)),
12064 }
12065 }
12066
12067 fn is_integer(self) -> bool {
12068 matches!(self, Self::I16 | Self::I32 | Self::I64)
12069 }
12070}
12071
12072#[derive(Clone, Copy)]
12078struct DetectedType<'a> {
12079 kind: DetectedKind,
12080 nullable: bool,
12081 auto: bool,
12082 fk_inner: Option<&'a syn::Type>,
12083}
12084
12085fn auto_inner_type(ty: &syn::Type) -> Option<&syn::Type> {
12090 let Type::Path(TypePath { path, qself: None }) = ty else {
12091 return None;
12092 };
12093 let last = path.segments.last()?;
12094 if last.ident != "Auto" {
12095 return None;
12096 }
12097 let syn::PathArguments::AngleBracketed(args) = &last.arguments else {
12098 return None;
12099 };
12100 args.args.iter().find_map(|a| match a {
12101 syn::GenericArgument::Type(t) => Some(t),
12102 _ => None,
12103 })
12104}
12105
12106fn cast_type(ty: &syn::Type) -> Option<&syn::Type> {
12108 let Type::Path(TypePath { path, qself: None }) = ty else {
12109 return None;
12110 };
12111 let last = path.segments.last()?;
12112 if last.ident == "Cast" {
12113 return Some(ty);
12114 }
12115 if last.ident != "Option" {
12116 return None;
12117 }
12118 let syn::PathArguments::AngleBracketed(args) = &last.arguments else {
12119 return None;
12120 };
12121 match args.args.first()? {
12122 syn::GenericArgument::Type(inner) => cast_type(inner).filter(|_| args.args.len() == 1),
12123 _ => None,
12124 }
12125}
12126
12127fn detect_type(ty: &syn::Type) -> syn::Result<DetectedType<'_>> {
12128 let Type::Path(TypePath { path, qself: None }) = ty else {
12129 return Err(syn::Error::new_spanned(ty, "unsupported field type"));
12130 };
12131 let last = path
12132 .segments
12133 .last()
12134 .ok_or_else(|| syn::Error::new_spanned(ty, "empty type path"))?;
12135
12136 if last.ident == "Option" {
12137 let inner = generic_inner(ty, &last.arguments, "Option")?;
12138 let inner_det = detect_type(inner)?;
12139 if inner_det.nullable {
12140 return Err(syn::Error::new_spanned(
12141 ty,
12142 "nested Option is not supported",
12143 ));
12144 }
12145 if inner_det.auto {
12146 return Err(syn::Error::new_spanned(
12147 ty,
12148 "`Option<Auto<T>>` is not supported — Auto fields are server-assigned and cannot be NULL",
12149 ));
12150 }
12151 return Ok(DetectedType {
12152 nullable: true,
12153 ..inner_det
12154 });
12155 }
12156
12157 if last.ident == "Auto" {
12158 let inner = generic_inner(ty, &last.arguments, "Auto")?;
12159 let inner_det = detect_type(inner)?;
12160 if inner_det.auto {
12161 return Err(syn::Error::new_spanned(ty, "nested Auto is not supported"));
12162 }
12163 if inner_det.nullable {
12164 return Err(syn::Error::new_spanned(
12165 ty,
12166 "`Auto<Option<T>>` is not supported — Auto fields are server-assigned and cannot be NULL",
12167 ));
12168 }
12169 if inner_det.fk_inner.is_some() {
12170 return Err(syn::Error::new_spanned(
12171 ty,
12172 "`Auto<ForeignKey<T>>` is not supported — Auto is for server-assigned PKs, ForeignKey is for parent references",
12173 ));
12174 }
12175 if !matches!(
12176 inner_det.kind,
12177 DetectedKind::I32 | DetectedKind::I64 | DetectedKind::Uuid | DetectedKind::DateTime
12178 ) {
12179 return Err(syn::Error::new_spanned(
12180 ty,
12181 "`Auto<T>` only supports integers (`i32` → SERIAL, `i64` → BIGSERIAL), \
12182 `uuid::Uuid` (DEFAULT gen_random_uuid()), or `chrono::DateTime<chrono::Utc>` \
12183 (DEFAULT now())",
12184 ));
12185 }
12186 return Ok(DetectedType {
12187 auto: true,
12188 ..inner_det
12189 });
12190 }
12191
12192 if last.ident == "ForeignKey" {
12193 let (inner, key_ty) = generic_pair(ty, &last.arguments, "ForeignKey")?;
12194 let kind = match key_ty {
12202 Some(k) => detect_type(k)?.kind,
12203 None => DetectedKind::I64,
12204 };
12205 return Ok(DetectedType {
12206 kind,
12207 nullable: false,
12208 auto: false,
12209 fk_inner: Some(inner),
12210 });
12211 }
12212
12213 let kind = match last.ident.to_string().as_str() {
12214 "i16" => DetectedKind::I16,
12215 "i32" => DetectedKind::I32,
12216 "i64" => DetectedKind::I64,
12217 "f32" => DetectedKind::F32,
12218 "f64" => DetectedKind::F64,
12219 "bool" => DetectedKind::Bool,
12220 "String" => DetectedKind::String,
12221 "DateTime" => DetectedKind::DateTime,
12222 "NaiveDate" => DetectedKind::Date,
12223 "NaiveTime" => DetectedKind::Time,
12224 "Uuid" => DetectedKind::Uuid,
12225 "Value" => DetectedKind::Json,
12226 "Decimal" => DetectedKind::Decimal,
12227 "Vec" => {
12231 let (inner, _) = generic_pair(ty, &last.arguments, "Vec")?;
12232 if let Type::Path(TypePath { path, qself: None }) = inner {
12233 if let Some(seg) = path.segments.last() {
12234 if seg.ident == "u8" && seg.arguments.is_empty() {
12235 return Ok(DetectedType {
12236 kind: DetectedKind::Binary,
12237 nullable: false,
12238 auto: false,
12239 fk_inner: None,
12240 });
12241 }
12242 }
12243 }
12244 return Err(syn::Error::new_spanned(
12245 ty,
12246 "unsupported `Vec<T>` field — only `Vec<u8>` (→ Binary) is supported; \
12247 for a PostgreSQL array column use `Array<String>` / `Array<i32>` / `Array<i64>`",
12248 ));
12249 }
12250 "Array" => {
12253 let (inner, _) = generic_pair(ty, &last.arguments, "Array")?;
12254 let elem = match inner {
12255 Type::Path(TypePath { path, qself: None }) => {
12256 path.segments.last().map(|s| s.ident.to_string())
12257 }
12258 _ => None,
12259 };
12260 let kind = match elem.as_deref() {
12261 Some("String") => DetectedKind::ArrayText,
12262 Some("i32") => DetectedKind::ArrayInt,
12263 Some("i64") => DetectedKind::ArrayBigInt,
12264 _ => {
12265 return Err(syn::Error::new_spanned(
12266 ty,
12267 "unsupported `Array<T>` element — only `Array<String>` (→ text[]), \
12268 `Array<i32>` (→ integer[]), and `Array<i64>` (→ bigint[]) are supported (#341)",
12269 ));
12270 }
12271 };
12272 return Ok(DetectedType {
12273 kind,
12274 nullable: false,
12275 auto: false,
12276 fk_inner: None,
12277 });
12278 }
12279 "Range" => {
12283 let (inner, _) = generic_pair(ty, &last.arguments, "Range")?;
12284 let elem = match inner {
12285 Type::Path(TypePath { path, qself: None }) => {
12286 path.segments.last().map(|s| s.ident.to_string())
12287 }
12288 _ => None,
12289 };
12290 let kind = match elem.as_deref() {
12291 Some("i32") => DetectedKind::RangeInt,
12292 Some("i64") => DetectedKind::RangeBigInt,
12293 Some("Decimal") => DetectedKind::RangeNumeric,
12294 Some("NaiveDate") => DetectedKind::RangeDate,
12295 Some("DateTime") => DetectedKind::RangeDateTime,
12296 _ => {
12297 return Err(syn::Error::new_spanned(
12298 ty,
12299 "unsupported `Range<T>` element — only `Range<i32>` (→ int4range), \
12300 `Range<i64>` (→ int8range), `Range<Decimal>` (→ numrange), \
12301 `Range<NaiveDate>` (→ daterange), and `Range<DateTime<Utc>>` \
12302 (→ tstzrange) are supported (#343)",
12303 ));
12304 }
12305 };
12306 return Ok(DetectedType {
12307 kind,
12308 nullable: false,
12309 auto: false,
12310 fk_inner: None,
12311 });
12312 }
12313 "Cast" => {
12318 return Ok(DetectedType {
12319 kind: DetectedKind::String,
12320 nullable: false,
12321 auto: false,
12322 fk_inner: None,
12323 });
12324 }
12325 "HStore" => {
12328 return Ok(DetectedType {
12329 kind: DetectedKind::HStore,
12330 nullable: false,
12331 auto: false,
12332 fk_inner: None,
12333 });
12334 }
12335 "Vector" => {
12338 return Ok(DetectedType {
12339 kind: DetectedKind::Vector,
12340 nullable: false,
12341 auto: false,
12342 fk_inner: None,
12343 });
12344 }
12345 "Point" => {
12348 return Ok(DetectedType {
12349 kind: DetectedKind::Geometry,
12350 nullable: false,
12351 auto: false,
12352 fk_inner: None,
12353 });
12354 }
12355 other => {
12356 return Err(syn::Error::new_spanned(
12357 ty,
12358 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)"),
12359 ));
12360 }
12361 };
12362 Ok(DetectedType {
12363 kind,
12364 nullable: false,
12365 auto: false,
12366 fk_inner: None,
12367 })
12368}
12369
12370fn generic_inner<'a>(
12371 ty: &'a Type,
12372 arguments: &'a PathArguments,
12373 wrapper: &str,
12374) -> syn::Result<&'a Type> {
12375 let PathArguments::AngleBracketed(args) = arguments else {
12376 return Err(syn::Error::new_spanned(
12377 ty,
12378 format!("{wrapper} requires a generic argument"),
12379 ));
12380 };
12381 args.args
12382 .iter()
12383 .find_map(|a| match a {
12384 GenericArgument::Type(t) => Some(t),
12385 _ => None,
12386 })
12387 .ok_or_else(|| {
12388 syn::Error::new_spanned(ty, format!("{wrapper}<T> requires a type argument"))
12389 })
12390}
12391
12392fn generic_pair<'a>(
12396 ty: &'a Type,
12397 arguments: &'a PathArguments,
12398 wrapper: &str,
12399) -> syn::Result<(&'a Type, Option<&'a Type>)> {
12400 let PathArguments::AngleBracketed(args) = arguments else {
12401 return Err(syn::Error::new_spanned(
12402 ty,
12403 format!("{wrapper} requires a generic argument"),
12404 ));
12405 };
12406 let mut types = args.args.iter().filter_map(|a| match a {
12407 GenericArgument::Type(t) => Some(t),
12408 _ => None,
12409 });
12410 let first = types.next().ok_or_else(|| {
12411 syn::Error::new_spanned(ty, format!("{wrapper}<T> requires a type argument"))
12412 })?;
12413 let second = types.next();
12414 Ok((first, second))
12415}
12416
12417fn to_snake_case(s: &str) -> String {
12418 let mut out = String::with_capacity(s.len() + 4);
12419 for (i, ch) in s.chars().enumerate() {
12420 if ch.is_ascii_uppercase() {
12421 if i > 0 {
12422 out.push('_');
12423 }
12424 out.push(ch.to_ascii_lowercase());
12425 } else {
12426 out.push(ch);
12427 }
12428 }
12429 out
12430}
12431
12432#[derive(Default)]
12438struct FormFieldAttrs {
12439 min: Option<i64>,
12440 max: Option<i64>,
12441 min_length: Option<u32>,
12442 max_length: Option<u32>,
12443 clean: Option<syn::Ident>,
12450}
12451
12452#[derive(Default)]
12455struct FormContainerAttrs {
12456 validate: Option<syn::Ident>,
12462}
12463
12464#[derive(Clone, Copy)]
12466enum FormFieldKind {
12467 String,
12468 I16,
12469 I32,
12470 I64,
12471 F32,
12472 F64,
12473 Bool,
12474}
12475
12476impl FormFieldKind {
12477 fn parse_method(self) -> &'static str {
12478 match self {
12479 Self::I16 => "i16",
12480 Self::I32 => "i32",
12481 Self::I64 => "i64",
12482 Self::F32 => "f32",
12483 Self::F64 => "f64",
12484 Self::String | Self::Bool => "",
12487 }
12488 }
12489}
12490
12491fn form_local(name: &str) -> syn::Ident {
12493 syn::Ident::new(name, proc_macro2::Span::mixed_site())
12494}
12495
12496fn expand_form(input: &DeriveInput) -> syn::Result<TokenStream2> {
12497 let root = rustango_root();
12498 let (errs, cleaned, data_param) = (
12499 form_local("__errors"),
12500 form_local("__cleaned"),
12501 form_local("data"),
12502 );
12503 let struct_name = &input.ident;
12504
12505 let Data::Struct(data) = &input.data else {
12506 return Err(syn::Error::new_spanned(
12507 struct_name,
12508 "Form can only be derived on structs",
12509 ));
12510 };
12511 let Fields::Named(named) = &data.fields else {
12512 return Err(syn::Error::new_spanned(
12513 struct_name,
12514 "Form requires a struct with named fields",
12515 ));
12516 };
12517
12518 let container = parse_form_container_attrs(input)?;
12520 let post_field_clean: Vec<TokenStream2> = Vec::new();
12521 let _ = post_field_clean;
12522
12523 let mut field_blocks: Vec<TokenStream2> = Vec::with_capacity(named.named.len());
12524 let mut field_idents: Vec<&syn::Ident> = Vec::with_capacity(named.named.len());
12525
12526 for field in &named.named {
12527 let ident = field
12528 .ident
12529 .as_ref()
12530 .ok_or_else(|| syn::Error::new(field.span(), "tuple structs are not supported"))?;
12531 let attrs = parse_form_field_attrs(field)?;
12532 let (kind, nullable) = detect_form_field(&field.ty, field.span())?;
12533 let is_string = matches!(kind, FormFieldKind::String);
12534 let is_number = !is_string && !matches!(kind, FormFieldKind::Bool);
12535 if !is_string && (attrs.min_length.is_some() || attrs.max_length.is_some()) {
12536 return Err(syn::Error::new(
12537 field.span(),
12538 "`min_length` / `max_length` need a String field",
12539 ));
12540 }
12541 if !is_number && (attrs.min.is_some() || attrs.max.is_some()) {
12542 return Err(syn::Error::new(
12543 field.span(),
12544 "`min` / `max` need a numeric field",
12545 ));
12546 }
12547
12548 let name_lit = ident_name(ident);
12549 let parse_block = render_form_field_parse(ident, &name_lit, kind, nullable, &attrs);
12550 let clean_block = if let Some(clean_fn) = &attrs.clean {
12556 quote! {
12557 if #errs.fields().get(#name_lit).is_none() {
12558 match Self::#clean_fn(&#ident) {
12559 ::core::result::Result::Ok(#cleaned) => { #ident = #cleaned; }
12560 ::core::result::Result::Err(__msg) => {
12561 #errs.add(#name_lit, __msg);
12562 }
12563 }
12564 }
12565 }
12566 } else {
12567 quote! {}
12568 };
12569 field_blocks.push(quote! {
12570 #parse_block
12571 #clean_block
12572 });
12573 field_idents.push(ident);
12574 }
12575
12576 let cross_field_call = if let Some(validate_fn) = &container.validate {
12581 quote! {
12582 if #errs.is_empty() {
12583 let __candidate = Self { #( #field_idents ),* };
12584 if let ::core::result::Result::Err(__other) = Self::#validate_fn(&__candidate) {
12585 #errs.merge(__other);
12586 }
12587 if !#errs.is_empty() {
12588 return ::core::result::Result::Err(#errs);
12589 }
12590 return ::core::result::Result::Ok(__candidate);
12591 }
12592 }
12593 } else {
12594 quote! {}
12595 };
12596
12597 Ok(quote! {
12598 impl #root::forms::Form for #struct_name {
12599 fn parse(
12600 #data_param: &::std::collections::HashMap<::std::string::String, ::std::string::String>,
12601 ) -> ::core::result::Result<Self, #root::forms::FormErrors> {
12602 let mut #errs = #root::forms::FormErrors::default();
12603 #( #field_blocks )*
12604 #cross_field_call
12605 if !#errs.is_empty() {
12606 return ::core::result::Result::Err(#errs);
12607 }
12608 ::core::result::Result::Ok(Self {
12609 #( #field_idents ),*
12610 })
12611 }
12612 }
12613 })
12614}
12615
12616fn parse_form_container_attrs(input: &DeriveInput) -> syn::Result<FormContainerAttrs> {
12617 let mut out = FormContainerAttrs::default();
12618 for attr in &input.attrs {
12619 if !attr.path().is_ident("form") {
12620 continue;
12621 }
12622 attr.parse_nested_meta(|meta| {
12623 if meta.path.is_ident("validate") {
12624 let s: LitStr = meta.value()?.parse()?;
12625 out.validate = Some(syn::Ident::new(&s.value(), s.span()));
12626 return Ok(());
12627 }
12628 Err(meta.error("unknown form container attribute (supported: `validate`)"))
12629 })?;
12630 }
12631 Ok(out)
12632}
12633
12634fn parse_form_field_attrs(field: &syn::Field) -> syn::Result<FormFieldAttrs> {
12635 let mut out = FormFieldAttrs::default();
12636 for attr in &field.attrs {
12637 if !attr.path().is_ident("form") {
12638 continue;
12639 }
12640 attr.parse_nested_meta(|meta| {
12641 if meta.path.is_ident("min") {
12642 let lit: syn::LitInt = meta.value()?.parse()?;
12643 out.min = Some(lit.base10_parse::<i64>()?);
12644 return Ok(());
12645 }
12646 if meta.path.is_ident("max") {
12647 let lit: syn::LitInt = meta.value()?.parse()?;
12648 out.max = Some(lit.base10_parse::<i64>()?);
12649 return Ok(());
12650 }
12651 if meta.path.is_ident("min_length") {
12652 let lit: syn::LitInt = meta.value()?.parse()?;
12653 out.min_length = Some(lit.base10_parse::<u32>()?);
12654 return Ok(());
12655 }
12656 if meta.path.is_ident("max_length") {
12657 let lit: syn::LitInt = meta.value()?.parse()?;
12658 out.max_length = Some(lit.base10_parse::<u32>()?);
12659 return Ok(());
12660 }
12661 if meta.path.is_ident("clean") {
12662 let s: LitStr = meta.value()?.parse()?;
12663 out.clean = Some(syn::Ident::new(&s.value(), s.span()));
12664 return Ok(());
12665 }
12666 Err(meta.error(
12667 "unknown form field attribute (supported: `min`, `max`, `min_length`, `max_length`, `clean`)",
12668 ))
12669 })?;
12670 }
12671 Ok(out)
12672}
12673
12674fn detect_form_field(ty: &Type, span: proc_macro2::Span) -> syn::Result<(FormFieldKind, bool)> {
12675 let Type::Path(TypePath { path, qself: None }) = ty else {
12676 return Err(syn::Error::new(
12677 span,
12678 "Form field must be a simple typed path (e.g. `String`, `i32`, `Option<String>`)",
12679 ));
12680 };
12681 let last = path
12682 .segments
12683 .last()
12684 .ok_or_else(|| syn::Error::new(span, "empty type path"))?;
12685
12686 if last.ident == "Option" {
12687 let inner = generic_inner(ty, &last.arguments, "Option")?;
12688 let (kind, nested) = detect_form_field(inner, span)?;
12689 if nested {
12690 return Err(syn::Error::new(
12691 span,
12692 "nested Option in Form fields is not supported",
12693 ));
12694 }
12695 return Ok((kind, true));
12696 }
12697
12698 let kind = match last.ident.to_string().as_str() {
12699 "String" => FormFieldKind::String,
12700 "i16" => FormFieldKind::I16,
12701 "i32" => FormFieldKind::I32,
12702 "i64" => FormFieldKind::I64,
12703 "f32" => FormFieldKind::F32,
12704 "f64" => FormFieldKind::F64,
12705 "bool" => FormFieldKind::Bool,
12706 other => {
12707 return Err(syn::Error::new(
12708 span,
12709 format!(
12710 "Form field type `{other}` is not supported in v0.8 — use String / \
12711 i16 / i32 / i64 / f32 / f64 / bool, optionally wrapped in Option<…>"
12712 ),
12713 ));
12714 }
12715 };
12716 Ok((kind, false))
12717}
12718
12719#[allow(clippy::too_many_lines)]
12720fn render_form_field_parse(
12721 ident: &syn::Ident,
12722 name_lit: &str,
12723 kind: FormFieldKind,
12724 nullable: bool,
12725 attrs: &FormFieldAttrs,
12726) -> TokenStream2 {
12727 let (errs, data_param) = (form_local("__errors"), form_local("data"));
12728 let lookup = quote! {
12730 let __raw: ::core::option::Option<&::std::string::String> = #data_param.get(#name_lit);
12731 };
12732
12733 let root = rustango_root();
12734 let checkbox = quote! {
12736 match #root::forms::checkbox(__s) {
12737 ::core::option::Option::Some(__b) => __b,
12738 ::core::option::Option::None => {
12739 #errs.add(#name_lit, "Enter true or false.");
12740 false
12741 }
12742 }
12743 };
12744 let parsed_value = match kind {
12745 FormFieldKind::Bool if nullable => quote! {
12747 let __v: ::core::option::Option<bool> = match __raw {
12748 ::core::option::Option::None => ::core::option::Option::None,
12749 ::core::option::Option::Some(__s) if __s.is_empty() => {
12750 ::core::option::Option::None
12751 }
12752 ::core::option::Option::Some(__s) => ::core::option::Option::Some(#checkbox),
12753 };
12754 },
12755 FormFieldKind::Bool => quote! {
12756 let __v: bool = match __raw {
12757 ::core::option::Option::None => false,
12758 ::core::option::Option::Some(__s) => #checkbox,
12759 };
12760 },
12761 FormFieldKind::String => {
12762 if nullable {
12763 quote! {
12764 let __v: ::core::option::Option<::std::string::String> = match __raw {
12765 ::core::option::Option::None => ::core::option::Option::None,
12766 ::core::option::Option::Some(__s) if __s.is_empty() => {
12767 ::core::option::Option::None
12768 }
12769 ::core::option::Option::Some(__s) => {
12770 ::core::option::Option::Some(::core::clone::Clone::clone(__s))
12771 }
12772 };
12773 }
12774 } else {
12775 quote! {
12776 let __v: ::std::string::String = match __raw {
12777 ::core::option::Option::Some(__s) if !__s.is_empty() => {
12778 ::core::clone::Clone::clone(__s)
12779 }
12780 _ => {
12781 #errs.add(#name_lit, "This field is required.");
12782 ::std::string::String::new()
12783 }
12784 };
12785 }
12786 }
12787 }
12788 FormFieldKind::I16
12789 | FormFieldKind::I32
12790 | FormFieldKind::I64
12791 | FormFieldKind::F32
12792 | FormFieldKind::F64 => {
12793 let parse_ty = syn::Ident::new(kind.parse_method(), proc_macro2::Span::call_site());
12794 let ty_lit = kind.parse_method();
12795 let default_val = match kind {
12796 FormFieldKind::I16 => quote! { 0i16 },
12797 FormFieldKind::I32 => quote! { 0i32 },
12798 FormFieldKind::I64 => quote! { 0i64 },
12799 FormFieldKind::F32 => quote! { 0f32 },
12800 FormFieldKind::F64 => quote! { 0f64 },
12801 _ => quote! { Default::default() },
12802 };
12803 let finite = if matches!(kind, FormFieldKind::F32 | FormFieldKind::F64) {
12805 quote! {
12806 .and_then(|__n| if __n.is_finite() {
12807 ::core::result::Result::Ok(__n)
12808 } else {
12809 ::core::result::Result::Err(::std::string::String::from("not a finite number"))
12810 })
12811 }
12812 } else {
12813 quote! {}
12814 };
12815 if nullable {
12816 quote! {
12817 let __v: ::core::option::Option<#parse_ty> = match __raw {
12818 ::core::option::Option::None => ::core::option::Option::None,
12819 ::core::option::Option::Some(__s) if __s.is_empty() => {
12820 ::core::option::Option::None
12821 }
12822 ::core::option::Option::Some(__s) => {
12823 match __s.parse::<#parse_ty>().map_err(|__e| ::std::string::ToString::to_string(&__e)) #finite {
12824 ::core::result::Result::Ok(__n) => {
12825 ::core::option::Option::Some(__n)
12826 }
12827 ::core::result::Result::Err(__e) => {
12828 #errs.add(
12829 #name_lit,
12830 ::std::format!("Enter a valid {} value: {}", #ty_lit, __e),
12831 );
12832 ::core::option::Option::None
12833 }
12834 }
12835 }
12836 };
12837 }
12838 } else {
12839 quote! {
12840 let __v: #parse_ty = match __raw {
12841 ::core::option::Option::Some(__s) if !__s.is_empty() => {
12842 match __s.parse::<#parse_ty>().map_err(|__e| ::std::string::ToString::to_string(&__e)) #finite {
12843 ::core::result::Result::Ok(__n) => __n,
12844 ::core::result::Result::Err(__e) => {
12845 #errs.add(
12846 #name_lit,
12847 ::std::format!("Enter a valid {} value: {}", #ty_lit, __e),
12848 );
12849 #default_val
12850 }
12851 }
12852 }
12853 _ => {
12854 #errs.add(#name_lit, "This field is required.");
12855 #default_val
12856 }
12857 };
12858 }
12859 }
12860 }
12861 };
12862
12863 let validators = render_form_validators(name_lit, kind, nullable, attrs);
12864
12865 quote! {
12866 let mut #ident = {
12870 #lookup
12871 #parsed_value
12872 #validators
12873 __v
12874 };
12875 }
12876}
12877
12878fn render_form_validators(
12879 name_lit: &str,
12880 kind: FormFieldKind,
12881 nullable: bool,
12882 attrs: &FormFieldAttrs,
12883) -> TokenStream2 {
12884 let errs = form_local("__errors");
12885 let mut checks: Vec<TokenStream2> = Vec::new();
12886
12887 let val_ref = if nullable {
12888 quote! { __v.as_ref() }
12889 } else {
12890 quote! { ::core::option::Option::Some(&__v) }
12891 };
12892
12893 let is_string = matches!(kind, FormFieldKind::String);
12894 let is_numeric = matches!(
12895 kind,
12896 FormFieldKind::I16
12897 | FormFieldKind::I32
12898 | FormFieldKind::I64
12899 | FormFieldKind::F32
12900 | FormFieldKind::F64
12901 );
12902
12903 if is_string {
12904 if let Some(min_len) = attrs.min_length {
12905 let min_len_usize = min_len as usize;
12906 checks.push(quote! {
12907 if let ::core::option::Option::Some(__s) = #val_ref {
12908 if __s.chars().count() < #min_len_usize {
12909 #errs.add(
12910 #name_lit,
12911 ::std::format!("Ensure this value has at least {} characters.", #min_len_usize),
12912 );
12913 }
12914 }
12915 });
12916 }
12917 if let Some(max_len) = attrs.max_length {
12918 let max_len_usize = max_len as usize;
12919 checks.push(quote! {
12920 if let ::core::option::Option::Some(__s) = #val_ref {
12921 if __s.chars().count() > #max_len_usize {
12922 #errs.add(
12923 #name_lit,
12924 ::std::format!("Ensure this value has at most {} characters.", #max_len_usize),
12925 );
12926 }
12927 }
12928 });
12929 }
12930 }
12931
12932 if is_numeric {
12933 if let Some(min) = attrs.min {
12934 checks.push(quote! {
12935 if let ::core::option::Option::Some(__n) = #val_ref {
12936 if (*__n as f64) < (#min as f64) {
12937 #errs.add(
12938 #name_lit,
12939 ::std::format!("Ensure this value is greater than or equal to {}.", #min),
12940 );
12941 }
12942 }
12943 });
12944 }
12945 if let Some(max) = attrs.max {
12946 checks.push(quote! {
12947 if let ::core::option::Option::Some(__n) = #val_ref {
12948 if (*__n as f64) > (#max as f64) {
12949 #errs.add(
12950 #name_lit,
12951 ::std::format!("Ensure this value is less than or equal to {}.", #max),
12952 );
12953 }
12954 }
12955 });
12956 }
12957 }
12958
12959 quote! { #( #checks )* }
12960}
12961
12962struct ViewSetAttrs {
12967 model: syn::Path,
12968 fields: Vec<String>,
12969 filter_fields: Vec<String>,
12970 search_fields: Vec<String>,
12971 ordering: Vec<(String, bool)>,
12973 page_size: Option<usize>,
12974 read_only: bool,
12975 allow_anonymous: bool,
12976 perms: ViewSetPermsAttrs,
12977 serializer: Option<syn::Path>,
12982}
12983
12984#[derive(Default)]
12985struct ViewSetPermsAttrs {
12986 list: Vec<String>,
12987 retrieve: Vec<String>,
12988 create: Vec<String>,
12989 update: Vec<String>,
12990 destroy: Vec<String>,
12991}
12992
12993fn expand_viewset(input: &DeriveInput) -> syn::Result<TokenStream2> {
12994 let root = rustango_root();
12995 let struct_name = &input.ident;
12996
12997 match &input.data {
12999 Data::Struct(s) => match &s.fields {
13000 Fields::Unit | Fields::Named(_) => {}
13001 Fields::Unnamed(_) => {
13002 return Err(syn::Error::new_spanned(
13003 struct_name,
13004 "ViewSet can only be derived on a unit struct or an empty named struct",
13005 ));
13006 }
13007 },
13008 _ => {
13009 return Err(syn::Error::new_spanned(
13010 struct_name,
13011 "ViewSet can only be derived on a struct",
13012 ));
13013 }
13014 }
13015
13016 let attrs = parse_viewset_attrs(input)?;
13017 let model_path = &attrs.model;
13018
13019 let field_lits = attrs.fields.iter().map(|f| f.as_str());
13020
13021 let filter_fields_call = if attrs.filter_fields.is_empty() {
13022 quote!()
13023 } else {
13024 let lits = attrs.filter_fields.iter().map(|f| f.as_str());
13025 quote!(.filter_fields(&[ #(#lits),* ]))
13026 };
13027
13028 let search_fields_call = if attrs.search_fields.is_empty() {
13029 quote!()
13030 } else {
13031 let lits = attrs.search_fields.iter().map(|f| f.as_str());
13032 quote!(.search_fields(&[ #(#lits),* ]))
13033 };
13034
13035 let ordering_call = if attrs.ordering.is_empty() {
13036 quote!()
13037 } else {
13038 let pairs = attrs.ordering.iter().map(|(f, desc)| {
13039 let f = f.as_str();
13040 quote!((#f, #desc))
13041 });
13042 quote!(.ordering(&[ #(#pairs),* ]))
13043 };
13044
13045 let page_size_call = if let Some(n) = attrs.page_size {
13046 quote!(.page_size(#n))
13047 } else {
13048 quote!()
13049 };
13050
13051 let read_only_call = if attrs.read_only {
13052 quote!(.read_only())
13053 } else {
13054 quote!()
13055 };
13056 let allow_anonymous_call = if attrs.allow_anonymous {
13057 quote!(.allow_anonymous())
13058 } else {
13059 quote!()
13060 };
13061
13062 let serializer_call = if let Some(ref ser) = attrs.serializer {
13066 quote!(.serializer::<#ser>())
13067 } else {
13068 quote!()
13069 };
13070
13071 let perms = &attrs.perms;
13072 let perms_call = if perms.list.is_empty()
13073 && perms.retrieve.is_empty()
13074 && perms.create.is_empty()
13075 && perms.update.is_empty()
13076 && perms.destroy.is_empty()
13077 {
13078 quote!()
13079 } else {
13080 let list_lits = perms.list.iter().map(|s| s.as_str());
13081 let retrieve_lits = perms.retrieve.iter().map(|s| s.as_str());
13082 let create_lits = perms.create.iter().map(|s| s.as_str());
13083 let update_lits = perms.update.iter().map(|s| s.as_str());
13084 let destroy_lits = perms.destroy.iter().map(|s| s.as_str());
13085 quote! {
13086 .permissions(#root::viewset::ViewSetPerms {
13087 list: ::std::vec![ #(#list_lits.to_owned()),* ],
13088 retrieve: ::std::vec![ #(#retrieve_lits.to_owned()),* ],
13089 create: ::std::vec![ #(#create_lits.to_owned()),* ],
13090 update: ::std::vec![ #(#update_lits.to_owned()),* ],
13091 destroy: ::std::vec![ #(#destroy_lits.to_owned()),* ],
13092 })
13093 }
13094 };
13095
13096 Ok(quote! {
13097 impl #struct_name {
13098 pub fn router(
13107 prefix: &str,
13108 pool: impl ::core::convert::Into<#root::sql::Pool>,
13109 ) -> #root::__axum::Router {
13110 #root::viewset::ViewSet::for_model(
13111 <#model_path as #root::core::Model>::SCHEMA,
13112 &[ #(#field_lits),* ],
13113 )
13114 #filter_fields_call
13115 #search_fields_call
13116 #ordering_call
13117 #page_size_call
13118 #perms_call
13119 #read_only_call
13120 #allow_anonymous_call
13121 #serializer_call
13122 .router(prefix, pool.into())
13123 }
13124 }
13125 })
13126}
13127
13128fn parse_viewset_attrs(input: &DeriveInput) -> syn::Result<ViewSetAttrs> {
13129 let mut model: Option<syn::Path> = None;
13130 let mut fields: Option<Vec<String>> = None;
13131 let mut filter_fields: Vec<String> = Vec::new();
13132 let mut search_fields: Vec<String> = Vec::new();
13133 let mut ordering: Vec<(String, bool)> = Vec::new();
13134 let mut page_size: Option<usize> = None;
13135 let mut read_only = false;
13136 let mut allow_anonymous = false;
13137 let mut perms = ViewSetPermsAttrs::default();
13138 let mut serializer: Option<syn::Path> = None;
13139
13140 for attr in &input.attrs {
13141 if !attr.path().is_ident("viewset") {
13142 continue;
13143 }
13144 attr.parse_nested_meta(|meta| {
13145 if meta.path.is_ident("model") {
13146 let path: syn::Path = meta.value()?.parse()?;
13147 model = Some(path);
13148 return Ok(());
13149 }
13150 if meta.path.is_ident("serializer") {
13151 let path: syn::Path = meta.value()?.parse()?;
13152 serializer = Some(path);
13153 return Ok(());
13154 }
13155 if meta.path.is_ident("fields") {
13156 let s: LitStr = meta.value()?.parse()?;
13157 fields = Some(split_field_list(&s.value()));
13158 return Ok(());
13159 }
13160 if meta.path.is_ident("filter_fields") {
13161 let s: LitStr = meta.value()?.parse()?;
13162 filter_fields = split_field_list(&s.value());
13163 return Ok(());
13164 }
13165 if meta.path.is_ident("search_fields") {
13166 let s: LitStr = meta.value()?.parse()?;
13167 search_fields = split_field_list(&s.value());
13168 return Ok(());
13169 }
13170 if meta.path.is_ident("ordering") {
13171 let s: LitStr = meta.value()?.parse()?;
13172 ordering = parse_ordering_list(&s.value());
13173 return Ok(());
13174 }
13175 if meta.path.is_ident("page_size") {
13176 let lit: syn::LitInt = meta.value()?.parse()?;
13177 page_size = Some(lit.base10_parse::<usize>()?);
13178 return Ok(());
13179 }
13180 if meta.path.is_ident("read_only") {
13181 read_only = true;
13182 return Ok(());
13183 }
13184 if meta.path.is_ident("allow_anonymous") {
13185 allow_anonymous = true;
13186 return Ok(());
13187 }
13188 if meta.path.is_ident("permissions") {
13189 meta.parse_nested_meta(|inner| {
13190 let parse_codenames = |inner: &syn::meta::ParseNestedMeta| -> syn::Result<Vec<String>> {
13191 let s: LitStr = inner.value()?.parse()?;
13192 Ok(split_field_list(&s.value()))
13193 };
13194 if inner.path.is_ident("list") {
13195 perms.list = parse_codenames(&inner)?;
13196 } else if inner.path.is_ident("retrieve") {
13197 perms.retrieve = parse_codenames(&inner)?;
13198 } else if inner.path.is_ident("create") {
13199 perms.create = parse_codenames(&inner)?;
13200 } else if inner.path.is_ident("update") {
13201 perms.update = parse_codenames(&inner)?;
13202 } else if inner.path.is_ident("destroy") {
13203 perms.destroy = parse_codenames(&inner)?;
13204 } else {
13205 return Err(inner.error(
13206 "unknown permissions key (supported: list, retrieve, create, update, destroy)",
13207 ));
13208 }
13209 Ok(())
13210 })?;
13211 return Ok(());
13212 }
13213 Err(meta.error(
13214 "unknown viewset attribute (supported: model, fields, filter_fields, \
13215 search_fields, ordering, page_size, read_only, allow_anonymous, serializer, \
13216 permissions(...))",
13217 ))
13218 })?;
13219 }
13220
13221 let model = model.ok_or_else(|| {
13222 syn::Error::new_spanned(&input.ident, "`#[viewset(model = SomeModel)]` is required")
13223 })?;
13224 let fields = fields.ok_or_else(|| {
13226 syn::Error::new_spanned(
13227 &input.ident,
13228 "`#[viewset(fields = \"id, title\")]` is required: the fields the API renders and writes",
13229 )
13230 })?;
13231
13232 Ok(ViewSetAttrs {
13233 model,
13234 fields,
13235 filter_fields,
13236 search_fields,
13237 ordering,
13238 page_size,
13239 read_only,
13240 allow_anonymous,
13241 perms,
13242 serializer,
13243 })
13244}
13245
13246struct SerializerContainerAttrs {
13249 model: syn::Path,
13250 cross_validate: Option<syn::Ident>,
13258}
13259
13260#[derive(Default)]
13261struct SerializerFieldAttrs {
13262 read_only: bool,
13263 write_only: bool,
13264 source: Option<String>,
13265 skip: bool,
13266 method: Option<String>,
13270 validate: Option<String>,
13275 nested: bool,
13285 nested_strict: bool,
13290 many: Option<syn::Type>,
13299 slug: Option<String>,
13310 max_length: Option<u64>,
13314 min_length: Option<u64>,
13317 min: Option<i64>,
13320 max: Option<i64>,
13323}
13324
13325fn parse_serializer_container_attrs(input: &DeriveInput) -> syn::Result<SerializerContainerAttrs> {
13326 let mut model: Option<syn::Path> = None;
13327 let mut cross_validate: Option<syn::Ident> = None;
13328 for attr in &input.attrs {
13329 if !attr.path().is_ident("serializer") {
13330 continue;
13331 }
13332 attr.parse_nested_meta(|meta| {
13333 if meta.path.is_ident("model") {
13334 let _eq: syn::Token![=] = meta.input.parse()?;
13335 model = Some(meta.input.parse()?);
13336 return Ok(());
13337 }
13338 if meta.path.is_ident("validate") {
13339 let s: LitStr = meta.value()?.parse()?;
13344 cross_validate = Some(syn::Ident::new(&s.value(), s.span()));
13345 return Ok(());
13346 }
13347 Err(meta.error(
13348 "unknown serializer container attribute \
13349 (supported: `model`, `validate`)",
13350 ))
13351 })?;
13352 }
13353 let model = model.ok_or_else(|| {
13354 syn::Error::new_spanned(
13355 &input.ident,
13356 "`#[serializer(model = SomeModel)]` is required",
13357 )
13358 })?;
13359 Ok(SerializerContainerAttrs {
13360 model,
13361 cross_validate,
13362 })
13363}
13364
13365#[derive(Default)]
13368struct SerdeWireAttrs {
13369 rename_ser: Option<String>,
13370 rename_de: Option<String>,
13371 skip_ser: bool,
13372 skip_de: bool,
13373 skip_ser_if: Option<syn::ExprPath>,
13374}
13375
13376#[derive(Clone, Copy)]
13378enum SerdeRenameRule {
13379 Lower,
13380 Upper,
13381 Pascal,
13382 Camel,
13383 ScreamingSnake,
13384 Kebab,
13385 ScreamingKebab,
13386}
13387
13388impl SerdeRenameRule {
13389 fn parse(s: &LitStr) -> syn::Result<Self> {
13390 Ok(match s.value().as_str() {
13391 "lowercase" | "snake_case" => Self::Lower,
13392 "UPPERCASE" => Self::Upper,
13393 "PascalCase" => Self::Pascal,
13394 "camelCase" => Self::Camel,
13395 "SCREAMING_SNAKE_CASE" => Self::ScreamingSnake,
13396 "kebab-case" => Self::Kebab,
13397 "SCREAMING-KEBAB-CASE" => Self::ScreamingKebab,
13398 _ => {
13399 return Err(syn::Error::new_spanned(
13400 s,
13401 "unknown serde `rename_all` rule",
13402 ))
13403 }
13404 })
13405 }
13406
13407 fn apply(self, field: &str) -> String {
13409 match self {
13410 Self::Lower => field.to_owned(),
13411 Self::Upper | Self::ScreamingSnake => field.to_ascii_uppercase(),
13412 Self::Pascal | Self::Camel => {
13413 let mut out = String::new();
13414 let mut cap = matches!(self, Self::Pascal);
13415 for ch in field.chars() {
13416 if ch == '_' {
13417 cap = !out.is_empty() || matches!(self, Self::Pascal);
13418 } else if cap {
13419 out.push(ch.to_ascii_uppercase());
13420 cap = false;
13421 } else {
13422 out.push(ch);
13423 }
13424 }
13425 out
13426 }
13427 Self::Kebab => field.replace('_', "-"),
13428 Self::ScreamingKebab => field.to_ascii_uppercase().replace('_', "-"),
13429 }
13430 }
13431}
13432
13433fn skip_serde_meta_value(meta: &syn::meta::ParseNestedMeta) -> syn::Result<()> {
13435 if meta.input.peek(syn::Token![=]) {
13436 let _: syn::Expr = meta.value()?.parse()?;
13437 } else if meta.input.peek(syn::token::Paren) {
13438 let _: proc_macro2::Group = meta.input.parse()?;
13439 }
13440 Ok(())
13441}
13442
13443fn parse_serde_rename(
13445 meta: &syn::meta::ParseNestedMeta,
13446 mut set: impl FnMut(bool, LitStr) -> syn::Result<()>,
13447) -> syn::Result<()> {
13448 if meta.input.peek(syn::Token![=]) {
13449 let s: LitStr = meta.value()?.parse()?;
13450 set(true, s.clone())?;
13451 return set(false, s);
13452 }
13453 meta.parse_nested_meta(|inner| {
13454 let is_ser = inner.path.is_ident("serialize");
13455 if !is_ser && !inner.path.is_ident("deserialize") {
13456 return Err(inner.error("expected `serialize` or `deserialize`"));
13457 }
13458 set(is_ser, inner.value()?.parse()?)
13459 })
13460}
13461
13462fn parse_serde_container_rules(
13464 input: &DeriveInput,
13465) -> syn::Result<(Option<SerdeRenameRule>, Option<SerdeRenameRule>)> {
13466 let (mut ser, mut de) = (None, None);
13467 for attr in input.attrs.iter().filter(|a| a.path().is_ident("serde")) {
13468 attr.parse_nested_meta(|meta| {
13469 if meta.path.is_ident("rename_all") {
13470 return parse_serde_rename(&meta, |is_ser, s| {
13471 let rule = Some(SerdeRenameRule::parse(&s)?);
13472 if is_ser {
13473 ser = rule;
13474 } else {
13475 de = rule;
13476 }
13477 Ok(())
13478 });
13479 }
13480 skip_serde_meta_value(&meta)
13481 })?;
13482 }
13483 Ok((ser, de))
13484}
13485
13486fn parse_serde_field_attrs(field: &syn::Field) -> syn::Result<SerdeWireAttrs> {
13487 let mut out = SerdeWireAttrs::default();
13488 for attr in field.attrs.iter().filter(|a| a.path().is_ident("serde")) {
13489 attr.parse_nested_meta(|meta| {
13490 if meta.path.is_ident("rename") {
13491 return parse_serde_rename(&meta, |is_ser, s| {
13492 if is_ser {
13493 out.rename_ser = Some(s.value());
13494 } else {
13495 out.rename_de = Some(s.value());
13496 }
13497 Ok(())
13498 });
13499 }
13500 if meta.path.is_ident("skip") {
13501 out.skip_ser = true;
13502 out.skip_de = true;
13503 return Ok(());
13504 }
13505 if meta.path.is_ident("skip_serializing") {
13506 out.skip_ser = true;
13507 return Ok(());
13508 }
13509 if meta.path.is_ident("skip_deserializing") {
13510 out.skip_de = true;
13511 return Ok(());
13512 }
13513 if meta.path.is_ident("skip_serializing_if") {
13514 let s: LitStr = meta.value()?.parse()?;
13515 out.skip_ser_if = Some(s.parse()?);
13516 return Ok(());
13517 }
13518 skip_serde_meta_value(&meta)
13519 })?;
13520 }
13521 Ok(out)
13522}
13523
13524fn parse_serializer_field_attrs(field: &syn::Field) -> syn::Result<SerializerFieldAttrs> {
13525 let mut out = SerializerFieldAttrs::default();
13526 for attr in &field.attrs {
13527 if !attr.path().is_ident("serializer") {
13528 continue;
13529 }
13530 attr.parse_nested_meta(|meta| {
13531 if meta.path.is_ident("read_only") {
13532 out.read_only = true;
13533 return Ok(());
13534 }
13535 if meta.path.is_ident("write_only") {
13536 out.write_only = true;
13537 return Ok(());
13538 }
13539 if meta.path.is_ident("skip") {
13540 out.skip = true;
13541 return Ok(());
13542 }
13543 if meta.path.is_ident("source") {
13544 let s: LitStr = meta.value()?.parse()?;
13545 out.source = Some(s.value());
13546 return Ok(());
13547 }
13548 if meta.path.is_ident("method") {
13549 let s: LitStr = meta.value()?.parse()?;
13550 out.method = Some(s.value());
13551 return Ok(());
13552 }
13553 if meta.path.is_ident("validate") {
13554 let s: LitStr = meta.value()?.parse()?;
13555 out.validate = Some(s.value());
13556 return Ok(());
13557 }
13558 if meta.path.is_ident("many") {
13559 let _eq: syn::Token![=] = meta.input.parse()?;
13560 out.many = Some(meta.input.parse()?);
13561 return Ok(());
13562 }
13563 if meta.path.is_ident("nested") {
13564 out.nested = true;
13565 if meta.input.peek(syn::token::Paren) {
13568 meta.parse_nested_meta(|inner| {
13569 if inner.path.is_ident("strict") {
13570 out.nested_strict = true;
13571 return Ok(());
13572 }
13573 Err(inner.error("unknown nested sub-attribute (supported: `strict`)"))
13574 })?;
13575 }
13576 return Ok(());
13577 }
13578 if meta.path.is_ident("slug") {
13579 let s: LitStr = meta.value()?.parse()?;
13580 out.slug = Some(s.value());
13581 return Ok(());
13582 }
13583 if meta.path.is_ident("max_length") {
13584 let lit: syn::LitInt = meta.value()?.parse()?;
13585 out.max_length = Some(lit.base10_parse::<u64>()?);
13586 return Ok(());
13587 }
13588 if meta.path.is_ident("min_length") {
13589 let lit: syn::LitInt = meta.value()?.parse()?;
13590 out.min_length = Some(lit.base10_parse::<u64>()?);
13591 return Ok(());
13592 }
13593 if meta.path.is_ident("min") {
13594 let lit: syn::LitInt = meta.value()?.parse()?;
13595 out.min = Some(lit.base10_parse::<i64>()?);
13596 return Ok(());
13597 }
13598 if meta.path.is_ident("max") {
13599 let lit: syn::LitInt = meta.value()?.parse()?;
13600 out.max = Some(lit.base10_parse::<i64>()?);
13601 return Ok(());
13602 }
13603 Err(meta.error(
13604 "unknown serializer field attribute (supported: \
13605 `read_only`, `write_only`, `source`, `skip`, `method`, \
13606 `validate`, `nested`, `many`, `slug`, `max_length`, \
13607 `min_length`, `min`, `max`)",
13608 ))
13609 })?;
13610 }
13611 if out.read_only && out.write_only {
13613 return Err(syn::Error::new_spanned(
13614 field,
13615 "a field cannot be both `read_only` and `write_only`",
13616 ));
13617 }
13618 if out.method.is_some() && out.source.is_some() {
13619 return Err(syn::Error::new_spanned(
13620 field,
13621 "`method` and `source` are mutually exclusive — `method` computes \
13622 the value from a method, `source` reads it from a different model field",
13623 ));
13624 }
13625 if out.slug.is_some() && (out.method.is_some() || out.nested || out.many.is_some()) {
13626 return Err(syn::Error::new_spanned(
13627 field,
13628 "`slug` is mutually exclusive with `method`, `nested`, and `many` \
13629 — pick one strategy for populating the field",
13630 ));
13631 }
13632 Ok(out)
13633}
13634
13635fn expand_serializer(input: &DeriveInput) -> syn::Result<TokenStream2> {
13636 let root = rustango_root();
13637 let struct_name = &input.ident;
13638 let struct_name_lit = struct_name.to_string();
13639
13640 let Data::Struct(data) = &input.data else {
13641 return Err(syn::Error::new_spanned(
13642 struct_name,
13643 "Serializer can only be derived on structs",
13644 ));
13645 };
13646 let Fields::Named(named) = &data.fields else {
13647 return Err(syn::Error::new_spanned(
13648 struct_name,
13649 "Serializer requires a struct with named fields",
13650 ));
13651 };
13652
13653 let container = parse_serializer_container_attrs(input)?;
13654 let model_path = &container.model;
13655
13656 #[allow(dead_code)]
13660 struct FieldInfo {
13661 ident: syn::Ident,
13662 ty: syn::Type,
13663 attrs: SerializerFieldAttrs,
13664 serde: SerdeWireAttrs,
13665 ser_name: String,
13667 de_name: String,
13668 }
13669 let (ser_rule, de_rule) = parse_serde_container_rules(input)?;
13670 let mut fields_info: Vec<FieldInfo> = Vec::new();
13671 for field in &named.named {
13672 let ident = field.ident.clone().expect("named field has ident");
13673 let attrs = parse_serializer_field_attrs(field)?;
13674 let serde = parse_serde_field_attrs(field)?;
13675 let name = ident_name(&ident);
13676 let wire = |rename: &Option<String>, rule: Option<SerdeRenameRule>| {
13677 rename
13678 .clone()
13679 .unwrap_or_else(|| rule.map_or_else(|| name.clone(), |r| r.apply(&name)))
13680 };
13681 fields_info.push(FieldInfo {
13682 ser_name: wire(&serde.rename_ser, ser_rule),
13683 de_name: wire(&serde.rename_de, de_rule),
13684 ident,
13685 ty: field.ty.clone(),
13686 attrs,
13687 serde,
13688 });
13689 }
13690
13691 let from_model_fields = fields_info.iter().map(|fi| {
13693 let ident = &fi.ident;
13694 if let Some(_inner) = &fi.attrs.many {
13695 quote! { #ident: ::std::vec::Vec::new() }
13699 } else if let Some(method) = &fi.attrs.method {
13700 let method_ident = syn::Ident::new(method, ident.span());
13704 quote! { #ident: Self::#method_ident(model) }
13705 } else if let Some(slug_field) = &fi.attrs.slug {
13706 let src_name = fi
13715 .attrs
13716 .source
13717 .as_deref()
13718 .unwrap_or(&ident_name(&fi.ident))
13719 .to_owned();
13720 let src_ident = ident_from_name(&src_name, ident.span());
13721 let slug_ident = syn::Ident::new(slug_field, ident.span());
13722 quote! {
13723 #ident: #root::serializer::LoadedRelation::render(
13724 &model.#src_ident,
13725 |__loaded| ::core::clone::Clone::clone(&__loaded.#slug_ident),
13726 ::core::default::Default::default,
13727 )
13728 }
13729 } else if fi.attrs.nested {
13730 let src_name = fi.attrs.source.as_deref().unwrap_or(&ident_name(&fi.ident)).to_owned();
13748 let src_ident = ident_from_name(&src_name, ident.span());
13749 let unloaded = if fi.attrs.nested_strict {
13750 let panic_msg = format!(
13751 "nested(strict) serializer for `{ident}` requires `model.{src_name}` to be loaded — \
13752 call .get(&pool).await? or .select_related(\"{src_name}\") on the model first",
13753 );
13754 quote! { || ::core::panic!("{}", #panic_msg) }
13755 } else {
13756 quote! { ::core::default::Default::default }
13757 };
13758 quote! {
13759 #ident: #root::serializer::LoadedRelation::render(
13760 &model.#src_ident,
13761 |__loaded| #root::serializer::ModelSerializer::from_model(__loaded),
13762 #unloaded,
13763 )
13764 }
13765 } else if fi.attrs.write_only || fi.attrs.skip {
13766 quote! { #ident: ::core::default::Default::default() }
13768 } else if let Some(src) = &fi.attrs.source {
13769 let src_ident = ident_from_name(src, ident.span());
13770 quote! { #ident: ::core::clone::Clone::clone(&model.#src_ident) }
13771 } else {
13772 quote! { #ident: ::core::clone::Clone::clone(&model.#ident) }
13773 }
13774 });
13775
13776 let is_writable = |fi: &&FieldInfo| {
13779 !fi.attrs.read_only
13780 && !fi.attrs.skip
13781 && !fi.serde.skip_de
13782 && fi.attrs.method.is_none()
13783 && !fi.attrs.nested
13784 && fi.attrs.many.is_none()
13785 && fi.attrs.slug.is_none()
13786 };
13787
13788 let opt_usize = |v: Option<u64>| match v {
13796 Some(n) => {
13797 let n = n as usize;
13798 quote!(::core::option::Option::Some(#n))
13799 }
13800 None => quote!(::core::option::Option::None),
13801 };
13802 #[cfg(feature = "openapi")]
13803 let opt_u64 = |v: Option<u64>| match v {
13804 Some(n) => quote!(::core::option::Option::Some(#n)),
13805 None => quote!(::core::option::Option::None),
13806 };
13807 let opt_i64 = |v: Option<i64>| match v {
13808 Some(n) => quote!(::core::option::Option::Some(#n)),
13809 None => quote!(::core::option::Option::None),
13810 };
13811 let constraint_blocks: Vec<_> = fields_info
13812 .iter()
13813 .filter(is_writable)
13814 .map(|fi| {
13815 let ident = &fi.ident;
13816 let fname = &fi.de_name;
13817 let mname = fi
13818 .attrs
13819 .source
13820 .clone()
13821 .unwrap_or_else(|| ident_name(&fi.ident));
13822 let attr_max_len = opt_usize(fi.attrs.max_length);
13823 let attr_min_len = opt_usize(fi.attrs.min_length);
13824 let attr_min = opt_i64(fi.attrs.min);
13825 let attr_max = opt_i64(fi.attrs.max);
13826 let ty = &fi.ty;
13828 let len_check = (fi.attrs.max_length.is_some() || fi.attrs.min_length.is_some())
13829 .then(|| quote! { #root::forms::validators::assert_length_bounded::<#ty>(); });
13830 let num_check = (fi.attrs.min.is_some() || fi.attrs.max.is_some())
13831 .then(|| quote! { #root::forms::validators::assert_number_bounded::<#ty>(); });
13832 quote! {
13833 #len_check
13834 #num_check
13835 if __sent(#fname) {
13836 let __sf = <#model_path as #root::core::Model>::SCHEMA.field(#mname);
13837 let __max_length: ::core::option::Option<usize> = #attr_max_len
13838 .or_else(|| __sf.and_then(|__f| __f.max_length).map(|__n| __n as usize));
13839 let __min_length: ::core::option::Option<usize> = #attr_min_len;
13840 let __min: ::core::option::Option<i64> =
13841 #attr_min.or_else(|| __sf.and_then(|__f| __f.min));
13842 let __max: ::core::option::Option<i64> =
13843 #attr_max.or_else(|| __sf.and_then(|__f| __f.max));
13844 let __choices = __sf.and_then(|__f| __f.choices);
13845 let __v = #root::__serde_json::to_value(&self.#ident)
13846 .unwrap_or(#root::__serde_json::Value::Null);
13847 #root::forms::validators::check_value(
13848 #fname, &__v, __max_length, __min_length, __min, __max, __choices,
13849 &mut __errors,
13850 );
13851 }
13852 }
13853 })
13854 .collect();
13855 let has_constraints = !constraint_blocks.is_empty();
13856
13857 let validator_calls: Vec<_> = fields_info
13861 .iter()
13862 .filter_map(|fi| {
13863 let ident = &fi.ident;
13864 let name_lit = &fi.de_name;
13865 let method = fi.attrs.validate.as_ref()?;
13866 let method_ident = syn::Ident::new(method, ident.span());
13867 Some(quote! {
13868 if __sent(#name_lit) {
13869 if let ::core::result::Result::Err(__e) = Self::#method_ident(&self.#ident) {
13870 __errors.add(#name_lit.to_owned(), __e);
13871 }
13872 }
13873 })
13874 })
13875 .collect();
13876 let cross_validate_call = container.cross_validate.as_ref().map(|method_ident| {
13883 quote! {
13884 if let ::core::result::Result::Err(__cross) = self.#method_ident() {
13887 __errors.merge(__cross);
13888 }
13889 }
13890 });
13891 let has_validators = !validator_calls.is_empty() || container.cross_validate.is_some();
13892 let has_run_validations = has_validators || has_constraints;
13895 let checks = quote! {
13899 let mut __errors = #root::forms::FormErrors::default();
13900 #( #constraint_blocks )*
13901 #( #validator_calls )*
13902 #cross_validate_call
13903 if __errors.is_empty() {
13904 ::core::result::Result::Ok(())
13905 } else {
13906 ::core::result::Result::Err(__errors)
13907 }
13908 };
13909 let validate_body = quote! {
13910 let __sent = |_: &str| true;
13911 #checks
13912 };
13913 let validate_patch_body = quote! {
13915 let __sent = |__n: &str| __obj.is_some_and(|__o| __o.contains_key(__n));
13916 #checks
13917 };
13918 let validate_method = if has_validators {
13924 quote! {
13925 impl #struct_name {
13926 pub fn validate(&self) -> ::core::result::Result<(), #root::forms::FormErrors> {
13932 #validate_body
13933 }
13934 }
13935 }
13936 } else {
13937 quote! {}
13938 };
13939 let trait_validate_override = if has_run_validations {
13944 quote! {
13945 fn validate(&self) -> ::core::result::Result<(), #root::forms::FormErrors> {
13946 #validate_body
13947 }
13948 }
13949 } else {
13950 quote! {}
13951 };
13952
13953 let many_setters: Vec<_> = fields_info
13957 .iter()
13958 .filter_map(|fi| {
13959 let many_ty = fi.attrs.many.as_ref()?;
13960 let ident = &fi.ident;
13961 let setter = syn::Ident::new(&format!("set_{}", ident_name(ident)), ident.span());
13962 Some(quote! {
13963 pub fn #setter(
13968 &mut self,
13969 models: &[<#many_ty as #root::serializer::ModelSerializer>::Model],
13970 ) -> &mut Self {
13971 self.#ident = models.iter()
13972 .map(<#many_ty as #root::serializer::ModelSerializer>::from_model)
13973 .collect();
13974 self
13975 }
13976 })
13977 })
13978 .collect();
13979 let many_setters_impl = if many_setters.is_empty() {
13980 quote! {}
13981 } else {
13982 quote! {
13983 impl #struct_name {
13984 #( #many_setters )*
13985 }
13986 }
13987 };
13988
13989 let output_fields: Vec<_> = fields_info
13991 .iter()
13992 .filter(|fi| !fi.attrs.write_only && !fi.serde.skip_ser)
13993 .collect();
13994 let output_field_count = output_fields.iter().map(|fi| {
13996 let ident = &fi.ident;
13997 if let Some(pred) = &fi.serde.skip_ser_if {
13998 quote! { + if #pred(&self.#ident) { 0 } else { 1 } }
13999 } else {
14000 quote! { + 1 }
14001 }
14002 });
14003 let output_field_count = quote! { 0usize #( #output_field_count )* };
14004 let serialize_fields = output_fields.iter().map(|fi| {
14005 let ident = &fi.ident;
14006 let name_lit = &fi.ser_name;
14007 if let Some(pred) = &fi.serde.skip_ser_if {
14008 quote! {
14009 if #pred(&self.#ident) {
14010 __state.skip_field(#name_lit)?;
14011 } else {
14012 __state.serialize_field(#name_lit, &self.#ident)?;
14013 }
14014 }
14015 } else {
14016 quote! { __state.serialize_field(#name_lit, &self.#ident)?; }
14017 }
14018 });
14019
14020 let writable_lits: Vec<_> = fields_info
14035 .iter()
14036 .filter(is_writable)
14037 .map(|fi| fi.de_name.clone())
14038 .collect();
14039
14040 let writable_source_lits: Vec<String> = fields_info
14045 .iter()
14046 .filter(is_writable)
14047 .map(|fi| {
14048 fi.attrs
14049 .source
14050 .clone()
14051 .unwrap_or_else(|| ident_name(&fi.ident))
14052 })
14053 .collect();
14054
14055 let readable: Vec<&FieldInfo> = fields_info
14059 .iter()
14060 .filter(|fi| {
14061 !fi.attrs.write_only
14062 && !fi.serde.skip_ser
14063 && !fi.attrs.skip
14064 && fi.attrs.method.is_none()
14065 && !fi.attrs.nested
14066 && fi.attrs.many.is_none()
14067 && fi.attrs.slug.is_none()
14068 })
14069 .collect();
14070 let readable_source_lits: Vec<String> = readable
14071 .iter()
14072 .map(|fi| {
14073 fi.attrs
14074 .source
14075 .clone()
14076 .unwrap_or_else(|| ident_name(&fi.ident))
14077 })
14078 .collect();
14079 let readable_lits: Vec<String> = readable.iter().map(|fi| fi.ser_name.clone()).collect();
14081
14082 let from_writable_json_inits: Vec<_> = fields_info
14089 .iter()
14090 .map(|fi| {
14091 let ident = &fi.ident;
14092 let fname = &fi.de_name;
14093 let ty = &fi.ty;
14094 if is_writable(&fi) {
14095 quote! {
14096 #ident: match __obj.and_then(|__o| __o.get(#fname)) {
14097 ::core::option::Option::Some(__v) => {
14098 match #root::__serde_json::from_value::<#ty>(
14099 ::core::clone::Clone::clone(__v),
14100 ) {
14101 ::core::result::Result::Ok(__x) => __x,
14102 ::core::result::Result::Err(__e) => {
14103 __errors.add(#fname.to_owned(), __e.to_string());
14104 ::core::default::Default::default()
14105 }
14106 }
14107 }
14108 ::core::option::Option::None => ::core::default::Default::default(),
14109 }
14110 }
14111 } else {
14112 quote! { #ident: ::core::default::Default::default() }
14113 }
14114 })
14115 .collect();
14116 let patch_overlays: Vec<_> = fields_info
14118 .iter()
14119 .filter(is_writable)
14120 .map(|fi| {
14121 let ident = &fi.ident;
14122 let fname = &fi.de_name;
14123 let ty = &fi.ty;
14124 quote! {
14125 if let ::core::option::Option::Some(__v) = __obj.and_then(|__o| __o.get(#fname)) {
14126 match #root::__serde_json::from_value::<#ty>(::core::clone::Clone::clone(__v)) {
14127 ::core::result::Result::Ok(__x) => __merged.#ident = __x,
14128 ::core::result::Result::Err(__e) => {
14129 __errors.add(#fname.to_owned(), __e.to_string());
14130 }
14131 }
14132 }
14133 }
14134 })
14135 .collect();
14136 let patch_validate_call = if has_run_validations {
14137 quote! { __merged.__rustango_validate_patch(__obj) }
14138 } else {
14139 quote! { let _ = &__merged; ::core::result::Result::Ok(()) }
14140 };
14141 let patch_validate_helper = if has_run_validations {
14142 quote! {
14143 impl #struct_name {
14144 #[doc(hidden)]
14145 fn __rustango_validate_patch(
14146 &self,
14147 __obj: ::core::option::Option<&#root::__serde_json::Map<::std::string::String, #root::__serde_json::Value>>,
14148 ) -> ::core::result::Result<(), #root::forms::FormErrors> {
14149 #validate_patch_body
14150 }
14151 }
14152 }
14153 } else {
14154 quote! {}
14155 };
14156
14157 let openapi_impl = {
14161 #[cfg(feature = "openapi")]
14162 {
14163 let property_calls = output_fields.iter().map(|fi| {
14164 let name_lit = &fi.ser_name;
14165 let ty = &fi.ty;
14166 let nullable_call = if is_option(ty) {
14167 quote! { .nullable() }
14168 } else {
14169 quote! {}
14170 };
14171 let limits_call = is_writable(fi).then(|| {
14173 let mname = fi
14174 .attrs
14175 .source
14176 .clone()
14177 .unwrap_or_else(|| ident_name(&fi.ident));
14178 let (a, b, c, d) = (
14179 opt_u64(fi.attrs.max_length),
14180 opt_u64(fi.attrs.min_length),
14181 opt_i64(fi.attrs.min),
14182 opt_i64(fi.attrs.max),
14183 );
14184 quote! {
14185 .field_limits(
14186 <#model_path as #root::core::Model>::SCHEMA.field(#mname),
14187 #a, #b, #c, #d,
14188 )
14189 }
14190 });
14191 quote! {
14192 .property(
14193 #name_lit,
14194 <#ty as #root::openapi::OpenApiSchema>::openapi_schema()
14195 #limits_call
14196 #nullable_call,
14197 )
14198 }
14199 });
14200 let required_lits: Vec<_> = output_fields
14201 .iter()
14202 .filter(|fi| !is_option(&fi.ty) && fi.serde.skip_ser_if.is_none())
14203 .map(|fi| fi.ser_name.clone())
14204 .collect();
14205 quote! {
14206 impl #root::openapi::OpenApiSchema for #struct_name {
14207 fn openapi_schema() -> #root::openapi::Schema {
14208 #root::openapi::Schema::object()
14209 #( #property_calls )*
14210 .required([ #( #required_lits ),* ])
14211 }
14212 }
14213 }
14214 }
14215 #[cfg(not(feature = "openapi"))]
14216 {
14217 quote! {}
14218 }
14219 };
14220
14221 let related_tables = fields_info.iter().filter_map(|fi| {
14223 let name = &fi.ser_name;
14224 if let Some(inner) = &fi.attrs.many {
14225 return Some(quote! {
14226 (#name, <<#inner as #root::serializer::ModelSerializer>::Model
14227 as #root::core::Model>::SCHEMA.table)
14228 });
14229 }
14230 if !(fi.attrs.nested || fi.attrs.slug.is_some()) {
14231 return None;
14232 }
14233 let src_name = fi
14234 .attrs
14235 .source
14236 .as_deref()
14237 .unwrap_or(&ident_name(&fi.ident))
14238 .to_owned();
14239 let src_ident = ident_from_name(&src_name, fi.ident.span());
14240 Some(quote! {
14241 (#name, #root::serializer::relation_table(|__m: &#model_path| &__m.#src_ident))
14242 })
14243 });
14244
14245 Ok(quote! {
14246 impl #root::serializer::ModelSerializer for #struct_name {
14247 type Model = #model_path;
14248
14249 fn related_tables() -> ::std::vec::Vec<(&'static str, &'static str)> {
14250 ::std::vec![ #( #related_tables ),* ]
14251 }
14252
14253 fn from_model(model: &Self::Model) -> Self {
14254 Self {
14255 #( #from_model_fields ),*
14256 }
14257 }
14258
14259 fn writable_fields() -> &'static [&'static str] {
14260 &[ #( #writable_lits ),* ]
14261 }
14262
14263 fn writable_source_fields() -> &'static [&'static str] {
14264 &[ #( #writable_source_lits ),* ]
14265 }
14266
14267 fn readable_source_fields() -> &'static [&'static str] {
14268 &[ #( #readable_source_lits ),* ]
14269 }
14270
14271 fn readable_fields() -> &'static [&'static str] {
14272 &[ #( #readable_lits ),* ]
14273 }
14274
14275 fn from_writable_json(
14276 __body: &#root::__serde_json::Value,
14277 ) -> ::core::result::Result<Self, #root::forms::FormErrors> {
14278 let mut __errors = #root::forms::FormErrors::default();
14279 let __obj = __body.as_object();
14280 let __out = Self {
14281 #( #from_writable_json_inits ),*
14282 };
14283 if __errors.is_empty() {
14284 ::core::result::Result::Ok(__out)
14285 } else {
14286 ::core::result::Result::Err(__errors)
14287 }
14288 }
14289
14290 fn validate_patch(
14291 __stored: &Self::Model,
14292 __body: &#root::__serde_json::Value,
14293 ) -> ::core::result::Result<(), #root::forms::FormErrors> {
14294 let mut __errors = #root::forms::FormErrors::default();
14295 let __obj = __body.as_object();
14296 #[allow(unused_mut)]
14297 let mut __merged = <Self as #root::serializer::ModelSerializer>::from_model(__stored);
14298 #( #patch_overlays )*
14299 if !__errors.is_empty() {
14300 return ::core::result::Result::Err(__errors);
14301 }
14302 #patch_validate_call
14303 }
14304
14305 #trait_validate_override
14306 }
14307
14308 #patch_validate_helper
14309
14310 impl #root::__serde::Serialize for #struct_name {
14311 fn serialize<S>(&self, serializer: S)
14312 -> ::core::result::Result<S::Ok, S::Error>
14313 where
14314 S: #root::__serde::Serializer,
14315 {
14316 use #root::__serde::ser::SerializeStruct;
14317 let mut __state = serializer.serialize_struct(
14318 #struct_name_lit,
14319 #output_field_count,
14320 )?;
14321 #( #serialize_fields )*
14322 __state.end()
14323 }
14324 }
14325
14326 #openapi_impl
14327
14328 #validate_method
14329
14330 #many_setters_impl
14331 })
14332}
14333
14334#[cfg_attr(not(feature = "openapi"), allow(dead_code))]
14338fn is_option(ty: &syn::Type) -> bool {
14339 if let syn::Type::Path(p) = ty {
14340 if let Some(last) = p.path.segments.last() {
14341 return last.ident == "Option";
14342 }
14343 }
14344 false
14345}
14346
14347#[cfg(test)]
14348mod main_attr_tests {
14349 use super::*;
14350
14351 fn expand(args: &str) -> String {
14352 let body = quote! { async fn main() { run().await } };
14353 expand_main(args.parse().unwrap(), body)
14354 .expect("expansion failed")
14355 .to_string()
14356 }
14357
14358 #[test]
14362 fn logging_false_emits_no_subscriber() {
14363 let out = expand("logging = false");
14364 assert!(
14365 !out.contains("logging :: Setup"),
14366 "`logging = false` must not install a subscriber:\n{out}",
14367 );
14368 assert!(
14369 out.contains("block_on"),
14370 "the runtime must still be built:\n{out}",
14371 );
14372 }
14373
14374 #[test]
14375 fn default_still_installs_a_subscriber() {
14376 let out = expand("");
14380 assert!(
14381 out.contains("logging :: Setup"),
14382 "the default entrypoint must still install one:\n{out}",
14383 );
14384 assert!(
14387 !out.contains("args ()"),
14388 "the entrypoint must not route logs by argv:\n{out}",
14389 );
14390 }
14391
14392 #[test]
14393 fn logging_false_composes_with_flavor() {
14394 let out = expand("flavor = \"current_thread\", logging = false");
14395 assert!(!out.contains("logging :: Setup"), "{out}");
14396 assert!(out.contains("new_current_thread"), "{out}");
14397 }
14398
14399 #[test]
14401 fn body_runs_on_a_big_stack_thread() {
14402 let out = expand("");
14403 assert!(out.contains("run_on_main_thread"), "{out}");
14404 assert!(out.contains("MAIN_STACK_SIZE"), "{out}");
14405 let out = expand("stack_size = 16777216, logging = false");
14406 assert!(out.contains("16777216usize"), "{out}");
14407 assert!(!out.contains("MAIN_STACK_SIZE"), "{out}");
14408 assert!(
14409 out.contains("thread_stack_size"),
14410 "workers get it too:\n{out}"
14411 );
14412 }
14413
14414 #[test]
14416 fn a_tiny_stack_size_is_refused() {
14417 let body = quote! { async fn main() {} };
14418 let err = expand_main("stack_size = 4096".parse().unwrap(), body)
14419 .expect_err("4 KiB")
14420 .to_string();
14421 assert!(err.contains("at least"), "{err}");
14422 }
14423
14424 #[test]
14426 fn lint_attributes_reach_the_body() {
14427 let body = quote! {
14428 #[allow(unused_variables)]
14429 #[doc = "entry"]
14430 async fn main() { let x = 1; }
14431 };
14432 let out = expand_main(quote!(), body).unwrap().to_string();
14433 assert_eq!(out.matches("allow (unused_variables)").count(), 2, "{out}");
14434 assert_eq!(out.matches("doc = \"entry\"").count(), 1, "{out}");
14435 }
14436
14437 #[test]
14438 fn logging_true_is_the_default_shape() {
14439 assert!(
14440 parse_main_args(&"logging = true".parse().unwrap())
14441 .unwrap()
14442 .logging
14443 );
14444 assert!(
14445 !parse_main_args(&"logging=false".parse().unwrap())
14446 .unwrap()
14447 .logging
14448 );
14449 }
14450}
14451
14452#[cfg(test)]
14453mod main_arg_tests {
14454 use super::*;
14455
14456 fn expand(args: &str) -> syn::Result<TokenStream2> {
14457 let body = quote! { async fn main() { run().await } };
14458 expand_main(args.parse().unwrap(), body)
14459 }
14460
14461 #[test]
14463 fn unknown_or_misspelt_args_are_errors() {
14464 for args in [
14465 "logging = flase",
14466 "multi_thread",
14467 "workers = 4",
14468 r#"flavor = "curent_thread""#,
14469 "worker_threads = 4",
14470 ] {
14471 let err = expand(args).expect_err(args).to_string();
14472 assert!(err.contains("#[rustango::main]"), "{args}: {err}");
14473 }
14474 }
14475
14476 #[test]
14477 fn supported_args_expand() {
14478 for args in [
14479 "",
14480 r#"flavor = "current_thread""#,
14481 r#"flavor = "multi_thread", logging = false"#,
14482 "logging = true",
14483 ] {
14484 assert!(expand(args).is_ok(), "{args}");
14485 }
14486 }
14487}
14488
14489#[cfg(test)]
14490mod embed_migrations_tests {
14491 use super::*;
14492
14493 fn expand(path: &str) -> String {
14494 expand_embed_migrations(quote! { #path })
14495 .expect("expansion failed")
14496 .to_string()
14497 }
14498
14499 #[test]
14500 fn missing_dir_warns() {
14501 let out = expand("./no_such_migrations_dir");
14502 assert!(out.contains("deprecated"), "missing dir must warn:\n{out}");
14503 assert!(out.contains("no_such_migrations_dir"), "{out}");
14504 }
14505
14506 #[test]
14507 fn existing_dir_does_not_warn() {
14508 let out = expand("./src");
14509 assert!(!out.contains("deprecated"), "{out}");
14510 }
14511}
14512
14513#[cfg(test)]
14514mod field_attr_tests {
14515 use super::*;
14516
14517 fn error_for(field: syn::Field) -> Option<String> {
14518 let attrs = parse_field_attrs(&field).unwrap();
14519 let kind = detect_type(&field.ty).unwrap().kind;
14520 check_bound_compatibility(&field, &attrs, kind)
14521 .err()
14522 .map(|e| e.to_string())
14523 }
14524
14525 #[test]
14527 fn type_specific_attrs_reject_other_types() {
14528 let f: syn::FieldsNamed = syn::parse_quote!({
14529 #[rustango(citext)] a: i64,
14530 #[rustango(vector(dims = 3))] b: String,
14531 #[rustango(geometry(srid = 4326))] c: String,
14532 #[rustango(citext)] d: String,
14533 });
14534 let errs: Vec<_> = f.named.into_iter().map(error_for).collect();
14535 assert!(errs[0].as_deref().unwrap().contains("citext"), "{errs:?}");
14536 assert!(errs[1].as_deref().unwrap().contains("vector"), "{errs:?}");
14537 assert!(errs[2].as_deref().unwrap().contains("geometry"), "{errs:?}");
14538 assert_eq!(errs[3], None);
14539 }
14540
14541 #[test]
14543 fn manager_ext_is_refused() {
14544 let input: DeriveInput = syn::parse_quote! {
14545 #[rustango(table = "p", manager(ext = "PostManagerExt"))]
14546 struct Post { id: i64 }
14547 };
14548 let err = parse_container_attrs(&input).err().expect("refused");
14549 assert!(err.to_string().contains("#2132"), "{err}");
14550 }
14551
14552 #[test]
14554 fn set_null_needs_an_optional_foreign_key() {
14555 let input: DeriveInput = syn::parse_quote! {
14556 #[rustango(table = "c")]
14557 struct Child {
14558 #[rustango(primary_key)] id: i64,
14559 #[rustango(on_delete = "set_null")] parent: ForeignKey<Parent>,
14560 }
14561 };
14562 let err = expand(&input).err().expect("refused");
14563 assert!(err.to_string().contains("set_null"), "{err}");
14564 let input: DeriveInput = syn::parse_quote! {
14565 #[rustango(table = "c")]
14566 struct Child {
14567 #[rustango(primary_key)] id: i64,
14568 #[rustango(on_delete = "set_null")] parent: Option<ForeignKey<Parent>>,
14569 }
14570 };
14571 assert!(expand(&input).is_ok());
14572 }
14573
14574 #[test]
14576 fn set_null_needs_a_nullable_fk_column() {
14577 let input: DeriveInput = syn::parse_quote! {
14578 #[rustango(table = "c")]
14579 struct Child {
14580 #[rustango(primary_key)] id: i64,
14581 #[rustango(fk = "p", on_delete = "set_null")] parent_id: i64,
14582 #[rustango(o2o = "q", on_delete = "set_null")] other_id: Option<i64>,
14583 }
14584 };
14585 let err = expand(&input).err().expect("refused");
14586 assert!(err.to_string().contains("set_null"), "{err}");
14587 let input: DeriveInput = syn::parse_quote! {
14588 #[rustango(table = "c")]
14589 struct Child {
14590 #[rustango(primary_key)] id: i64,
14591 #[rustango(fk = "p", on_delete = "set_null")] parent_id: std::option::Option<i64>,
14592 #[rustango(o2o = "q", on_delete = "set_null")] other_id: ::core::option::Option<i64>,
14593 #[rustango(on_delete = "set_null")] parent: core::option::Option<ForeignKey<Parent>>,
14594 }
14595 };
14596 assert!(expand(&input).is_ok());
14597 }
14598
14599 #[test]
14602 fn unknown_index_columns_are_errors() {
14603 let input: DeriveInput = syn::parse_quote! {
14604 #[rustango(
14605 table = "w",
14606 unique_together = "a, nope",
14607 index_together = "a, b_typo",
14608 index("missing"),
14609 get_latest_by = "-when_typo"
14610 )]
14611 struct W { #[rustango(primary_key)] id: i64, a: i64, b: i64 }
14612 };
14613 let err = expand(&input).expect_err("must not compile");
14614 let all: Vec<String> = err.into_iter().map(|e| e.to_string()).collect();
14615 for name in ["nope", "b_typo", "missing", "when_typo"] {
14616 assert!(
14617 all.iter().any(|m| m.contains(&format!("`{name}`"))),
14618 "{name}: {all:?}"
14619 );
14620 }
14621 }
14622
14623 #[test]
14626 fn known_index_columns_compile() {
14627 let input: DeriveInput = syn::parse_quote! {
14628 #[rustango(
14629 table = "w",
14630 unique_together = "a, b_col",
14631 index("lower(a)"),
14632 get_latest_by = "-b"
14633 )]
14634 struct W {
14635 #[rustango(primary_key)] id: i64,
14636 a: i64,
14637 #[rustango(column = "b_col")] b: i64,
14638 }
14639 };
14640 expand(&input).expect("compiles");
14641 }
14642
14643 #[test]
14645 fn reverse_accessor_clash_names_related_name() {
14646 let input: DeriveInput = syn::parse_quote! {
14647 #[rustango(table = "m")]
14648 struct Message {
14649 #[rustango(primary_key)] id: i64,
14650 sender: ForeignKey<User>,
14651 recipient: ForeignKey<User>,
14652 }
14653 };
14654 let err = expand(&input).err().expect("refused");
14655 assert!(err.to_string().contains("related_name"), "{err}");
14656 }
14657
14658 #[test]
14661 fn related_name_plus_skips_the_reverse_accessor() {
14662 let input: DeriveInput = syn::parse_quote! {
14663 #[rustango(table = "m")]
14664 struct Message {
14665 #[rustango(primary_key)] id: i64,
14666 sender: ForeignKey<User>,
14667 #[rustango(related_name = "+")] recipient: ForeignKey<User>,
14668 #[rustango(related_name = "+")] owner: ForeignKey<other::Account>,
14669 }
14670 };
14671 let out = expand(&input).expect("derives").to_string();
14672 assert_eq!(out.matches("message_set_pool").count(), 1, "{out}");
14673 assert!(!out.contains("impl other :: Account {"), "{out}");
14674 }
14675
14676 fn field_error(src: &str) -> Option<String> {
14677 let f: syn::FieldsNamed = syn::parse_str(&format!("{{ {src} }}")).unwrap();
14678 let field = f.named.into_iter().next().unwrap();
14679 match parse_field_attrs(&field) {
14680 Err(e) => Some(e.to_string()),
14681 Ok(_) => error_for(field),
14682 }
14683 }
14684
14685 #[test]
14687 fn flag_attrs_accept_eq_true() {
14688 for key in [
14689 "primary_key",
14690 "unique",
14691 "soft_delete",
14692 "index",
14693 "auto_now",
14694 "auto_now_add",
14695 ] {
14696 let f: syn::FieldsNamed =
14697 syn::parse_str(&format!("{{ #[rustango({key} = true)] a: i64 }}")).unwrap();
14698 let field = f.named.into_iter().next().unwrap();
14699 assert!(parse_field_attrs(&field).is_ok(), "{key} = true");
14700 let err = field_error(&format!("#[rustango({key} = false)] a: i64")).unwrap();
14701 assert!(err.contains(&format!("`{key} = false`")), "{err}");
14702 }
14703 let f: syn::Field =
14704 syn::parse_quote!(#[rustango(primary_key = true, unique = true)] a: i64);
14705 let attrs = parse_field_attrs(&f).unwrap();
14706 assert!(attrs.primary_key && attrs.unique);
14707 }
14708
14709 #[test]
14711 fn unknown_key_is_named_with_a_suggestion() {
14712 let err = field_error("#[rustango(max_lenght = 5)] a: String").unwrap();
14713 assert!(err.contains("`max_lenght`"), "{err}");
14714 assert!(err.contains("did you mean `max_length`"), "{err}");
14715 assert!(err.contains("Supported: column"), "{err}");
14716 let input: DeriveInput = syn::parse_quote! {
14717 #[rustango(tabel = "p")]
14718 struct P { id: i64 }
14719 };
14720 let err = parse_container_attrs(&input).err().unwrap().to_string();
14721 assert!(
14722 err.contains("`tabel`") && err.contains("did you mean `table`"),
14723 "{err}"
14724 );
14725 let err = field_error("#[rustango(zzzzzz)] a: i64").unwrap();
14726 assert!(!err.contains("did you mean"), "{err}");
14727 }
14728
14729 #[test]
14731 fn listed_keys_are_all_known() {
14732 for key in FIELD_ATTR_KEYS {
14733 let err = field_error(&format!("#[rustango({key})] a: i64")).unwrap_or_default();
14734 assert!(!err.contains("unknown rustango field"), "{key}: {err}");
14735 }
14736 for key in CONTAINER_ATTR_KEYS {
14737 let input: DeriveInput =
14738 syn::parse_str(&format!("#[rustango({key})] struct P {{ id: i64 }}")).unwrap();
14739 let err = parse_container_attrs(&input)
14740 .err()
14741 .map(|e| e.to_string())
14742 .unwrap_or_default();
14743 assert!(!err.contains("unknown rustango container"), "{key}: {err}");
14744 }
14745 }
14746
14747 #[test]
14749 fn default_shapes_only_sqlite_takes_are_refused() {
14750 for bad in [
14751 r#"#[rustango(default = "draft")] s: String"#,
14752 r#"#[rustango(default = "draft")] s: Option<String>"#,
14753 r#"#[rustango(default = "abc")] n: i32"#,
14754 r#"#[rustango(default = "'abc'")] n: i64"#,
14755 r#"#[rustango(default = "true")] n: i32"#,
14756 r#"#[rustango(default = "")] n: i32"#,
14757 r#"#[rustango(default = "'x'")] n: f64"#,
14758 ] {
14759 assert!(field_error(bad).is_some(), "{bad} must be refused");
14760 }
14761 let err = field_error(r#"#[rustango(default = "draft")] s: String"#).unwrap();
14762 assert!(err.contains(r#"default = "'draft'""#), "{err}");
14763 let err = field_error(r#"#[rustango(default = "current_user")] s: String"#).unwrap();
14765 assert!(
14766 err.contains("Postgres only") && !err.contains("column"),
14767 "{err}"
14768 );
14769 for good in [
14770 r#"#[rustango(default = "'draft'")] s: String"#,
14771 r#"#[rustango(default = "")] s: String"#,
14772 r#"#[rustango(default = "now()")] s: String"#,
14773 r#"#[rustango(default = "CURRENT_TIMESTAMP")] s: String"#,
14774 r#"#[rustango(default = "NULL")] s: Option<String>"#,
14775 r#"#[rustango(default = "0")] n: i32"#,
14776 r#"#[rustango(default = "-1")] n: i64"#,
14777 r#"#[rustango(default = "'5'")] n: i64"#,
14778 r#"#[rustango(default = "(1 + 2)")] n: i64"#,
14779 r#"#[rustango(default = "1.5")] n: f64"#,
14780 r#"#[rustango(default = "true")] b: bool"#,
14781 ] {
14782 assert_eq!(field_error(good), None, "{good}");
14783 }
14784 }
14785
14786 #[test]
14788 fn raw_idents_derive_plain_names() {
14789 let ident: syn::Ident = syn::parse_quote!(r#type);
14790 assert_eq!(column_type_ident(&ident).to_string(), "type_col");
14791 assert_eq!(ident_from_name("type", ident.span()).to_string(), "r#type");
14792 }
14793}
14794
14795#[cfg(test)]
14796mod model_attr_name_tests {
14797 use super::*;
14798
14799 fn container_error(item: proc_macro2::TokenStream) -> Option<String> {
14800 let input: DeriveInput = syn::parse2(item).unwrap();
14801 parse_container_attrs(&input).err().map(|e| e.to_string())
14802 }
14803
14804 fn field_error(field: proc_macro2::TokenStream) -> Option<String> {
14805 let f: syn::FieldsNamed = syn::parse2(quote!({ #field })).unwrap();
14806 let field = f.named.into_iter().next().unwrap();
14807 parse_field_attrs(&field).err().map(|e| e.to_string())
14808 }
14809
14810 #[test]
14812 fn rgo_is_read_like_rustango() {
14813 let long: DeriveInput = syn::parse_quote! {
14814 #[rustango(table = "t", display = "name")] struct S;
14815 };
14816 let short: DeriveInput = syn::parse_quote! {
14817 #[rgo(table = "t", display = "name")] struct S;
14818 };
14819 let (long, short) = (
14820 parse_container_attrs(&long).unwrap(),
14821 parse_container_attrs(&short).unwrap(),
14822 );
14823 assert_eq!(long.table, short.table);
14824 assert_eq!(short.table.as_deref(), Some("t"));
14825 assert_eq!(long.display.map(|d| d.0), short.display.map(|d| d.0));
14826 }
14827
14828 #[test]
14829 fn mixing_names_on_one_item_or_field_is_refused() {
14830 let err = container_error(quote! {
14831 #[rustango(table = "t")] #[rgo(display = "name")] struct S;
14832 })
14833 .expect("a mix on an item must fail");
14834 assert!(
14835 err.contains("`#[rustango(...)]` and `#[rgo(...)]`"),
14836 "{err}"
14837 );
14838 let err = field_error(quote! { #[rgo(primary_key)] #[rustango(column = "c")] a: i64 })
14839 .expect("a mix on a field must fail");
14840 assert!(
14841 err.contains("`#[rgo(...)]` and `#[rustango(...)]`"),
14842 "{err}"
14843 );
14844 assert_eq!(
14846 container_error(quote! { #[rgo(table = "t")] #[rgo(display = "name")] struct S; }),
14847 None
14848 );
14849 assert_eq!(
14850 field_error(quote! { #[rustango(primary_key)] #[rustango(column = "c")] a: i64 }),
14851 None
14852 );
14853 }
14854
14855 #[test]
14857 fn hints_use_the_written_name() {
14858 let err = container_error(quote! { #[rgo(table = "t", unique_together = "a")] struct S; })
14859 .expect("one column must fail");
14860 assert!(err.contains("#[rgo(unique)]"), "{err}");
14861 let err =
14862 container_error(quote! { #[rustango(table = "t", index_together = "a")] struct S; })
14863 .expect("one column must fail");
14864 assert!(err.contains("#[rustango(index)]"), "{err}");
14865 }
14866}
14867
14868fn decode_text(bytes: Vec<u8>) -> Result<String, String> {
14872 fn utf16(bytes: &[u8], unit: fn([u8; 2]) -> u16) -> Result<String, String> {
14873 if bytes.len() % 2 != 0 {
14874 return Err("odd-length UTF-16".into());
14875 }
14876 let units = bytes.chunks_exact(2).map(|c| unit([c[0], c[1]]));
14877 char::decode_utf16(units)
14878 .collect::<Result<String, _>>()
14879 .map_err(|e| e.to_string())
14880 }
14881 match bytes.as_slice() {
14882 [0xFF, 0xFE, rest @ ..] => utf16(rest, u16::from_le_bytes),
14883 [0xFE, 0xFF, rest @ ..] => utf16(rest, u16::from_be_bytes),
14884 _ => {
14885 let mut bytes = bytes;
14886 if bytes.starts_with(&[0xEF, 0xBB, 0xBF]) {
14887 bytes.drain(..3);
14888 }
14889 String::from_utf8(bytes).map_err(|e| e.to_string())
14890 }
14891 }
14892}
14893
14894#[cfg(test)]
14895mod decode_text_tests {
14896 use super::decode_text;
14897
14898 #[test]
14899 fn a_bom_or_utf16_migration_decodes() {
14900 let text = r#"{"name": "0001_é"}"#;
14901 let mut bom = vec![0xEF, 0xBB, 0xBF];
14902 bom.extend(text.bytes());
14903 let mut le = vec![0xFF, 0xFE];
14904 le.extend(text.encode_utf16().flat_map(u16::to_le_bytes));
14905 let mut be = vec![0xFE, 0xFF];
14906 be.extend(text.encode_utf16().flat_map(u16::to_be_bytes));
14907 for bytes in [text.as_bytes().to_vec(), bom, le, be] {
14908 assert_eq!(decode_text(bytes).unwrap(), text);
14909 }
14910 assert!(decode_text(vec![0xFF, 0xFE, 0x41]).is_err());
14911 }
14912}