1use proc_macro::TokenStream;
10use proc_macro2::TokenStream as TokenStream2;
11use quote::quote;
12use syn::{
13 parse_macro_input, spanned::Spanned, Data, DeriveInput, Fields, GenericArgument, LitStr,
14 PathArguments, Type, TypePath,
15};
16
17fn rustango_root() -> TokenStream2 {
38 use proc_macro_crate::{crate_name, FoundCrate};
39 match crate_name("rustango") {
40 Ok(FoundCrate::Itself) => quote!(::rustango),
41 Ok(FoundCrate::Name(name)) => {
42 let ident = proc_macro2::Ident::new(&name, proc_macro2::Span::call_site());
43 quote!(::#ident)
44 }
45 Err(_) => quote!(::rustango),
46 }
47}
48
49#[proc_macro_derive(Model, attributes(rustango, rgo))]
51pub fn derive_model(input: TokenStream) -> TokenStream {
52 let input = parse_macro_input!(input as DeriveInput);
53 expand(&input)
54 .unwrap_or_else(syn::Error::into_compile_error)
55 .into()
56}
57
58#[proc_macro_derive(ViewSet, attributes(viewset))]
99pub fn derive_viewset(input: TokenStream) -> TokenStream {
100 let input = parse_macro_input!(input as DeriveInput);
101 expand_viewset(&input)
102 .unwrap_or_else(syn::Error::into_compile_error)
103 .into()
104}
105
106#[proc_macro_derive(Form, attributes(form))]
134pub fn derive_form(input: TokenStream) -> TokenStream {
135 let input = parse_macro_input!(input as DeriveInput);
136 expand_form(&input)
137 .unwrap_or_else(syn::Error::into_compile_error)
138 .into()
139}
140
141#[proc_macro_derive(Serializer, attributes(serializer, serde))]
166pub fn derive_serializer(input: TokenStream) -> TokenStream {
167 let input = parse_macro_input!(input as DeriveInput);
168 expand_serializer(&input)
169 .unwrap_or_else(syn::Error::into_compile_error)
170 .into()
171}
172
173#[proc_macro]
212pub fn embed_migrations(input: TokenStream) -> TokenStream {
213 expand_embed_migrations(input.into())
214 .unwrap_or_else(syn::Error::into_compile_error)
215 .into()
216}
217
218#[allow(non_snake_case)]
268#[proc_macro]
269pub fn Q(input: TokenStream) -> TokenStream {
270 expand_q(input.into())
271 .unwrap_or_else(syn::Error::into_compile_error)
272 .into()
273}
274
275#[proc_macro_attribute]
321pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
322 expand_main(args.into(), item.into())
323 .unwrap_or_else(syn::Error::into_compile_error)
324 .into()
325}
326
327fn expand_main(args: TokenStream2, item: TokenStream2) -> syn::Result<TokenStream2> {
328 let mut input: syn::ItemFn = syn::parse2(item)?;
329 if input.sig.asyncness.is_none() {
330 return Err(syn::Error::new(
331 input.sig.ident.span(),
332 "`#[rustango::main]` must wrap an `async fn`",
333 ));
334 }
335
336 let root = rustango_root();
346 let MainArgs {
347 flavor,
348 logging,
349 stack_size,
350 } = parse_main_args(&args)?;
351 let stack_size = match stack_size {
352 Some(n) => quote!(#n),
353 None => quote!(#root::__private_runtime::MAIN_STACK_SIZE),
354 };
355 let builder_call = match flavor {
356 Flavor::CurrentThread => quote! {
357 #root::__private_runtime::tokio::runtime::Builder::new_current_thread()
358 },
359 Flavor::MultiThread => quote! {
361 #root::__private_runtime::tokio::runtime::Builder::new_multi_thread()
362 .thread_stack_size(#stack_size)
363 },
364 };
365
366 let mut inner = input.clone();
369 inner.sig.ident = syn::Ident::new("__rustango_main_body", input.sig.ident.span());
370 inner.attrs.retain(|a| {
372 ["allow", "expect", "deny", "warn", "forbid"]
373 .iter()
374 .any(|l| a.path().is_ident(l))
375 });
376 inner.vis = syn::Visibility::Inherited;
377 input.sig.asyncness = None;
378 input.sig.output = syn::parse2(quote!(-> ::std::process::ExitCode))?;
379
380 let logging_prologue = if logging {
384 quote! {
385 let _ = #root::logging::Setup::new()
390 .with_default_env_filter(#root::__private_runtime::main_default_filter())
391 .with_format(#root::__private_runtime::main_format())
392 .install();
393 }
394 } else {
395 quote! {}
396 };
397
398 input.block = syn::parse2(quote! {{
399 { #logging_prologue }
400 #inner
401 #root::__private_runtime::run_on_main_thread(#stack_size, || {
402 let __rt = #builder_call
403 .enable_all()
404 .build()
405 .expect("failed to build tokio runtime");
406 ::std::process::Termination::report(__rt.block_on(__rustango_main_body()))
407 })
408 }})?;
409
410 Ok(quote! {
411 #input
412 })
413}
414
415enum Flavor {
416 MultiThread,
417 CurrentThread,
418}
419
420struct MainArgs {
422 flavor: Flavor,
423 logging: bool,
424 stack_size: Option<usize>,
426}
427
428const MIN_STACK_SIZE: usize = 64 * 1024;
430
431const MAIN_ARGS_HELP: &str =
432 "supported: `flavor = \"current_thread\" | \"multi_thread\"`, `logging = true | false`, `stack_size = <bytes>`";
433
434fn parse_main_args(args: &TokenStream2) -> syn::Result<MainArgs> {
436 use syn::parse::Parser as _;
437 let metas = syn::punctuated::Punctuated::<syn::MetaNameValue, syn::Token![,]>::parse_terminated
438 .parse2(args.clone())
439 .map_err(|e| {
440 syn::Error::new(
441 e.span(),
442 format!("#[rustango::main]: {e}; {MAIN_ARGS_HELP}"),
443 )
444 })?;
445 let mut out = MainArgs {
446 flavor: Flavor::MultiThread,
447 logging: true,
448 stack_size: None,
449 };
450 for meta in &metas {
451 let lit = match &meta.value {
452 syn::Expr::Lit(l) => Some(&l.lit),
453 _ => None,
454 };
455 let name = meta.path.get_ident().map(ToString::to_string);
456 match (name.as_deref(), lit) {
457 (Some("flavor"), Some(syn::Lit::Str(s))) if s.value() == "current_thread" => {
458 out.flavor = Flavor::CurrentThread;
459 }
460 (Some("flavor"), Some(syn::Lit::Str(s))) if s.value() == "multi_thread" => {
461 out.flavor = Flavor::MultiThread;
462 }
463 (Some("logging"), Some(syn::Lit::Bool(b))) => out.logging = b.value,
464 (Some("stack_size"), Some(syn::Lit::Int(n))) => {
465 let bytes = n.base10_parse::<usize>()?;
466 if bytes < MIN_STACK_SIZE {
467 return Err(syn::Error::new(
468 n.span(),
469 format!("#[rustango::main]: `stack_size` must be at least {MIN_STACK_SIZE} bytes"),
470 ));
471 }
472 out.stack_size = Some(bytes);
473 }
474 _ => {
475 return Err(syn::Error::new(
476 meta.span(),
477 format!("#[rustango::main]: unsupported argument; {MAIN_ARGS_HELP}"),
478 ))
479 }
480 }
481 }
482 Ok(out)
483}
484
485struct QInput {
487 base_path: syn::Path,
488 field: syn::Ident,
489 value: syn::Expr,
490}
491
492impl syn::parse::Parse for QInput {
493 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
494 let base_path: syn::Path = input.parse()?;
495 input.parse::<syn::Token![.]>()?;
496 let field: syn::Ident = input.parse()?;
497 input.parse::<syn::Token![=]>()?;
498 let value: syn::Expr = input.parse()?;
499 Ok(QInput {
500 base_path,
501 field,
502 value,
503 })
504 }
505}
506
507fn expand_q(input: TokenStream2) -> syn::Result<TokenStream2> {
508 let q: QInput = syn::parse2(input)?;
509 let root = rustango_root();
510 let field_str = ident_name(&q.field);
511 let field_span = q.field.span();
512 let (base, suffix) = match field_str.find("__") {
513 Some(idx) => (&field_str[..idx], &field_str[idx + 2..]),
514 None => (field_str.as_str(), ""),
515 };
516 if base.is_empty() {
517 return Err(syn::Error::new(
518 field_span,
519 "Q!(): field name is empty before `__` suffix",
520 ));
521 }
522 let base_ident = ident_from_name(base, field_span);
523 let value = &q.value;
524 let path = &q.base_path;
525
526 let expanded = match suffix {
531 "" | "exact" => quote! {
532 #root::core::Column::eq(#path::#base_ident, #value)
533 },
534 "ne" => quote! {
535 #root::core::Column::ne(#path::#base_ident, #value)
536 },
537 "gt" => quote! {
538 #root::core::Column::gt(#path::#base_ident, #value)
539 },
540 "gte" => quote! {
541 #root::core::Column::gte(#path::#base_ident, #value)
542 },
543 "lt" => quote! {
544 #root::core::Column::lt(#path::#base_ident, #value)
545 },
546 "lte" => quote! {
547 #root::core::Column::lte(#path::#base_ident, #value)
548 },
549 "iexact" => quote! {
554 #root::core::Column::iexact(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
555 },
556 "contains" => quote! {
557 #root::core::Column::contains(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
558 },
559 "icontains" => quote! {
560 #root::core::Column::icontains(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
561 },
562 "startswith" => quote! {
563 #root::core::Column::startswith(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
564 },
565 "istartswith" => quote! {
566 #root::core::Column::istartswith(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
567 },
568 "endswith" => quote! {
569 #root::core::Column::endswith(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
570 },
571 "iendswith" => quote! {
572 #root::core::Column::iendswith(#path::#base_ident, ::std::string::ToString::to_string(&(#value)))
573 },
574 "in" => quote! {
575 #root::core::Column::is_in(#path::#base_ident, #value)
576 },
577 "not_in" => quote! {
578 #root::core::Column::not_in(#path::#base_ident, #value)
579 },
580 "isnull" => {
581 let b = match value {
584 syn::Expr::Lit(syn::ExprLit {
585 lit: syn::Lit::Bool(b),
586 ..
587 }) => b.value(),
588 _ => {
589 return Err(syn::Error::new_spanned(
590 value,
591 "Q!(): `__isnull` requires a `true` or `false` literal",
592 ));
593 }
594 };
595 if b {
596 quote! { #root::core::Column::is_null(#path::#base_ident) }
597 } else {
598 quote! { #root::core::Column::is_not_null(#path::#base_ident) }
599 }
600 }
601 "between" => {
602 let tuple = match value {
604 syn::Expr::Tuple(t) if t.elems.len() == 2 => t,
605 _ => {
606 return Err(syn::Error::new_spanned(
607 value,
608 "Q!(): `__between` requires a tuple literal `(lo, hi)`",
609 ));
610 }
611 };
612 let lo = &tuple.elems[0];
613 let hi = &tuple.elems[1];
614 quote! { #root::core::Column::between(#path::#base_ident, #lo, #hi) }
615 }
616 "regex" => quote! {
617 #root::core::Column::regex(#path::#base_ident, #value)
618 },
619 "iregex" => quote! {
620 #root::core::Column::iregex(#path::#base_ident, #value)
621 },
622 _ => {
623 return Err(syn::Error::new(
624 field_span,
625 format!(
626 "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",
627 suffix
628 ),
629 ));
630 }
631 };
632 Ok(expanded)
633}
634
635fn expand_embed_migrations(input: TokenStream2) -> syn::Result<TokenStream2> {
636 let path_str = if input.is_empty() {
638 "./migrations".to_string()
639 } else {
640 let lit: LitStr = syn::parse2(input)?;
641 lit.value()
642 };
643
644 let manifest = std::env::var("CARGO_MANIFEST_DIR").map_err(|_| {
645 syn::Error::new(
646 proc_macro2::Span::call_site(),
647 "embed_migrations! must be invoked during a Cargo build (CARGO_MANIFEST_DIR not set)",
648 )
649 })?;
650 let abs = std::path::Path::new(&manifest).join(&path_str);
651
652 let mut entries: Vec<(String, std::path::PathBuf)> = Vec::new();
653 if abs.is_dir() {
654 let read = std::fs::read_dir(&abs).map_err(|e| {
655 syn::Error::new(
656 proc_macro2::Span::call_site(),
657 format!("embed_migrations!: cannot read {}: {e}", abs.display()),
658 )
659 })?;
660 for entry in read.flatten() {
661 let path = entry.path();
662 if !path.is_file() {
663 continue;
664 }
665 if path.extension().and_then(|s| s.to_str()) != Some("json") {
666 continue;
667 }
668 let Some(stem) = path.file_stem().and_then(|s| s.to_str()) else {
669 continue;
670 };
671 entries.push((stem.to_owned(), path));
672 }
673 }
674 entries.sort_by(|a, b| a.0.cmp(&b.0));
675
676 let mut chain_names: Vec<String> = Vec::with_capacity(entries.len());
689 let mut prev_refs: Vec<(String, Option<String>)> = Vec::with_capacity(entries.len());
690 let mut texts: Vec<String> = Vec::with_capacity(entries.len());
691 for (stem, path) in &entries {
692 let raw = std::fs::read(path)
693 .map_err(|e| e.to_string())
694 .and_then(decode_text)
695 .map_err(|e| {
696 syn::Error::new(
697 proc_macro2::Span::call_site(),
698 format!(
699 "embed_migrations!: cannot read {} for chain validation: {e}",
700 path.display()
701 ),
702 )
703 })?;
704 let json: serde_json::Value = serde_json::from_str(&raw).map_err(|e| {
705 syn::Error::new(
706 proc_macro2::Span::call_site(),
707 format!(
708 "embed_migrations!: {} is not valid JSON: {e}",
709 path.display()
710 ),
711 )
712 })?;
713 let name = json
714 .get("name")
715 .and_then(|v| v.as_str())
716 .ok_or_else(|| {
717 syn::Error::new(
718 proc_macro2::Span::call_site(),
719 format!(
720 "embed_migrations!: {} is missing the `name` field",
721 path.display()
722 ),
723 )
724 })?
725 .to_owned();
726 if name != *stem {
727 return Err(syn::Error::new(
728 proc_macro2::Span::call_site(),
729 format!(
730 "embed_migrations!: file stem `{stem}` does not match the migration's \
731 `name` field `{name}` — rename the file or fix the JSON",
732 ),
733 ));
734 }
735 let prev = json.get("prev").and_then(|v| v.as_str()).map(str::to_owned);
736 chain_names.push(name.clone());
737 prev_refs.push((name, prev));
738 texts.push(raw);
739 }
740
741 let name_set: std::collections::HashSet<&str> =
742 chain_names.iter().map(String::as_str).collect();
743 for (name, prev) in &prev_refs {
744 if let Some(p) = prev {
745 if !name_set.contains(p.as_str()) {
746 return Err(syn::Error::new(
747 proc_macro2::Span::call_site(),
748 format!(
749 "embed_migrations!: broken migration chain — `{name}` declares \
750 prev=`{p}` but no migration with that name exists in {}",
751 abs.display()
752 ),
753 ));
754 }
755 }
756 }
757
758 let pairs: Vec<TokenStream2> = entries
761 .iter()
762 .zip(&texts)
763 .map(|((name, path), text)| {
764 let path_lit = path.display().to_string();
765 quote! { (#name, {
766 const _: &[u8] = ::core::include_bytes!(#path_lit);
767 #text
768 }) }
769 })
770 .collect();
771
772 let missing_warning = if abs.is_dir() {
775 quote! {}
776 } else {
777 let note = format!(
778 "embed_migrations!: directory {} does not exist; no migrations are embedded",
779 abs.display()
780 );
781 quote! {
782 #[deprecated(note = #note)]
783 const __RUSTANGO_MIGRATIONS_DIR_MISSING: () = ();
784 #[allow(clippy::let_unit_value)]
785 let _ = __RUSTANGO_MIGRATIONS_DIR_MISSING;
786 }
787 };
788
789 Ok(quote! {
790 {
791 #missing_warning
792 const __RUSTANGO_EMBEDDED: &[(&'static str, &'static str)] = &[#(#pairs),*];
793 __RUSTANGO_EMBEDDED
794 }
795 })
796}
797
798fn expand(input: &DeriveInput) -> syn::Result<TokenStream2> {
799 let root = rustango_root();
800 let struct_name = &input.ident;
801
802 let Data::Struct(data) = &input.data else {
803 return Err(syn::Error::new_spanned(
804 struct_name,
805 "Model can only be derived on structs",
806 ));
807 };
808 let Fields::Named(named) = &data.fields else {
809 return Err(syn::Error::new_spanned(
810 struct_name,
811 "Model requires a struct with named fields",
812 ));
813 };
814
815 let mut container = parse_container_attrs(input)?;
816 let table = container
817 .table
818 .unwrap_or_else(|| to_snake_case(&struct_name.to_string()));
819 let model_name = struct_name.to_string();
820
821 let collected = collect_fields(named, &table)?;
822 let explicit_pks = container
824 .reverse_has_relations
825 .iter()
826 .map(|r| (&r.self_pk_column, r.self_pk_span))
827 .chain(
828 container
829 .generic_has_relations
830 .iter()
831 .map(|r| (&r.self_pk_column, r.self_pk_span)),
832 );
833 for (column, span) in explicit_pks {
834 if let (Some(column), Some(span)) = (column, span) {
835 if !collected.column_entries.iter().any(|c| &c.column == column) {
836 return Err(syn::Error::new(
837 span,
838 format!("`self_pk_column = \"{column}\"` names no column of `{model_name}`"),
839 ));
840 }
841 }
842 }
843 if let Some((_, pk_column)) = &collected.primary_key {
845 for rel in &mut container.reverse_has_relations {
846 rel.self_pk_column.get_or_insert_with(|| pk_column.clone());
847 }
848 for rel in &mut container.generic_has_relations {
849 rel.self_pk_column.get_or_insert_with(|| pk_column.clone());
850 }
851 }
852
853 if let Some((ref display, span)) = container.display {
855 if !collected.field_names.iter().any(|n| n == display) {
856 return Err(syn::Error::new(
857 span,
858 format!("`display = \"{display}\"` does not match any field on this struct"),
859 ));
860 }
861 }
862 let display = container.display.map(|(name, _)| name);
863 let app_label = container.app.clone();
864
865 if let Some(admin) = &container.admin {
875 for (label, list) in [
876 ("search_fields", &admin.search_fields),
877 ("readonly_fields", &admin.readonly_fields),
878 ("list_filter", &admin.list_filter),
879 ] {
880 if let Some((names, span)) = list {
881 for name in names {
882 if !collected.field_names.iter().any(|n| n == name) {
883 return Err(syn::Error::new(
884 *span,
885 format!(
886 "`{label} = \"{name}\"`: \"{name}\" is not a declared field on this struct"
887 ),
888 ));
889 }
890 }
891 }
892 }
893 if let Some((pairs, span)) = &admin.ordering {
894 for (name, _) in pairs {
895 if !collected.field_names.iter().any(|n| n == name) {
896 return Err(syn::Error::new(
897 *span,
898 format!(
899 "`ordering = \"{name}\"`: \"{name}\" is not a declared field on this struct"
900 ),
901 ));
902 }
903 }
904 }
905 if let Some((groups, span)) = &admin.fieldsets {
906 for (_, fields) in groups {
907 for name in fields {
908 if !collected.field_names.iter().any(|n| n == name) {
909 return Err(syn::Error::new(
910 *span,
911 format!(
912 "`fieldsets`: \"{name}\" is not a declared field on this struct"
913 ),
914 ));
915 }
916 }
917 }
918 }
919 }
920 if let Some(audit) = &container.audit {
921 if let Some((names, span)) = &audit.track {
922 for name in names {
923 if !collected.field_names.iter().any(|n| n == name) {
924 return Err(syn::Error::new(
925 *span,
926 format!(
927 "`audit(track = \"{name}\")`: \"{name}\" is not a declared field on this struct"
928 ),
929 ));
930 }
931 }
932 }
933 }
934
935 for (col, _desc, span) in &container.default_order {
939 if !collected.field_names.iter().any(|n| n == col) {
940 return Err(syn::Error::new(
941 *span,
942 format!(
943 "`default_order = \"...\"`: \"{col}\" is not a declared field on this struct"
944 ),
945 ));
946 }
947 }
948
949 check_known_columns(&container.indexes, &container.get_latest_by, &collected)?;
950
951 let audit_track_names: Option<Vec<String>> = container.audit.as_ref().map(|audit| {
954 audit
955 .track
956 .as_ref()
957 .map(|(names, _)| names.clone())
958 .unwrap_or_default()
959 });
960
961 let upsert_unique: Option<Vec<String>> = container
964 .indexes
965 .iter()
966 .find(|i| i.unique && i.where_clause.is_none() && !i.columns.is_empty())
967 .map(|i| i.columns.clone());
968
969 let mut all_indexes: Vec<IndexAttr> = container.indexes;
971 for field in &named.named {
972 let ident = field.ident.as_ref().expect("named");
973 let col = ident_name(ident);
975 if let Ok(fa) = parse_field_attrs(field) {
977 if fa.index {
978 let col_name = fa.column.clone().unwrap_or_else(|| col.clone());
979 let auto_name = capped_index_name(if fa.index_unique {
980 format!("{table}_{col_name}_uq_idx")
981 } else {
982 format!("{table}_{col_name}_idx")
983 });
984 all_indexes.push(IndexAttr {
985 span: field.span(),
986 name: fa.index_name.or(Some(auto_name)),
987 columns: vec![col_name],
988 unique: fa.index_unique,
989 method: fa.index_method,
990 where_clause: None,
991 include: Vec::new(),
992 });
993 }
994 }
995 }
996 for field in &named.named {
998 let (Ok(fa), Ok(det)) = (parse_field_attrs(field), detect_type(&field.ty)) else {
999 continue;
1000 };
1001 if det.kind != DetectedKind::String || fa.max_length.is_some() {
1002 continue;
1003 }
1004 let col = fa
1005 .column
1006 .clone()
1007 .unwrap_or_else(|| ident_name(field.ident.as_ref().expect("named")));
1008 let relation = det.fk_inner.is_some() || fa.fk.is_some() || fa.o2o.is_some();
1010 if fa.primary_key
1011 || fa.unique
1012 || relation
1013 || all_indexes.iter().any(|i| i.columns.contains(&col))
1014 {
1015 return Err(syn::Error::new_spanned(
1016 field,
1017 "a primary-key, unique, indexed or foreign-key `String` needs \
1018 `max_length`: MySQL cannot index an unbounded (LONGTEXT) column",
1019 ));
1020 }
1021 }
1022
1023 let model_impl = model_impl_tokens(
1024 struct_name,
1025 &model_name,
1026 &table,
1027 display.as_deref(),
1028 app_label.as_deref(),
1029 container.admin.as_ref(),
1030 &container.default_order,
1031 &collected.field_schemas,
1032 collected.soft_delete_column.as_deref(),
1033 container.permissions,
1034 audit_track_names.as_deref(),
1035 &container.m2m,
1036 &all_indexes,
1037 &container.checks,
1038 &container.excludes,
1039 &container.composite_fks,
1040 &container.generic_fks,
1041 container.scope.as_deref(),
1042 container.is_view,
1043 container.verbose_name.as_deref(),
1044 container.verbose_name_plural.as_deref(),
1045 container.managed,
1046 container.base_manager_name.as_deref(),
1047 container.order_with_respect_to.as_deref(),
1048 container.proxy,
1049 &container.required_db_features,
1050 container.required_db_vendor.as_deref(),
1051 container.default_related_name.as_deref(),
1052 container.db_table_comment.as_deref(),
1053 container
1054 .get_latest_by
1055 .as_ref()
1056 .map(|(c, d, _)| (c.as_str(), *d)),
1057 &container.extra_permissions,
1058 &container.default_permissions,
1059 &container.global_scopes,
1060 &container.reverse_has_relations,
1061 &container.generic_has_relations,
1062 );
1063 let module_ident = column_module_ident(struct_name);
1064 let column_consts = column_const_tokens(&module_ident, &collected.column_entries);
1065 let audited_fields: Option<Vec<&ColumnEntry>> = container.audit.as_ref().map(|audit| {
1066 let track_set: Option<std::collections::HashSet<&str>> = audit
1067 .track
1068 .as_ref()
1069 .map(|(names, _)| names.iter().map(String::as_str).collect());
1070 collected
1071 .column_entries
1072 .iter()
1073 .filter(|c| {
1074 track_set
1075 .as_ref()
1076 .map_or(true, |s| s.contains(c.name.as_str()))
1077 })
1078 .collect()
1079 });
1080 let inherent_impl = inherent_impl_tokens(
1081 struct_name,
1082 &collected,
1083 collected.primary_key.as_ref(),
1084 &column_consts,
1085 audited_fields.as_deref(),
1086 upsert_unique.as_deref(),
1087 &container.manager_fns,
1088 );
1089 let column_module = column_module_tokens(&module_ident, struct_name, &collected.column_entries);
1090 let from_row_impl = from_row_impl_tokens(
1091 struct_name,
1092 &collected.from_row_inits,
1093 &collected.from_row_inits_my,
1094 );
1095 let reverse_helpers = reverse_helper_tokens(
1096 struct_name,
1097 &collected.fk_relations,
1098 container.default_related_name.as_deref(),
1099 )?;
1100 let m2m_accessors = m2m_accessor_tokens(struct_name, &container.m2m);
1101 let generic_m2m_accessors = generic_m2m_accessor_tokens(struct_name, &container.generic_m2m);
1102 let through_accessors = through_accessor_tokens(struct_name, &container.through_relations);
1104 let reverse_has_accessors = reverse_has_accessor_tokens(
1106 struct_name,
1107 &container.reverse_has_relations,
1108 &collected.column_entries,
1109 );
1110 let generic_fk_accessors = generic_fk_accessor_tokens(
1111 struct_name,
1112 &container.generic_fks,
1113 &collected.column_entries,
1114 );
1115
1116 Ok(quote! {
1117 #model_impl
1118 #inherent_impl
1119 #from_row_impl
1120 #column_module
1121 #reverse_helpers
1122 #m2m_accessors
1123 #generic_m2m_accessors
1124 #through_accessors
1125 #reverse_has_accessors
1126 #generic_fk_accessors
1127
1128 #root::core::inventory::submit! {
1129 #root::core::ModelEntry::new(
1134 <#struct_name as #root::core::Model>::SCHEMA,
1135 ::core::module_path!(),
1136 )
1137 .with_audited(
1138 <#struct_name as #root::core::Model>::__rustango_audited_update,
1139 <#struct_name as #root::core::Model>::__rustango_audited_delete,
1140 )
1141 .with_audited_create(<#struct_name as #root::core::Model>::__rustango_audited_create)
1142 .with_audited_update_record(
1143 <#struct_name as #root::core::Model>::__rustango_audited_update_record,
1144 )
1145 .with_audited_cascade(<#struct_name as #root::core::Model>::__rustango_audited_cascade)
1146 }
1147 })
1148}
1149
1150fn check_known_columns(
1153 indexes: &[IndexAttr],
1154 get_latest_by: &Option<(String, bool, proc_macro2::Span)>,
1155 collected: &CollectedFields,
1156) -> syn::Result<()> {
1157 let is_column = |c: &str| collected.column_entries.iter().any(|e| e.column == c);
1158 let plain = |c: &str| c.chars().all(|ch| ch.is_ascii_alphanumeric() || ch == '_');
1160 let mut found: Vec<(proc_macro2::Span, String)> = Vec::new();
1161 for idx in indexes {
1162 for col in idx.columns.iter().filter(|c| plain(c) && !is_column(c)) {
1163 found.push((
1164 idx.span,
1165 format!("index column `{col}` is not a column of this model; its DDL will fail"),
1166 ));
1167 }
1168 }
1169 if let Some((col, _, span)) = get_latest_by {
1170 if !is_column(col) && !collected.field_names.iter().any(|n| n == col) {
1171 found.push((
1172 *span,
1173 format!("`get_latest_by`: `{col}` is not a field of this model"),
1174 ));
1175 }
1176 }
1177 let mut errors = found
1178 .into_iter()
1179 .map(|(span, note)| syn::Error::new(span, note));
1180 let Some(mut first) = errors.next() else {
1181 return Ok(());
1182 };
1183 first.extend(errors);
1184 Err(first)
1185}
1186
1187fn join_guard_tokens(rel: &FkRelation, receiver: &TokenStream2) -> TokenStream2 {
1190 let root = rustango_root();
1191 let parent_ty = &rel.parent_type;
1192 let field_ident = &rel.field_ident;
1193 let null_fk = if rel.nullable {
1194 quote! {
1195 if #receiver.#field_ident.is_none() {
1196 return ::core::result::Result::Ok(false);
1197 }
1198 }
1199 } else {
1200 quote! {}
1201 };
1202 quote! {
1203 #null_fk
1204 #root::sql::__rustango_require_join(
1205 row, <#parent_ty as #root::core::Model>::SCHEMA, alias,
1206 )?;
1207 }
1208}
1209
1210fn loaded_reuse_tokens(
1214 rel: &FkRelation,
1215 receiver: &TokenStream2,
1216 load: &TokenStream2,
1217) -> TokenStream2 {
1218 let root = rustango_root();
1219 let field_ident = &rel.field_ident;
1220 let pattern = if rel.nullable {
1221 quote!(::core::option::Option::Some(#root::sql::ForeignKey::Loaded { value: __loaded, .. }))
1222 } else {
1223 quote!(#root::sql::ForeignKey::Loaded { value: __loaded, .. })
1224 };
1225 quote! {
1226 if let #pattern = &mut #receiver.#field_ident {
1227 if let ::core::option::Option::Some(__r) = __rest {
1228 let _ = #load(&mut **__loaded, row, __r, &__next_alias)?;
1229 }
1230 return ::core::result::Result::Ok(true);
1231 }
1232 }
1233}
1234
1235fn loaded_assign_tokens(rel: &FkRelation, receiver: &TokenStream2) -> TokenStream2 {
1238 let root = rustango_root();
1239 let field_ident = &rel.field_ident;
1240 if rel.nullable {
1241 quote! {
1242 if let ::core::option::Option::Some(__fk) = &mut #receiver.#field_ident {
1243 let _pk = ::core::clone::Clone::clone(__fk.pk_ref());
1244 *__fk = #root::sql::ForeignKey::loaded(_pk, _parent);
1245 }
1246 }
1247 } else {
1248 quote! {
1249 let _pk = ::core::clone::Clone::clone(#receiver.#field_ident.pk_ref());
1250 #receiver.#field_ident = #root::sql::ForeignKey::loaded(_pk, _parent);
1251 }
1252 }
1253}
1254
1255fn load_related_impl_tokens(struct_name: &syn::Ident, fk_relations: &[FkRelation]) -> TokenStream2 {
1266 let root = rustango_root();
1267 let arms = fk_relations.iter().map(|rel| {
1268 let parent_ty = &rel.parent_type;
1269 let field_ident = &rel.field_ident;
1270 let fk_name = ident_name(field_ident);
1271 let fk_col = fk_name.as_str();
1272 let guard = join_guard_tokens(rel, "e!(self));
1273 let reuse = loaded_reuse_tokens(
1274 rel,
1275 "e!(self),
1276 "e!(#root::sql::LoadRelated::__rustango_load_related),
1277 );
1278 let assign = loaded_assign_tokens(rel, "e!(self));
1279 quote! {
1280 #fk_col => {
1281 #guard
1282 #reuse
1283 let mut _parent: #parent_ty = <#parent_ty>::__rustango_from_aliased_row(row, alias)?;
1284 if let ::core::option::Option::Some(__r) = __rest {
1290 let _ = #root::sql::LoadRelated::__rustango_load_related(
1291 &mut _parent, row, __r, &__next_alias,
1292 )?;
1293 }
1294 #assign
1295 ::core::result::Result::Ok(true)
1296 }
1297 }
1298 });
1299 quote! {
1300 #[cfg(feature = "postgres")]
1301 impl #root::sql::LoadRelated for #struct_name {
1302 #[allow(unused_variables)]
1303 fn __rustango_load_related(
1304 &mut self,
1305 row: &#root::sql::sqlx::postgres::PgRow,
1306 field_name: &str,
1307 alias: &str,
1308 ) -> ::core::result::Result<bool, #root::sql::sqlx::Error> {
1309 let (__base, __rest): (&str, ::core::option::Option<&str>) =
1315 match field_name.split_once("__") {
1316 ::core::option::Option::Some((b, r)) => (b, ::core::option::Option::Some(r)),
1317 ::core::option::Option::None => (field_name, ::core::option::Option::None),
1318 };
1319 let __next_alias: ::std::string::String = match __rest {
1320 ::core::option::Option::Some(__r) => {
1321 let __rb = __r.split_once("__").map(|(b, _)| b).unwrap_or(__r);
1322 ::std::format!("{}__{}", alias, __rb)
1323 }
1324 ::core::option::Option::None => ::std::string::String::new(),
1325 };
1326 match __base {
1327 #( #arms )*
1328 _ => ::core::result::Result::Ok(false),
1329 }
1330 }
1331 }
1332 }
1333}
1334
1335fn load_related_impl_my_tokens(
1343 struct_name: &syn::Ident,
1344 fk_relations: &[FkRelation],
1345) -> TokenStream2 {
1346 let root = rustango_root();
1347 let arms = fk_relations.iter().map(|rel| {
1348 let parent_ty = &rel.parent_type;
1349 let field_ident = &rel.field_ident;
1350 let fk_name = ident_name(field_ident);
1351 let fk_col = fk_name.as_str();
1352 let guard = join_guard_tokens(rel, "e!(__self));
1353 let reuse = loaded_reuse_tokens(
1354 rel,
1355 "e!(__self),
1356 "e!(#root::sql::LoadRelatedMy::__rustango_load_related_my),
1357 );
1358 let assign = loaded_assign_tokens(rel, "e!(__self));
1359 quote! {
1364 #fk_col => {
1365 #guard
1366 #reuse
1367 let mut _parent: #parent_ty =
1368 <#parent_ty>::__rustango_from_aliased_my_row(row, alias)?;
1369 if let ::core::option::Option::Some(__r) = __rest {
1372 let _ = #root::sql::LoadRelatedMy::__rustango_load_related_my(
1373 &mut _parent, row, __r, &__next_alias,
1374 )?;
1375 }
1376 #assign
1377 ::core::result::Result::Ok(true)
1378 }
1379 }
1380 });
1381 quote! {
1382 #root::__impl_my_load_related!(#struct_name, |__self, row, field_name, alias, __rest, __next_alias| {
1383 #( #arms )*
1384 });
1385 }
1386}
1387
1388fn load_related_impl_sqlite_tokens(
1392 struct_name: &syn::Ident,
1393 fk_relations: &[FkRelation],
1394) -> TokenStream2 {
1395 let root = rustango_root();
1396 let arms = fk_relations.iter().map(|rel| {
1397 let parent_ty = &rel.parent_type;
1398 let field_ident = &rel.field_ident;
1399 let fk_name = ident_name(field_ident);
1400 let fk_col = fk_name.as_str();
1401 let guard = join_guard_tokens(rel, "e!(__self));
1402 let reuse = loaded_reuse_tokens(
1403 rel,
1404 "e!(__self),
1405 "e!(#root::sql::LoadRelatedSqlite::__rustango_load_related_sqlite),
1406 );
1407 let assign = loaded_assign_tokens(rel, "e!(__self));
1408 quote! {
1409 #fk_col => {
1410 #guard
1411 #reuse
1412 let mut _parent: #parent_ty =
1413 <#parent_ty>::__rustango_from_aliased_sqlite_row(row, alias)?;
1414 if let ::core::option::Option::Some(__r) = __rest {
1417 let _ = #root::sql::LoadRelatedSqlite::__rustango_load_related_sqlite(
1418 &mut _parent, row, __r, &__next_alias,
1419 )?;
1420 }
1421 #assign
1422 ::core::result::Result::Ok(true)
1423 }
1424 }
1425 });
1426 quote! {
1427 #root::__impl_sqlite_load_related!(#struct_name, |__self, row, field_name, alias, __rest, __next_alias| {
1428 #( #arms )*
1429 });
1430 }
1431}
1432
1433fn fk_pk_access_impl_tokens(struct_name: &syn::Ident, fk_relations: &[FkRelation]) -> TokenStream2 {
1441 let root = rustango_root();
1442 let arms = fk_relations.iter().map(|rel| {
1443 let field_ident = &rel.field_ident;
1444 let fk_name = ident_name(field_ident);
1445 let fk_col = fk_name.as_str();
1446 if rel.pk_kind == DetectedKind::I64 {
1447 if rel.nullable {
1453 quote! {
1454 #fk_col => self.#field_ident
1455 .as_ref()
1456 .map(|fk| #root::sql::ForeignKey::pk(fk)),
1457 }
1458 } else {
1459 quote! {
1460 #fk_col => ::core::option::Option::Some(self.#field_ident.pk()),
1461 }
1462 }
1463 } else {
1464 quote! {
1472 #fk_col => ::core::option::Option::None,
1473 }
1474 }
1475 });
1476 let value_arms = fk_relations.iter().map(|rel| {
1482 let field_ident = &rel.field_ident;
1483 let fk_name = ident_name(field_ident);
1484 let fk_col = fk_name.as_str();
1485 if rel.nullable {
1486 quote! {
1487 #fk_col => self.#field_ident
1488 .as_ref()
1489 .map(|fk| ::core::convert::Into::<#root::core::SqlValue>::into(
1490 #root::sql::ForeignKey::pk(fk)
1491 )),
1492 }
1493 } else {
1494 quote! {
1495 #fk_col => ::core::option::Option::Some(
1496 ::core::convert::Into::<#root::core::SqlValue>::into(
1497 self.#field_ident.pk()
1498 )
1499 ),
1500 }
1501 }
1502 });
1503 quote! {
1504 impl #root::sql::FkPkAccess for #struct_name {
1505 #[allow(unused_variables)]
1506 fn __rustango_fk_pk(&self, field_name: &str) -> ::core::option::Option<i64> {
1507 match field_name {
1508 #( #arms )*
1509 _ => ::core::option::Option::None,
1510 }
1511 }
1512 #[allow(unused_variables)]
1513 fn __rustango_fk_pk_value(
1514 &self,
1515 field_name: &str,
1516 ) -> ::core::option::Option<#root::core::SqlValue> {
1517 match field_name {
1518 #( #value_arms )*
1519 _ => ::core::option::Option::None,
1520 }
1521 }
1522 }
1523 }
1524}
1525
1526const NO_REVERSE_ACCESSOR: &str = "+";
1528
1529fn reverse_helper_tokens(
1537 child_ident: &syn::Ident,
1538 fk_relations: &[FkRelation],
1539 default_related_name: Option<&str>,
1540) -> syn::Result<TokenStream2> {
1541 let root = rustango_root();
1542 let fk_relations: Vec<&FkRelation> = fk_relations
1544 .iter()
1545 .filter(|rel| rel.related_name.as_deref() != Some(NO_REVERSE_ACCESSOR))
1546 .collect();
1547 if fk_relations.is_empty() {
1548 return Ok(TokenStream2::new());
1549 }
1550 let default_pg_suffix = default_related_name
1564 .map(str::to_owned)
1565 .unwrap_or_else(|| format!("{}_set", to_snake_case(&ident_name(child_ident))));
1566 let name_of = |rel: &FkRelation| {
1567 rel.related_name
1568 .clone()
1569 .unwrap_or_else(|| default_pg_suffix.clone())
1570 };
1571 for (i, rel) in fk_relations.iter().enumerate() {
1573 let parent_ty = &rel.parent_type;
1574 let parent = quote!(#parent_ty).to_string();
1575 let name = name_of(rel);
1576 if let Some(first) = fk_relations[..i].iter().find(|r| {
1577 let other = &r.parent_type;
1578 quote!(#other).to_string() == parent && name_of(r) == name
1579 }) {
1580 return Err(syn::Error::new_spanned(
1581 &rel.field_ident,
1582 format!(
1583 "`{}` and `{}` both point at `{parent}` and would both add `{parent}::{name}`; \
1584 give one `#[rustango(related_name = \"...\")]`, or `related_name = \"+\"` for none",
1585 first.field_ident, rel.field_ident
1586 ),
1587 ));
1588 }
1589 }
1590 let impls = fk_relations.iter().map(|rel| {
1591 let pg_suffix = name_of(rel);
1592 let pool_suffix = format!("{}_pool", pg_suffix);
1593 let pg_method_ident = syn::Ident::new(&pg_suffix, child_ident.span());
1594 let pool_method_ident = syn::Ident::new(&pool_suffix, child_ident.span());
1595 let parent_ty = &rel.parent_type;
1596 let fk_name = ident_name(&rel.field_ident);
1597 let fk_col = fk_name.as_str();
1598 let doc = format!(
1599 "Fetch every `{child_ident}` whose `{fk_col}` foreign key points at this row. \
1600 Single SQL query — `SELECT … FROM <{child_ident} table> WHERE {fk_col} = $1` — \
1601 generated from the FK declaration on `{child_ident}::{fk_col}`. Composes with \
1602 further `{child_ident}::objects()` filters via direct queryset use."
1603 );
1604 quote! {
1605 impl #parent_ty {
1606 #[doc = #doc]
1607 pub async fn #pg_method_ident<'_c, _E: #root::sql::SqlExecutor<'_c>>(
1615 &self,
1616 _executor: _E,
1617 ) -> ::core::result::Result<
1618 ::std::vec::Vec<#child_ident>,
1619 #root::sql::ExecError,
1620 > {
1621 let _key = #root::sql::__reverse_fk_key::<Self, #child_ident>(self, #fk_col)?;
1622 #root::query::QuerySet::<#child_ident>::new()
1623 .filter_op(#fk_col, #root::core::Op::Eq, _key)
1624 .fetch_on(_executor)
1625 .await
1626 }
1627
1628 #[deprecated(since = "0.62.0", note = "drop the `_pool` suffix")]
1633 pub async fn #pool_method_ident(
1634 &self,
1635 pool: &#root::sql::Pool,
1636 ) -> ::core::result::Result<
1637 ::std::vec::Vec<#child_ident>,
1638 #root::sql::ExecError,
1639 > {
1640 self.#pg_method_ident(pool).await
1641 }
1642 }
1643 }
1644 });
1645 Ok(quote! { #( #impls )* })
1646}
1647
1648fn generic_fk_accessor_tokens(
1662 struct_name: &syn::Ident,
1663 generic_fks: &[GenericFkAttr],
1664 column_entries: &[ColumnEntry],
1665) -> TokenStream2 {
1666 let root = rustango_root();
1667 if generic_fks.is_empty() {
1668 return TokenStream2::new();
1669 }
1670 let methods = generic_fks.iter().filter_map(|gfk| {
1671 let ct_ident = column_entries
1677 .iter()
1678 .find(|c| c.column == gfk.ct_column)
1679 .map(|c| c.ident.clone())?;
1680 let pk_ident = column_entries
1681 .iter()
1682 .find(|c| c.column == gfk.pk_column)
1683 .map(|c| c.ident.clone())?;
1684
1685 let accessor_ident =
1686 syn::Ident::new(&format!("{}_pool", gfk.name), struct_name.span());
1687 let setter_ident =
1688 syn::Ident::new(&format!("set_{}_for", gfk.name), struct_name.span());
1689 let name_literal = gfk.name.as_str();
1690
1691 Some(quote! {
1692 #[doc = concat!(
1693 "Resolve the polymorphic `",
1694 #name_literal,
1695 "` relation. Reads `self.",
1696 stringify!(#ct_ident),
1697 "` + `self.",
1698 stringify!(#pk_ident),
1699 "`, looks up the matching `ContentType`, and fetches the target row as a JSON map.\n\n",
1700 "Returns `Ok(None)` when the ContentType is stale / unseeded or the target row was deleted. Emitted by `#[rustango(generic_fk(name = \"",
1701 #name_literal,
1702 "\", ...))]`."
1703 )]
1704 pub async fn #accessor_ident(
1705 &self,
1706 pool: &#root::sql::Pool,
1707 ) -> ::core::result::Result<
1708 ::core::option::Option<#root::__serde_json::Value>,
1709 #root::sql::ExecError,
1710 > {
1711 let gfk = #root::contenttypes::GenericForeignKey::new(
1712 self.#ct_ident as i64,
1713 self.#pk_ident as i64,
1714 );
1715 gfk.get_object(pool).await
1716 }
1717
1718 #[doc = concat!(
1719 "Set the polymorphic `",
1720 #name_literal,
1721 "` target. Looks up the `ContentType` for `T` via the cached registry, then assigns both `self.",
1722 stringify!(#ct_ident),
1723 "` and `self.",
1724 stringify!(#pk_ident),
1725 "`.\n\nFollow with `self.insert(pool)` or `self.update(pool)` to persist. Emitted by `#[rustango(generic_fk(name = \"",
1726 #name_literal,
1727 "\", ...))]`."
1728 )]
1729 pub async fn #setter_ident<T: #root::core::Model>(
1730 &mut self,
1731 pool: &#root::sql::Pool,
1732 target_pk: i64,
1733 ) -> ::core::result::Result<(), #root::sql::ExecError> {
1734 let gfk = #root::contenttypes::GenericForeignKey::for_target::<T>(
1735 pool,
1736 target_pk,
1737 ).await?;
1738 self.#ct_ident = gfk.content_type_id as _;
1739 self.#pk_ident = gfk.object_pk as _;
1740 ::core::result::Result::Ok(())
1741 }
1742 })
1743 });
1744 quote! {
1745 impl #struct_name {
1746 #( #methods )*
1747 }
1748 }
1749}
1750
1751fn m2m_accessor_tokens(struct_name: &syn::Ident, m2m_relations: &[M2MAttr]) -> TokenStream2 {
1752 let root = rustango_root();
1753 if m2m_relations.is_empty() {
1754 return TokenStream2::new();
1755 }
1756 let methods = m2m_relations.iter().map(|rel| {
1757 let method_name = format!("{}_m2m", rel.name);
1758 let method_ident = syn::Ident::new(&method_name, struct_name.span());
1759 let through = rel.through.as_str();
1760 let src_col = rel.src.as_str();
1761 let dst_col = rel.dst.as_str();
1762 quote! {
1763 pub fn #method_ident(&self) -> #root::sql::M2MManager {
1764 #root::sql::M2MManager {
1765 src_pk: self.__rustango_pk_value(),
1766 through: #through,
1767 src_col: #src_col,
1768 dst_col: #dst_col,
1769 }
1770 }
1771 }
1772 });
1773 quote! {
1774 impl #struct_name {
1775 #( #methods )*
1776 }
1777 }
1778}
1779
1780fn generic_m2m_accessor_tokens(
1783 struct_name: &syn::Ident,
1784 relations: &[GenericM2MAttr],
1785) -> TokenStream2 {
1786 let root = rustango_root();
1787 if relations.is_empty() {
1788 return TokenStream2::new();
1789 }
1790 let methods = relations.iter().map(|rel| {
1791 let method_ident = syn::Ident::new(&format!("{}_m2m", rel.name), struct_name.span());
1792 let through = rel.through.as_str();
1793 let pk_col = rel.pk_column.as_str();
1794 let ct_col = rel.ct_column.as_str();
1795 let related_col = rel.related_column.as_str();
1796 quote! {
1797 pub fn #method_ident(&self) -> #root::sql::GenericM2MManager {
1798 #root::sql::GenericM2MManager {
1799 src_pk: self.__rustango_pk_value(),
1800 src_schema: <Self as #root::core::Model>::SCHEMA,
1801 through: #through,
1802 pk_col: #pk_col,
1803 ct_col: #ct_col,
1804 dst_col: #related_col,
1805 }
1806 }
1807 }
1808 });
1809 let entries = relations.iter().map(|rel| {
1811 let through = rel.through.as_str();
1812 let pk_col = rel.pk_column.as_str();
1813 let ct_col = rel.ct_column.as_str();
1814 quote! {
1815 #root::core::inventory::submit! {
1816 #root::core::GenericM2MEntry::new(
1817 <#struct_name as #root::core::Model>::SCHEMA,
1818 #through,
1819 #pk_col,
1820 #ct_col,
1821 )
1822 }
1823 }
1824 });
1825 quote! {
1826 impl #struct_name {
1827 #( #methods )*
1828 }
1829 #( #entries )*
1830 }
1831}
1832
1833fn reverse_has_accessor_tokens(
1855 struct_name: &syn::Ident,
1856 reverse_has_relations: &[ReverseHasAttr],
1857 columns: &[ColumnEntry],
1858) -> TokenStream2 {
1859 let root = rustango_root();
1860 if reverse_has_relations.is_empty() {
1861 return TokenStream2::new();
1862 }
1863 let methods = reverse_has_relations.iter().map(|rel| {
1864 let exists_name = format!("{}_exists_expr", rel.name);
1865 let not_exists_name = format!("{}_not_exists_expr", rel.name);
1866 let count_name = format!("{}_count", rel.name);
1867 let fetch_name = format!("{}_fetch", rel.name);
1868 let first_name = format!("{}_first", rel.name);
1869 let pluck_name = format!("{}_pluck", rel.name);
1870 let accessor_name = rel.name.as_str();
1871 let exists_ident = syn::Ident::new(&exists_name, struct_name.span());
1872 let not_exists_ident = syn::Ident::new(¬_exists_name, struct_name.span());
1873 let count_ident = syn::Ident::new(&count_name, struct_name.span());
1874 let fetch_ident = syn::Ident::new(&fetch_name, struct_name.span());
1875 let first_ident = syn::Ident::new(&first_name, struct_name.span());
1876 let pluck_ident = syn::Ident::new(&pluck_name, struct_name.span());
1877 let accessor_ident = syn::Ident::new(accessor_name, struct_name.span());
1878 let child = &rel.child;
1879 let child_fk_column = rel.child_fk_column.as_str();
1880 let self_pk_column = rel.self_pk_column();
1881 let self_value = columns
1883 .iter()
1884 .find(|c| c.column == self_pk_column)
1885 .map_or_else(
1886 || quote! { self.__rustango_pk_value() },
1887 |c| {
1888 let ident = &c.ident;
1889 quote! {
1890 ::core::convert::Into::<#root::core::SqlValue>::into(
1891 ::core::clone::Clone::clone(&self.#ident)
1892 )
1893 }
1894 },
1895 );
1896 let exists_doc = format!(
1897 "Eloquent `whereHas` analog — yields `EXISTS (SELECT 1 \
1898 FROM <{child}> WHERE {child_fk_column} = <outer>.{self_pk_column})`. \
1899 Drop into `QuerySet::<{struct_name}>::where_raw(...)` to \
1900 filter to {struct_name}s with at least one matching child.",
1901 );
1902 let not_exists_doc = format!(
1903 "Eloquent `whereDoesntHave` analog — yields `NOT EXISTS \
1904 (SELECT 1 FROM <{child}> WHERE {child_fk_column} = \
1905 <outer>.{self_pk_column})`. Drop into \
1906 `QuerySet::<{struct_name}>::where_raw(...)` to filter to \
1907 {struct_name}s with **no** matching child.",
1908 );
1909 let count_doc = format!(
1910 "Eloquent `$model->{name}->count()` analog — returns \
1911 the number of `{child}` rows whose `{child_fk_column}` \
1912 matches this `{struct_name}` instance's `{self_pk_column}`. \
1913 Issued as `SELECT COUNT(*) FROM <{child}> WHERE \
1914 {child_fk_column} = <self.{self_pk_column}>`.",
1915 name = rel.name,
1916 );
1917 let accessor_doc = format!(
1918 "Eloquent `$model->{name}` accessor — returns a \
1919 `QuerySet<{child}>` filtered to rows whose \
1920 `{child_fk_column}` matches this `{struct_name}` \
1921 instance's `{self_pk_column}`. **Chainable**: compose `.filter()` \
1922 / `.order_by()` / `.limit()` etc. on top, then call \
1923 `.fetch(&pool)` (the QuerySet trait method) when \
1924 done. For the simple \"fetch all\" hot path with no \
1925 further composition, prefer the bare-name \
1926 `{name}_fetch(&pool)` companion.",
1927 name = rel.name,
1928 );
1929 let fetch_doc = format!(
1930 "Eloquent `$model->{name}->get()` — bare-name hot-path \
1931 over `{name}(&self).fetch(&pool)`. Use this when \
1932 you don't need further `.filter()` / `.order_by()` \
1933 composition; falls back to the chainable accessor when \
1934 you do. Avoids the `_pool` suffix on the most common \
1935 call-site shape.",
1936 name = rel.name,
1937 );
1938 quote! {
1939 #[doc = #accessor_doc]
1940 pub fn #accessor_ident(&self) -> #root::query::QuerySet<#child> {
1941 #root::query::QuerySet::<#child>::new()
1942 .filter(#child_fk_column, #self_value)
1943 }
1944
1945 #[doc = #fetch_doc]
1946 pub async fn #fetch_ident(
1947 &self,
1948 pool: &#root::sql::Pool,
1949 ) -> ::core::result::Result<
1950 ::std::vec::Vec<#child>,
1951 #root::sql::ExecError,
1952 > {
1953 use #root::sql::FetcherPool as _;
1954 self.#accessor_ident().fetch(pool).await
1955 }
1956
1957 pub async fn #first_ident(
1964 &self,
1965 pool: &#root::sql::Pool,
1966 ) -> ::core::result::Result<
1967 ::core::option::Option<#child>,
1968 #root::sql::ExecError,
1969 > {
1970 self.#accessor_ident().first(pool).await
1971 }
1972
1973 pub async fn #pluck_ident<U>(
1978 &self,
1979 col: &'static str,
1980 pool: &#root::sql::Pool,
1981 ) -> ::core::result::Result<
1982 ::std::vec::Vec<U>,
1983 #root::sql::ExecError,
1984 >
1985 where
1986 U: #root::sql::FlatScalar,
1987 {
1988 self.#accessor_ident()
1989 .values_list_flat(col)
1990 .fetch::<U>(pool)
1991 .await
1992 }
1993
1994 #[doc = #exists_doc]
1995 pub fn #exists_ident() -> #root::core::WhereExpr {
1996 use #root::core::{Expr, Model as _, Op, SelectQuery, WhereExpr};
1997 let child_schema =
1998 <#child as #root::core::Model>::SCHEMA;
1999 let inner = SelectQuery::new(child_schema).where_clause(WhereExpr::ExprCompare {
2000 lhs: Expr::Column(#child_fk_column),
2001 op: Op::Eq,
2002 rhs: Expr::OuterRef(#self_pk_column),
2003 });
2004 WhereExpr::Exists(::std::boxed::Box::new(inner))
2005 }
2006
2007 #[doc = #not_exists_doc]
2008 pub fn #not_exists_ident() -> #root::core::WhereExpr {
2009 use #root::core::{Expr, Model as _, Op, SelectQuery, WhereExpr};
2010 let child_schema =
2011 <#child as #root::core::Model>::SCHEMA;
2012 let inner = SelectQuery::new(child_schema).where_clause(WhereExpr::ExprCompare {
2013 lhs: Expr::Column(#child_fk_column),
2014 op: Op::Eq,
2015 rhs: Expr::OuterRef(#self_pk_column),
2016 });
2017 WhereExpr::NotExists(::std::boxed::Box::new(inner))
2018 }
2019
2020 #[doc = #count_doc]
2021 pub async fn #count_ident(
2022 &self,
2023 pool: &#root::sql::Pool,
2024 ) -> ::core::result::Result<
2025 i64,
2026 #root::sql::ExecError,
2027 > {
2028 use #root::sql::CounterPool as _;
2029 self.#accessor_ident().count(pool).await
2030 }
2031 }
2032 });
2033 quote! {
2034 impl #struct_name {
2035 #( #methods )*
2036 }
2037 }
2038}
2039
2040fn through_accessor_tokens(
2058 struct_name: &syn::Ident,
2059 through_relations: &[ThroughAttr],
2060) -> TokenStream2 {
2061 let root = rustango_root();
2062 if through_relations.is_empty() {
2063 return TokenStream2::new();
2064 }
2065 let methods = through_relations.iter().map(|rel| {
2066 let method_name = format!("{}_through", rel.name);
2067 let count_name = format!("{}_through_count", rel.name);
2068 let fetch_name = format!("{}_through_fetch", rel.name);
2069 let first_name = format!("{}_through_first", rel.name);
2070 let pluck_name = format!("{}_through_pluck", rel.name);
2071 let method_ident = syn::Ident::new(&method_name, struct_name.span());
2072 let count_ident = syn::Ident::new(&count_name, struct_name.span());
2073 let fetch_ident = syn::Ident::new(&fetch_name, struct_name.span());
2074 let first_ident = syn::Ident::new(&first_name, struct_name.span());
2075 let pluck_ident = syn::Ident::new(&pluck_name, struct_name.span());
2076 let far = &rel.far;
2077 let intermediate = &rel.intermediate;
2078 let far_fk_column = rel.far_fk_column.as_str();
2079 let intermediate_fk_column = rel.intermediate_fk_column.as_str();
2080 let intermediate_pk_column = rel.intermediate_pk_column.as_str();
2081 let doc = format!(
2082 "Eloquent `hasManyThrough` accessor — returns a \
2083 `QuerySet<{far}>` whose rows reach this `{struct_name}` \
2084 instance through the intermediate `{intermediate}` table. \
2085 Generated SQL shape: \
2086 `… WHERE {far_fk_column} IN (SELECT \
2087 {intermediate_pk_column} FROM <{intermediate}> WHERE \
2088 {intermediate_fk_column} = self.pk)`. Chainable like any \
2089 other QuerySet — compose `.filter()` / `.order_by()` / \
2090 `.limit()` etc. on top.",
2091 );
2092 let count_doc = format!(
2093 "Eloquent `$model->{name}->count()` analog for the \
2094 through-relation — returns the number of `{far}` rows \
2095 reachable through `{intermediate}`. Equivalent to \
2096 `self.{name}_through().count(pool)` but spelled \
2097 as a bare instance method for parity with the \
2098 `reverse_has` `<name>_count` shape.",
2099 name = rel.name,
2100 );
2101 quote! {
2102 #[doc = #doc]
2103 pub fn #method_ident(&self) -> #root::query::QuerySet<#far> {
2104 use #root::core::{Filter, Model as _, Op, SelectQuery, WhereExpr};
2105 let intermediate_schema =
2106 <#intermediate as #root::core::Model>::SCHEMA;
2107 let sub = SelectQuery::new(intermediate_schema)
2108 .where_clause(WhereExpr::Predicate(Filter::new(
2109 #intermediate_fk_column,
2110 Op::Eq,
2111 self.__rustango_pk_value(),
2112 )))
2113 .projection(::std::vec![#intermediate_pk_column]);
2114 #root::query::QuerySet::<#far>::new().where_raw(
2115 WhereExpr::InSubquery {
2116 column: #far_fk_column,
2117 negated: false,
2118 subquery: ::std::boxed::Box::new(sub),
2119 },
2120 )
2121 }
2122
2123 #[doc = #count_doc]
2124 pub async fn #count_ident(
2125 &self,
2126 pool: &#root::sql::Pool,
2127 ) -> ::core::result::Result<
2128 i64,
2129 #root::sql::ExecError,
2130 > {
2131 use #root::sql::CounterPool as _;
2132 self.#method_ident().count(pool).await
2133 }
2134
2135 pub async fn #fetch_ident(
2143 &self,
2144 pool: &#root::sql::Pool,
2145 ) -> ::core::result::Result<
2146 ::std::vec::Vec<#far>,
2147 #root::sql::ExecError,
2148 > {
2149 use #root::sql::FetcherPool as _;
2150 self.#method_ident().fetch(pool).await
2151 }
2152
2153 pub async fn #first_ident(
2160 &self,
2161 pool: &#root::sql::Pool,
2162 ) -> ::core::result::Result<
2163 ::core::option::Option<#far>,
2164 #root::sql::ExecError,
2165 > {
2166 self.#method_ident().first(pool).await
2167 }
2168
2169 pub async fn #pluck_ident<U>(
2173 &self,
2174 col: &'static str,
2175 pool: &#root::sql::Pool,
2176 ) -> ::core::result::Result<
2177 ::std::vec::Vec<U>,
2178 #root::sql::ExecError,
2179 >
2180 where
2181 U: #root::sql::FlatScalar,
2182 {
2183 self.#method_ident()
2184 .values_list_flat(col)
2185 .fetch::<U>(pool)
2186 .await
2187 }
2188 }
2189 });
2190 quote! {
2191 impl #struct_name {
2192 #( #methods )*
2193 }
2194 }
2195}
2196
2197struct ColumnEntry {
2198 ident: syn::Ident,
2201 value_ty: Type,
2203 name: String,
2205 column: String,
2207 field_type_tokens: TokenStream2,
2209 uuid_column: bool,
2211}
2212
2213struct CollectedFields {
2214 field_schemas: Vec<TokenStream2>,
2215 from_row_inits: Vec<TokenStream2>,
2216 from_aliased_row_inits: Vec<TokenStream2>,
2220 from_row_inits_my: Vec<TokenStream2>,
2222 from_aliased_row_inits_my: Vec<TokenStream2>,
2223 insert_columns: Vec<TokenStream2>,
2226 insert_values: Vec<TokenStream2>,
2229 insert_pushes: Vec<TokenStream2>,
2234 returning_cols: Vec<TokenStream2>,
2237 auto_assigns: Vec<TokenStream2>,
2240 auto_field_idents: Vec<(syn::Ident, String, bool)>,
2244 generated_field_idents: Vec<(syn::Ident, String)>,
2249 first_db_auto: Option<(syn::Ident, Type)>,
2252 bulk_pushes_no_auto: Vec<TokenStream2>,
2256 bulk_pushes_all: Vec<TokenStream2>,
2260 bulk_columns_no_auto: Vec<TokenStream2>,
2263 bulk_columns_all: Vec<TokenStream2>,
2266 bulk_fill_in_place: Vec<TokenStream2>,
2269 rust_filled_idents: Vec<syn::Ident>,
2271 auto_now_idents: Vec<syn::Ident>,
2273 uuid_v7_fields: Vec<(syn::Ident, String)>,
2276 bulk_auto_uniformity: Vec<TokenStream2>,
2280 first_auto_ident: Option<syn::Ident>,
2283 has_auto: bool,
2285 pk_is_auto: bool,
2289 update_assignments: Vec<TokenStream2>,
2292 upsert_update_columns: Vec<TokenStream2>,
2295 primary_key: Option<(syn::Ident, String)>,
2296 column_entries: Vec<ColumnEntry>,
2297 field_names: Vec<String>,
2300 fk_relations: Vec<FkRelation>,
2305 soft_delete_column: Option<String>,
2310 soft_delete_field_ident: Option<syn::Ident>,
2314}
2315
2316#[derive(Clone)]
2317struct FkRelation {
2318 parent_type: Type,
2321 field_ident: syn::Ident,
2324 pk_kind: DetectedKind,
2329 nullable: bool,
2334 related_name: Option<String>,
2339}
2340
2341fn collect_fields(named: &syn::FieldsNamed, table: &str) -> syn::Result<CollectedFields> {
2342 let root = rustango_root();
2343 let cap = named.named.len();
2344 let mut out = CollectedFields {
2345 field_schemas: Vec::with_capacity(cap),
2346 from_row_inits: Vec::with_capacity(cap),
2347 from_aliased_row_inits: Vec::with_capacity(cap),
2348 from_row_inits_my: Vec::with_capacity(cap),
2349 from_aliased_row_inits_my: Vec::with_capacity(cap),
2350 insert_columns: Vec::with_capacity(cap),
2351 insert_values: Vec::with_capacity(cap),
2352 insert_pushes: Vec::with_capacity(cap),
2353 returning_cols: Vec::new(),
2354 auto_assigns: Vec::new(),
2355 auto_field_idents: Vec::new(),
2356 generated_field_idents: Vec::new(),
2357 first_db_auto: None,
2358 bulk_pushes_no_auto: Vec::with_capacity(cap),
2359 bulk_pushes_all: Vec::with_capacity(cap),
2360 bulk_columns_no_auto: Vec::with_capacity(cap),
2361 bulk_columns_all: Vec::with_capacity(cap),
2362 bulk_fill_in_place: Vec::new(),
2363 rust_filled_idents: Vec::new(),
2364 auto_now_idents: Vec::new(),
2365 uuid_v7_fields: Vec::new(),
2366 bulk_auto_uniformity: Vec::new(),
2367 first_auto_ident: None,
2368 has_auto: false,
2369 pk_is_auto: false,
2370 update_assignments: Vec::with_capacity(cap),
2371 upsert_update_columns: Vec::with_capacity(cap),
2372 primary_key: None,
2373 column_entries: Vec::with_capacity(cap),
2374 field_names: Vec::with_capacity(cap),
2375 fk_relations: Vec::new(),
2376 soft_delete_column: None,
2377 soft_delete_field_ident: None,
2378 };
2379
2380 for field in &named.named {
2381 let info = process_field(field, table)?;
2382 out.field_names.push(ident_name(info.ident));
2383 out.field_schemas.push(info.schema);
2384 out.from_row_inits.push(info.from_row_init);
2385 out.from_aliased_row_inits.push(info.from_aliased_row_init);
2386 out.from_row_inits_my.push(info.from_row_init_my);
2387 out.from_aliased_row_inits_my
2388 .push(info.from_aliased_row_init_my);
2389 if let Some(parent_ty) = info.fk_inner.clone() {
2390 out.fk_relations.push(FkRelation {
2391 parent_type: parent_ty,
2392 field_ident: info.ident.clone(),
2393 pk_kind: info.fk_pk_kind,
2394 nullable: info.nullable,
2395 related_name: info.related_name.clone(),
2396 });
2397 }
2398 if info.soft_delete {
2399 if out.soft_delete_column.is_some() {
2400 return Err(syn::Error::new_spanned(
2401 field,
2402 "only one field may be marked `#[rustango(soft_delete)]`",
2403 ));
2404 }
2405 out.soft_delete_column = Some(info.column.clone());
2406 out.soft_delete_field_ident = Some(info.ident.clone());
2407 }
2408 let column = info.column.as_str();
2409 let ident = info.ident;
2410 if info.generated_as.is_some() {
2419 out.column_entries.push(ColumnEntry {
2420 ident: ident.clone(),
2421 value_ty: info.value_ty.clone(),
2422 name: ident_name(ident),
2423 column: info.column.clone(),
2424 field_type_tokens: info.field_type_tokens,
2425 uuid_column: info.uuid_column,
2426 });
2427 out.returning_cols.push(quote!(#column));
2434 out.generated_field_idents
2435 .push((ident.clone(), info.column.clone()));
2436 continue;
2437 }
2438 out.insert_columns.push(quote!(#column));
2439 out.insert_values.push(quote! {
2440 ::core::convert::Into::<#root::core::SqlValue>::into(
2441 ::core::clone::Clone::clone(&self.#ident)
2442 )
2443 });
2444 if info.auto {
2445 out.has_auto = true;
2446 if out.first_auto_ident.is_none() {
2447 out.first_auto_ident = Some(ident.clone());
2448 }
2449 if !info.default_uuid_v7 && !info.auto_now_add && !info.auto_now {
2462 if out.first_db_auto.is_none() {
2463 out.first_db_auto = auto_inner_type(info.value_ty)
2464 .cloned()
2465 .map(|ty| (ident.clone(), ty));
2466 }
2467 out.returning_cols.push(quote!(#column));
2468 out.auto_field_idents.push((
2469 ident.clone(),
2470 info.column.clone(),
2471 info.fk_pk_kind == DetectedKind::Uuid,
2472 ));
2473 out.auto_assigns.push(quote! {
2474 self.#ident = #root::sql::try_get_returning(_returning_row, #column)?;
2475 });
2476 }
2477 if info.default_uuid_v7 {
2478 out.insert_pushes.push(quote! {
2484 if matches!(&self.#ident, #root::sql::Auto::Unset) {
2485 self.#ident = #root::sql::Auto::Set(
2486 #root::__uuid::Uuid::now_v7(),
2487 );
2488 }
2489 if let #root::sql::Auto::Set(_v) = &self.#ident {
2490 _columns.push(#column);
2491 _values.push(::core::convert::Into::<#root::core::SqlValue>::into(
2492 ::core::clone::Clone::clone(_v)
2493 ));
2494 }
2495 });
2496 } else if info.auto_now_add || info.auto_now {
2497 out.insert_pushes.push(quote! {
2519 if matches!(&self.#ident, #root::sql::Auto::Unset) {
2520 self.#ident = #root::sql::Auto::Set(
2521 #root::__chrono::Utc::now(),
2522 );
2523 }
2524 if let #root::sql::Auto::Set(_v) = &self.#ident {
2525 _columns.push(#column);
2526 _values.push(::core::convert::Into::<#root::core::SqlValue>::into(
2527 ::core::clone::Clone::clone(_v)
2528 ));
2529 }
2530 });
2531 } else {
2532 out.insert_pushes.push(quote! {
2533 if let #root::sql::Auto::Set(_v) = &self.#ident {
2534 _columns.push(#column);
2535 _values.push(::core::convert::Into::<#root::core::SqlValue>::into(
2536 ::core::clone::Clone::clone(_v)
2537 ));
2538 }
2539 });
2540 }
2541 if info.default_uuid_v7 || info.auto_now_add || info.auto_now {
2554 out.rust_filled_idents.push(ident.clone());
2555 }
2556 if info.auto_now {
2557 out.auto_now_idents.push(ident.clone());
2558 }
2559 if info.default_uuid_v7 {
2560 out.uuid_v7_fields
2561 .push((ident.clone(), info.column.clone()));
2562 }
2563 let rust_fill = if info.default_uuid_v7 {
2564 Some(quote!(#root::__uuid::Uuid::now_v7()))
2565 } else if info.auto_now_add || info.auto_now {
2566 Some(quote!(#root::__chrono::Utc::now()))
2567 } else {
2568 None
2569 };
2570 out.bulk_columns_all.push(quote!(#column));
2573 if let Some(fill) = &rust_fill {
2574 let push = quote! {
2575 _row_vals.push(::core::convert::Into::<#root::core::SqlValue>::into(
2576 match &_row.#ident {
2577 #root::sql::Auto::Set(_v) => ::core::clone::Clone::clone(_v),
2578 #root::sql::Auto::Unset => #fill,
2579 }
2580 ));
2581 };
2582 out.bulk_columns_no_auto.push(quote!(#column));
2583 out.bulk_pushes_no_auto.push(push.clone());
2584 out.bulk_pushes_all.push(push);
2585 } else {
2586 out.bulk_pushes_all.push(quote! {
2587 _row_vals.push(::core::convert::Into::<#root::core::SqlValue>::into(
2588 ::core::clone::Clone::clone(&_row.#ident)
2589 ));
2590 });
2591 }
2592 if info.default_uuid_v7 {
2593 out.bulk_fill_in_place.push(quote! {
2594 if matches!(&_row.#ident, #root::sql::Auto::Unset) {
2595 _row.#ident = #root::sql::Auto::Set(#root::__uuid::Uuid::now_v7());
2596 }
2597 });
2598 }
2599 let ident_clone = ident.clone();
2604 if rust_fill.is_none() {
2605 out.bulk_auto_uniformity.push(quote! {
2606 for _r in rows.iter().skip(1) {
2607 if matches!(_r.#ident_clone, #root::sql::Auto::Unset) != _first_unset {
2608 return ::core::result::Result::Err(
2609 #root::sql::ExecError::Sql(
2610 #root::sql::SqlError::BulkAutoMixed
2611 )
2612 );
2613 }
2614 }
2615 });
2616 }
2617 } else {
2618 out.insert_pushes.push(quote! {
2619 _columns.push(#column);
2620 _values.push(::core::convert::Into::<#root::core::SqlValue>::into(
2621 ::core::clone::Clone::clone(&self.#ident)
2622 ));
2623 });
2624 out.bulk_columns_no_auto.push(quote!(#column));
2626 out.bulk_columns_all.push(quote!(#column));
2627 let push_expr = quote! {
2628 _row_vals.push(::core::convert::Into::<#root::core::SqlValue>::into(
2629 ::core::clone::Clone::clone(&_row.#ident)
2630 ));
2631 };
2632 out.bulk_pushes_no_auto.push(push_expr.clone());
2633 out.bulk_pushes_all.push(push_expr);
2634 }
2635 if info.primary_key {
2636 if out.primary_key.is_some() {
2637 return Err(syn::Error::new_spanned(
2638 field,
2639 "only one field may be marked `#[rustango(primary_key)]`",
2640 ));
2641 }
2642 out.primary_key = Some((ident.clone(), info.column.clone()));
2643 if info.auto {
2644 out.pk_is_auto = true;
2645 }
2646 } else if info.auto_now_add {
2647 } else if info.auto_now {
2649 out.update_assignments.push(quote! {
2652 #root::core::Assignment::new(
2653 #column,
2654 {
2655 let _now = #root::__chrono::Utc::now();
2656 self.#ident = #root::sql::Auto::Set(_now);
2657 ::core::convert::Into::<#root::core::SqlValue>::into(_now)
2658 },
2659 )
2660 });
2661 out.upsert_update_columns.push(quote!(#column));
2662 } else {
2663 out.update_assignments.push(quote! {
2664 #root::core::Assignment::new(
2665 #column,
2666 ::core::convert::Into::<#root::core::SqlValue>::into(
2667 ::core::clone::Clone::clone(&self.#ident)
2668 ),
2669 )
2670 });
2671 out.upsert_update_columns.push(quote!(#column));
2672 }
2673 out.column_entries.push(ColumnEntry {
2674 ident: ident.clone(),
2675 value_ty: info.value_ty.clone(),
2676 name: ident_name(ident),
2677 column: info.column.clone(),
2678 field_type_tokens: info.field_type_tokens,
2679 uuid_column: info.uuid_column,
2680 });
2681 }
2682 Ok(out)
2683}
2684
2685fn capped_index_name(name: String) -> String {
2688 const MAX: usize = 63;
2689 if name.len() <= MAX {
2690 return name;
2691 }
2692 let hash = name.bytes().fold(0x811c_9dc5_u32, |h, b| {
2694 (h ^ u32::from(b)).wrapping_mul(0x0100_0193)
2695 });
2696 let mut cut = MAX - 9;
2697 while !name.is_char_boundary(cut) {
2698 cut -= 1;
2699 }
2700 format!("{}_{hash:08x}", &name[..cut])
2701}
2702
2703fn model_impl_tokens(
2704 struct_name: &syn::Ident,
2705 model_name: &str,
2706 table: &str,
2707 display: Option<&str>,
2708 app_label: Option<&str>,
2709 admin: Option<&AdminAttrs>,
2710 default_order: &[(String, bool, proc_macro2::Span)],
2711 field_schemas: &[TokenStream2],
2712 soft_delete_column: Option<&str>,
2713 permissions: bool,
2714 audit_track: Option<&[String]>,
2715 m2m_relations: &[M2MAttr],
2716 indexes: &[IndexAttr],
2717 checks: &[CheckAttr],
2718 excludes: &[ExcludeAttr],
2719 composite_fks: &[CompositeFkAttr],
2720 generic_fks: &[GenericFkAttr],
2721 scope: Option<&str>,
2722 is_view: bool,
2723 verbose_name: Option<&str>,
2724 verbose_name_plural: Option<&str>,
2725 managed: bool,
2726 base_manager_name: Option<&str>,
2727 order_with_respect_to: Option<&str>,
2728 proxy: bool,
2729 required_db_features: &[String],
2730 required_db_vendor: Option<&str>,
2731 default_related_name: Option<&str>,
2732 db_table_comment: Option<&str>,
2733 get_latest_by: Option<(&str, bool)>,
2734 extra_permissions: &[(String, String)],
2735 default_permissions: &[String],
2736 global_scopes: &[GlobalScopeAttr],
2737 reverse_has_relations: &[ReverseHasAttr],
2738 generic_has_relations: &[GenericHasAttr],
2739) -> TokenStream2 {
2740 let root = rustango_root();
2741 let display_tokens = if let Some(name) = display {
2742 quote!(::core::option::Option::Some(#name))
2743 } else {
2744 quote!(::core::option::Option::None)
2745 };
2746 let app_label_tokens = if let Some(name) = app_label {
2747 quote!(::core::option::Option::Some(#name))
2748 } else {
2749 quote!(::core::option::Option::None)
2750 };
2751 let soft_delete_tokens = if let Some(col) = soft_delete_column {
2752 quote!(::core::option::Option::Some(#col))
2753 } else {
2754 quote!(::core::option::Option::None)
2755 };
2756 let audit_track_tokens = match audit_track {
2757 None => quote!(::core::option::Option::None),
2758 Some(names) => {
2759 let lits = names.iter().map(|n| n.as_str());
2760 quote!(::core::option::Option::Some(&[ #(#lits),* ]))
2761 }
2762 };
2763 let admin_tokens = admin_config_tokens(admin);
2764 let scope_tokens = match scope.map(|s| s.to_ascii_lowercase()).as_deref() {
2768 Some("registry") => quote!(#root::core::ModelScope::Registry),
2769 _ => quote!(#root::core::ModelScope::Tenant),
2770 };
2771 let verbose_name_tokens = optional_str(verbose_name);
2772 let verbose_name_plural_tokens = optional_str(verbose_name_plural);
2773 let base_manager_name_tokens = optional_str(base_manager_name);
2774 let order_with_respect_to_tokens = optional_str(order_with_respect_to);
2775 let required_db_features_lits: Vec<&str> =
2776 required_db_features.iter().map(String::as_str).collect();
2777 let required_db_vendor_tokens = optional_str(required_db_vendor);
2778 let default_related_name_tokens = optional_str(default_related_name);
2779 let db_table_comment_tokens = optional_str(db_table_comment);
2780 let get_latest_by_tokens = match get_latest_by {
2781 Some((col, desc)) => {
2782 quote!(::core::option::Option::Some((#col, #desc)))
2783 }
2784 None => quote!(::core::option::Option::None),
2785 };
2786 let extra_permission_tokens: Vec<_> = extra_permissions
2787 .iter()
2788 .map(|(c, l)| quote!((#c, #l)))
2789 .collect();
2790 let default_permission_tokens: Vec<_> = default_permissions
2791 .iter()
2792 .map(|action| quote!(#action))
2793 .collect();
2794 let indexes_tokens = indexes.iter().map(|idx| {
2795 let derived_name = idx
2800 .name
2801 .clone()
2802 .unwrap_or_else(|| capped_index_name(format!("{table}_{}_idx", idx.columns.join("_"))));
2803 let name = derived_name.as_str();
2804 let cols: Vec<&str> = idx.columns.iter().map(String::as_str).collect();
2805 let unique = idx.unique;
2806 let method_variant = match idx.method.as_str() {
2810 "gin" => quote!(#root::core::IndexMethod::Gin),
2811 "gist" => quote!(#root::core::IndexMethod::Gist),
2812 "brin" => quote!(#root::core::IndexMethod::Brin),
2813 "spgist" => quote!(#root::core::IndexMethod::SpGist),
2814 "hash" => quote!(#root::core::IndexMethod::Hash),
2815 "bloom" => quote!(#root::core::IndexMethod::Bloom),
2816 _ => quote!(#root::core::IndexMethod::BTree),
2817 };
2818 let where_clause = match &idx.where_clause {
2819 Some(s) => quote!(::core::option::Option::Some(#s)),
2820 None => quote!(::core::option::Option::None),
2821 };
2822 let include_lits: Vec<&str> = idx.include.iter().map(String::as_str).collect();
2823 quote! {{
2824 let mut i = #root::core::IndexSchema::new(#name, &[ #(#cols),* ]);
2825 i.unique = #unique;
2826 i.method = #method_variant;
2827 i.where_clause = #where_clause;
2828 i.include = &[ #(#include_lits),* ];
2829 i
2830 }}
2831 });
2832 let checks_tokens = checks.iter().map(|c| {
2833 let name = c.name.as_str();
2834 let expr = c.expr.as_str();
2835 quote!(#root::core::CheckConstraint::new(#name, #expr))
2836 });
2837 let excludes_tokens = excludes.iter().map(|e| {
2838 let name = e.name.as_str();
2839 let using = e.using.as_str();
2840 let element_tokens = e.elements.iter().map(|(col, op)| {
2841 let col_s = col.as_str();
2842 let op_s = op.as_str();
2843 quote!((#col_s, #op_s))
2844 });
2845 let where_tokens = match e.where_clause.as_deref() {
2846 Some(w) => quote!(::core::option::Option::Some(#w)),
2847 None => quote!(::core::option::Option::None),
2848 };
2849 quote! {{
2850 let mut e = #root::core::ExclusionConstraint::new(
2851 #name,
2852 #using,
2853 &[ #(#element_tokens),* ],
2854 );
2855 e.where_clause = #where_tokens;
2856 e
2857 }}
2858 });
2859 let composite_fk_tokens = composite_fks.iter().map(|rel| {
2860 let name = rel.name.as_str();
2861 let to = rel.to.as_str();
2862 let from_cols: Vec<&str> = rel.from.iter().map(String::as_str).collect();
2863 let on_cols: Vec<&str> = rel.on.iter().map(String::as_str).collect();
2864 quote! {
2865 #root::core::CompositeFkRelation::new(
2866 #name,
2867 #to,
2868 &[ #(#from_cols),* ],
2869 &[ #(#on_cols),* ],
2870 )
2871 }
2872 });
2873 let generic_fk_tokens = generic_fks.iter().map(|rel| {
2874 let name = rel.name.as_str();
2875 let ct_col = rel.ct_column.as_str();
2876 let pk_col = rel.pk_column.as_str();
2877 quote!(#root::core::GenericRelation::new(#name, #ct_col, #pk_col))
2878 });
2879 let default_order_tokens = default_order.iter().map(|(col, desc, _)| {
2882 let col_lit = col.as_str();
2883 quote! { (#col_lit, #desc) }
2884 });
2885
2886 let global_scope_tokens = global_scopes.iter().map(|s| {
2891 let name = s.name.as_str();
2892 let mut apply = s.apply.clone();
2894 if let Some(first) = apply.segments.first_mut() {
2895 if first.ident == "Self" {
2896 first.ident = struct_name.clone();
2897 }
2898 }
2899 quote!(#root::core::GlobalScope::new(#name, #apply))
2900 });
2901
2902 let m2m_tokens = m2m_relations.iter().map(|rel| {
2903 let name = rel.name.as_str();
2904 let to = rel.to.as_str();
2905 let through = rel.through.as_str();
2906 let src = rel.src.as_str();
2907 let dst = rel.dst.as_str();
2908 let auto_create = rel.auto_create;
2909 quote! {{
2910 let mut m = #root::core::M2MRelation::new(#name, #to, #through, #src, #dst);
2911 m.auto_create = #auto_create;
2912 m
2913 }}
2914 });
2915 let reverse_relations_override = if reverse_has_relations.is_empty() {
2921 quote!()
2922 } else {
2923 let entries = reverse_has_relations.iter().map(|rel| {
2924 let name = rel.name.as_str();
2925 let child = &rel.child;
2926 let child_fk_column = rel.child_fk_column.as_str();
2927 let self_pk_column = rel.self_pk_column();
2928 quote! {
2929 #root::core::ReverseRelation::new(
2930 #name,
2931 <#child as #root::core::Model>::SCHEMA,
2932 #child_fk_column,
2933 #self_pk_column,
2934 )
2935 }
2936 });
2937 quote! {
2938 fn reverse_relations() -> &'static [#root::core::ReverseRelation] {
2939 const RELS: &[#root::core::ReverseRelation] = &[ #(#entries),* ];
2940 RELS
2941 }
2942 }
2943 };
2944 let generic_reverse_relations_override = if generic_has_relations.is_empty() {
2947 quote!()
2948 } else {
2949 let entries = generic_has_relations.iter().map(|rel| {
2950 let name = rel.name.as_str();
2951 let child = &rel.child;
2952 let ct_column = rel.ct_column.as_str();
2953 let pk_column = rel.pk_column.as_str();
2954 let self_pk_column = rel.self_pk_column();
2955 quote! {
2956 #root::core::GenericReverseRelation::new(
2957 #name,
2958 <#child as #root::core::Model>::SCHEMA,
2959 #ct_column,
2960 #pk_column,
2961 #self_pk_column,
2962 )
2963 }
2964 });
2965 quote! {
2966 fn generic_reverse_relations() -> &'static [#root::core::GenericReverseRelation] {
2967 const RELS: &[#root::core::GenericReverseRelation] = &[ #(#entries),* ];
2968 RELS
2969 }
2970 }
2971 };
2972 let fields_slice = const_slice(
2973 "e!(#root::core::FieldSchema),
2974 field_schemas.iter().cloned(),
2975 );
2976 let m2m_slice = const_slice("e!(#root::core::M2MRelation), m2m_tokens);
2977 let indexes_slice = const_slice("e!(#root::core::IndexSchema), indexes_tokens);
2978 let checks_slice = const_slice("e!(#root::core::CheckConstraint), checks_tokens);
2979 let excludes_slice = const_slice("e!(#root::core::ExclusionConstraint), excludes_tokens);
2980 let composite_fk_slice = const_slice(
2981 "e!(#root::core::CompositeFkRelation),
2982 composite_fk_tokens,
2983 );
2984 let generic_fk_slice = const_slice("e!(#root::core::GenericRelation), generic_fk_tokens);
2985 let audited_update_override = if audit_track.is_some() {
2986 quote! {
2987 fn __rustango_audited_update() -> ::core::option::Option<#root::audit::AuditedUpdate> {
2988 ::core::option::Option::Some(Self::__rustango_update_audited)
2989 }
2990 fn __rustango_audited_delete() -> ::core::option::Option<#root::audit::AuditedDelete> {
2991 ::core::option::Option::Some(Self::__rustango_delete_audited)
2992 }
2993 fn __rustango_audited_create() -> ::core::option::Option<#root::audit::AuditedCreate> {
2994 ::core::option::Option::Some(Self::__rustango_create_audited)
2995 }
2996 fn __rustango_audited_update_record(
2997 ) -> ::core::option::Option<#root::audit::AuditedUpdateRecord> {
2998 ::core::option::Option::Some(Self::__rustango_update_record_audited)
2999 }
3000 fn __rustango_audited_cascade() -> ::core::option::Option<#root::audit::AuditedCascade> {
3001 ::core::option::Option::Some(Self::__rustango_cascade_audited)
3002 }
3003 }
3004 } else {
3005 quote!()
3006 };
3007 let global_scope_slice = const_slice("e!(#root::core::GlobalScope), global_scope_tokens);
3008 quote! {
3009 impl #root::core::Model for #struct_name {
3010 const SCHEMA: &'static #root::core::ModelSchema = &{
3011 #root::core::ModelSchema::from_parts(#root::core::ModelSchemaParts {
3012 name: #model_name,
3013 table: #table,
3014 fields: #fields_slice,
3015 display: #display_tokens,
3016 app_label: #app_label_tokens,
3017 admin: #admin_tokens,
3018 soft_delete_column: #soft_delete_tokens,
3019 permissions: #permissions,
3020 audit_track: #audit_track_tokens,
3021 m2m: #m2m_slice,
3022 indexes: #indexes_slice,
3023 check_constraints: #checks_slice,
3024 exclusion_constraints: #excludes_slice,
3025 composite_relations: #composite_fk_slice,
3026 generic_relations: #generic_fk_slice,
3027 scope: #scope_tokens,
3028 default_order: &[ #(#default_order_tokens),* ],
3029 is_view: #is_view,
3030 verbose_name: #verbose_name_tokens,
3031 verbose_name_plural: #verbose_name_plural_tokens,
3032 managed: #managed,
3033 base_manager_name: #base_manager_name_tokens,
3034 order_with_respect_to: #order_with_respect_to_tokens,
3035 proxy: #proxy,
3036 required_db_features: &[ #(#required_db_features_lits),* ],
3037 required_db_vendor: #required_db_vendor_tokens,
3038 default_related_name: #default_related_name_tokens,
3039 db_table_comment: #db_table_comment_tokens,
3040 get_latest_by: #get_latest_by_tokens,
3041 extra_permissions: &[ #(#extra_permission_tokens),* ],
3042 default_permissions: &[ #(#default_permission_tokens),* ],
3043 global_scopes: #global_scope_slice,
3044 })
3045 };
3046
3047 #reverse_relations_override
3048 #generic_reverse_relations_override
3049 #audited_update_override
3050 }
3051 }
3052}
3053
3054fn admin_config_tokens(admin: Option<&AdminAttrs>) -> TokenStream2 {
3058 let root = rustango_root();
3059 let Some(admin) = admin else {
3060 return quote!(::core::option::Option::None);
3061 };
3062
3063 let list_display = admin
3064 .list_display
3065 .as_ref()
3066 .map(|(v, _)| v.as_slice())
3067 .unwrap_or(&[]);
3068 let list_display_lits = list_display.iter().map(|s| s.as_str());
3069
3070 let search_fields = admin
3071 .search_fields
3072 .as_ref()
3073 .map(|(v, _)| v.as_slice())
3074 .unwrap_or(&[]);
3075 let search_fields_lits = search_fields.iter().map(|s| s.as_str());
3076
3077 let readonly_fields = admin
3078 .readonly_fields
3079 .as_ref()
3080 .map(|(v, _)| v.as_slice())
3081 .unwrap_or(&[]);
3082 let readonly_fields_lits = readonly_fields.iter().map(|s| s.as_str());
3083
3084 let list_filter = admin
3085 .list_filter
3086 .as_ref()
3087 .map(|(v, _)| v.as_slice())
3088 .unwrap_or(&[]);
3089 let list_filter_lits = list_filter.iter().map(|s| s.as_str());
3090
3091 let actions = admin
3092 .actions
3093 .as_ref()
3094 .map(|(v, _)| v.as_slice())
3095 .unwrap_or(&[]);
3096 let actions_lits = actions.iter().map(|s| s.as_str());
3097
3098 let fieldsets = admin
3099 .fieldsets
3100 .as_ref()
3101 .map(|(v, _)| v.as_slice())
3102 .unwrap_or(&[]);
3103 let fieldset_tokens = const_slice(
3104 "e!(#root::core::Fieldset),
3105 fieldsets.iter().map(|(title, fields)| {
3106 let title = title.as_str();
3107 let field_lits = fields.iter().map(String::as_str);
3108 quote!(#root::core::Fieldset::new(#title, &[ #( #field_lits ),* ]))
3109 }),
3110 );
3111
3112 let list_display_links = admin
3113 .list_display_links
3114 .as_ref()
3115 .map(|(v, _)| v.as_slice())
3116 .unwrap_or(&[]);
3117 let list_display_links_lits = list_display_links.iter().map(|s| s.as_str());
3118
3119 let search_help_text = admin.search_help_text.as_deref().unwrap_or("");
3120 let actions_on_top = admin.actions_on_top.unwrap_or(true);
3121 let actions_on_bottom = admin.actions_on_bottom.unwrap_or(false);
3122 let date_hierarchy = admin.date_hierarchy.as_deref().unwrap_or("");
3123
3124 let prepopulated = admin
3125 .prepopulated_fields
3126 .as_ref()
3127 .map(|(v, _)| v.as_slice())
3128 .unwrap_or(&[]);
3129 let prepopulated_tokens = const_slice(
3130 "e!(#root::core::PrepopulatedField),
3131 prepopulated.iter().map(|(target, sources)| {
3132 let target = target.as_str();
3133 let source_lits = sources.iter().map(String::as_str);
3134 quote!(#root::core::PrepopulatedField::new(#target, &[ #( #source_lits ),* ]))
3135 }),
3136 );
3137
3138 let raw_id_fields = admin
3139 .raw_id_fields
3140 .as_ref()
3141 .map(|(v, _)| v.as_slice())
3142 .unwrap_or(&[]);
3143 let raw_id_fields_lits = raw_id_fields.iter().map(|s| s.as_str());
3144
3145 let autocomplete_fields = admin
3146 .autocomplete_fields
3147 .as_ref()
3148 .map(|(v, _)| v.as_slice())
3149 .unwrap_or(&[]);
3150 let autocomplete_fields_lits = autocomplete_fields.iter().map(|s| s.as_str());
3151
3152 let list_select_related_tokens = match admin.list_select_related.as_deref() {
3154 None | Some("all") => quote!(#root::core::ListSelectRelated::All),
3155 Some("none") => quote!(#root::core::ListSelectRelated::None),
3156 Some(raw) => {
3157 let names: Vec<&str> = raw
3158 .split(',')
3159 .map(str::trim)
3160 .filter(|s| !s.is_empty())
3161 .collect();
3162 quote!(#root::core::ListSelectRelated::Only(&[ #( #names ),* ]))
3163 }
3164 };
3165
3166 let formfield_pairs: Vec<(&str, &str)> = admin
3169 .formfield_overrides
3170 .as_ref()
3171 .map(|(v, _)| v.iter().map(|(f, w)| (f.as_str(), w.as_str())).collect())
3172 .unwrap_or_default();
3173 let formfield_tokens = formfield_pairs.iter().map(|(field, widget)| {
3174 let field = *field;
3175 let widget = *widget;
3176 quote!((#field, #widget))
3177 });
3178
3179 let list_per_page = admin.list_per_page.unwrap_or(0);
3180
3181 let ordering_pairs = admin
3182 .ordering
3183 .as_ref()
3184 .map(|(v, _)| v.as_slice())
3185 .unwrap_or(&[]);
3186 let ordering_tokens = ordering_pairs.iter().map(|(name, desc)| {
3187 let name = name.as_str();
3188 let desc = *desc;
3189 quote!((#name, #desc))
3190 });
3191
3192 quote! {
3193 ::core::option::Option::Some({
3194 const ADMIN: &#root::core::AdminConfig = &{
3195 #root::core::AdminConfig::from_parts(#root::core::AdminConfigParts {
3196 list_display: &[ #( #list_display_lits ),* ],
3197 search_fields: &[ #( #search_fields_lits ),* ],
3198 list_per_page: #list_per_page,
3199 ordering: &[ #( #ordering_tokens ),* ],
3200 readonly_fields: &[ #( #readonly_fields_lits ),* ],
3201 list_filter: &[ #( #list_filter_lits ),* ],
3202 actions: &[ #( #actions_lits ),* ],
3203 fieldsets: #fieldset_tokens,
3204 list_display_links: &[ #( #list_display_links_lits ),* ],
3205 search_help_text: #search_help_text,
3206 actions_on_top: #actions_on_top,
3207 actions_on_bottom: #actions_on_bottom,
3208 date_hierarchy: #date_hierarchy,
3209 prepopulated_fields: #prepopulated_tokens,
3210 raw_id_fields: &[ #( #raw_id_fields_lits ),* ],
3211 autocomplete_fields: &[ #( #autocomplete_fields_lits ),* ],
3212 list_select_related: #list_select_related_tokens,
3213 formfield_overrides: &[ #( #formfield_tokens ),* ],
3214 })
3215 };
3216 ADMIN
3217 })
3218 }
3219}
3220
3221fn inherent_impl_tokens(
3222 struct_name: &syn::Ident,
3223 fields: &CollectedFields,
3224 primary_key: Option<&(syn::Ident, String)>,
3225 column_consts: &TokenStream2,
3226 audited_fields: Option<&[&ColumnEntry]>,
3227 upsert_unique: Option<&[String]>,
3228 manager_fns: &[syn::Ident],
3229) -> TokenStream2 {
3230 let root = rustango_root();
3231
3232 let pool_insert_method = if audited_fields.is_some() && !fields.has_auto {
3250 quote!()
3259 } else if audited_fields.is_some() && fields.has_auto {
3260 quote!()
3263 } else if fields.has_auto {
3264 let pushes = &fields.insert_pushes;
3265 let filled = &fields.rust_filled_idents;
3267 let was_unset: Vec<syn::Ident> = filled
3268 .iter()
3269 .map(|i| quote::format_ident!("__{}_was_unset", ident_name(i)))
3270 .collect();
3271 let insert_or_ignore_call = quote! {
3272 #( let #was_unset = matches!(&self.#filled, #root::sql::Auto::Unset); )*
3273 let mut _columns: ::std::vec::Vec<&'static str> =
3274 ::std::vec::Vec::new();
3275 let mut _values: ::std::vec::Vec<#root::core::SqlValue> =
3276 ::std::vec::Vec::new();
3277 #( #pushes )*
3278 let _query = #root::core::InsertQuery::new(
3279 <Self as #root::core::Model>::SCHEMA,
3280 _columns,
3281 _values,
3282 );
3283 let _result = #root::sql::insert_or_ignore(pool, &_query).await;
3284 if !matches!(_result, ::core::result::Result::Ok(true)) {
3285 #( if #was_unset { self.#filled = #root::sql::Auto::Unset; } )*
3286 }
3287 _result
3288 };
3289 if fields.returning_cols.is_empty() {
3296 quote! {
3297
3298
3299 pub async fn insert_or_ignore(
3307 &mut self,
3308 pool: &#root::sql::Pool,
3309 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
3310 #insert_or_ignore_call
3311 }
3312 }
3313 } else {
3314 quote! {
3315
3316
3317 pub async fn insert_or_ignore(
3334 &mut self,
3335 pool: &#root::sql::Pool,
3336 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
3337 #insert_or_ignore_call
3338 }
3339 }
3340 }
3341 } else {
3342 let insert_columns = &fields.insert_columns;
3343 let insert_values = &fields.insert_values;
3344 quote! {
3345
3346
3347 pub async fn insert_or_ignore(
3367 &self,
3368 pool: &#root::sql::Pool,
3369 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
3370 let _query = #root::core::InsertQuery::new(
3371 <Self as #root::core::Model>::SCHEMA,
3372 ::std::vec![ #( #insert_columns ),* ],
3373 ::std::vec![ #( #insert_values ),* ],
3374 );
3375 #root::sql::insert_or_ignore(pool, &_query).await
3376 }
3377 }
3378 };
3379
3380 let audit_pair_tokens: Vec<TokenStream2> = audited_fields
3383 .map(|tracked| {
3384 tracked
3385 .iter()
3386 .map(|c| {
3387 let column_lit = c.column.as_str();
3388 let ident = &c.ident;
3389 quote! {
3390 (
3391 #column_lit,
3392 #root::core::__json_value(&self.#ident),
3393 )
3394 }
3395 })
3396 .collect()
3397 })
3398 .unwrap_or_default();
3399 let audit_pk_to_string = if let Some((pk_ident, _)) = primary_key {
3400 if fields.pk_is_auto {
3401 quote!(self.#pk_ident.get().map(|v| ::std::format!("{}", v)).unwrap_or_default())
3402 } else {
3403 quote!(::std::format!("{}", &self.#pk_ident))
3404 }
3405 } else {
3406 quote!(::std::string::String::new())
3407 };
3408 let delete_audited_body = if primary_key.is_some() {
3411 quote! {
3412 ::std::boxed::Box::pin(#root::audit::delete_many_with_audit::<Self>(
3413 pool,
3414 query,
3415 |_r: &Self| _r.__rustango_audit_entry(#root::audit::AuditOp::Delete),
3416 ))
3417 }
3418 } else {
3419 quote! {
3420 let _ = (pool, query);
3421 ::std::boxed::Box::pin(async {
3422 ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
3423 table: <Self as #root::core::Model>::SCHEMA.table,
3424 })
3425 })
3426 }
3427 };
3428 let cascade_audited_body = if primary_key.is_some() {
3429 quote! {
3430 ::std::boxed::Box::pin(#root::audit::cascade_rows::<Self>(
3431 tx,
3432 <Self as #root::core::Model>::SCHEMA,
3433 rows,
3434 |_r: &Self| _r.__rustango_audit_entry(#root::audit::AuditOp::Delete),
3435 ))
3436 }
3437 } else {
3438 quote! {
3439 let _ = (tx, rows);
3440 ::std::boxed::Box::pin(async {
3441 ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
3442 table: <Self as #root::core::Model>::SCHEMA.table,
3443 })
3444 })
3445 }
3446 };
3447 let update_audited_body = if primary_key.is_some() {
3448 quote! {
3449 ::std::boxed::Box::pin(#root::audit::update_many_with_audit::<Self>(
3450 pool,
3451 query,
3452 move |_r: &Self| _r.__rustango_audit_entry(op),
3453 ))
3454 }
3455 } else {
3456 quote! {
3457 let _ = (pool, query, op);
3458 ::std::boxed::Box::pin(async {
3459 ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
3460 table: <Self as #root::core::Model>::SCHEMA.table,
3461 })
3462 })
3463 }
3464 };
3465 let audit_entry_method = if audited_fields.is_some() {
3466 let pairs = audit_pair_tokens.iter();
3467 let pk_str = audit_pk_to_string.clone();
3468 quote! {
3469 #[doc(hidden)]
3471 pub fn __rustango_audit_pairs(
3472 &self,
3473 ) -> ::std::vec::Vec<(&'static str, #root::__serde_json::Value)> {
3474 ::std::vec![ #( #pairs ),* ]
3475 }
3476
3477 #[doc(hidden)]
3479 pub fn __rustango_audit_pk(&self) -> ::std::string::String {
3480 #pk_str
3481 }
3482
3483 #[doc(hidden)]
3485 pub fn __rustango_audit_entry(
3486 &self,
3487 operation: #root::audit::AuditOp,
3488 ) -> #root::audit::PendingEntry {
3489 #root::audit::PendingEntry {
3490 entity_table: <Self as #root::core::Model>::SCHEMA.table,
3491 entity_pk: self.__rustango_audit_pk(),
3492 operation,
3493 source: #root::audit::current_source(),
3494 changes: #root::audit::snapshot_changes(&self.__rustango_audit_pairs()),
3495 }
3496 }
3497
3498 #[doc(hidden)]
3500 pub fn __rustango_update_audited<'a>(
3501 pool: &'a #root::sql::Pool,
3502 query: &'a #root::core::UpdateQuery,
3503 op: #root::audit::AuditOp,
3504 ) -> ::std::pin::Pin<::std::boxed::Box<
3505 dyn ::core::future::Future<
3506 Output = ::core::result::Result<u64, #root::sql::ExecError>,
3507 > + ::core::marker::Send + 'a,
3508 >> {
3509 #update_audited_body
3510 }
3511
3512 #[doc(hidden)]
3514 pub fn __rustango_create_audited<'a, 't>(
3515 tx: &'a mut #root::sql::PoolTx<'t>,
3516 pk: #root::core::SqlValue,
3517 ) -> ::std::pin::Pin<::std::boxed::Box<
3518 dyn ::core::future::Future<
3519 Output = ::core::result::Result<(), #root::sql::ExecError>,
3520 > + ::core::marker::Send + 'a,
3521 >> {
3522 ::std::boxed::Box::pin(#root::audit::record_create::<Self>(
3523 tx,
3524 <Self as #root::core::Model>::SCHEMA,
3525 pk,
3526 |_r: &Self| _r.__rustango_audit_entry(#root::audit::AuditOp::Create),
3527 ))
3528 }
3529
3530 #[doc(hidden)]
3532 pub fn __rustango_update_record_audited<'a, 't>(
3533 tx: &'a mut #root::sql::PoolTx<'t>,
3534 pk: #root::core::SqlValue,
3535 ) -> ::std::pin::Pin<::std::boxed::Box<
3536 dyn ::core::future::Future<
3537 Output = ::core::result::Result<(), #root::sql::ExecError>,
3538 > + ::core::marker::Send + 'a,
3539 >> {
3540 ::std::boxed::Box::pin(#root::audit::record_update::<Self>(
3541 tx,
3542 <Self as #root::core::Model>::SCHEMA,
3543 pk,
3544 |_r: &Self| _r.__rustango_audit_entry(#root::audit::AuditOp::Update),
3545 ))
3546 }
3547
3548 #[doc(hidden)]
3550 pub fn __rustango_cascade_audited<'a, 't>(
3551 tx: &'a mut #root::sql::PoolTx<'t>,
3552 rows: #root::core::WhereExpr,
3553 ) -> ::std::pin::Pin<::std::boxed::Box<
3554 dyn ::core::future::Future<
3555 Output = ::core::result::Result<
3556 ::std::vec::Vec<(#root::core::SqlValue, #root::audit::PendingEntry)>,
3557 #root::sql::ExecError,
3558 >,
3559 > + ::core::marker::Send + 'a,
3560 >> {
3561 #cascade_audited_body
3562 }
3563
3564 #[doc(hidden)]
3566 pub fn __rustango_delete_audited<'a>(
3567 pool: &'a #root::sql::Pool,
3568 query: &'a #root::core::DeleteQuery,
3569 ) -> ::std::pin::Pin<::std::boxed::Box<
3570 dyn ::core::future::Future<
3571 Output = ::core::result::Result<u64, #root::sql::ExecError>,
3572 > + ::core::marker::Send + 'a,
3573 >> {
3574 #delete_audited_body
3575 }
3576 }
3577 } else {
3578 quote!()
3579 };
3580 let make_entry = |op_path: TokenStream2, written: Option<TokenStream2>| -> TokenStream2 {
3583 if let (Some(v), Some(col)) = (written, fields.soft_delete_column.as_deref()) {
3584 quote! {
3585 let mut _audit_entry = self.__rustango_audit_entry(#op_path);
3586 _audit_entry.set_tracked(#col, #v);
3587 }
3588 } else {
3589 quote!(let _audit_entry = self.__rustango_audit_entry(#op_path);)
3590 }
3591 };
3592 let soft_deleted_json = quote!(#root::__serde_json::to_value(&_now)
3593 .unwrap_or(#root::__serde_json::Value::Null));
3594 let restored_json = quote!(#root::__serde_json::Value::Null);
3595 let make_pool_update = |op_path: TokenStream2, written: TokenStream2| -> TokenStream2 {
3598 if audited_fields.is_some() {
3599 let entry = make_entry(op_path, Some(written));
3600 quote! {
3601 #entry
3602 #root::audit::save_one_with_audit(pool, &_query, &_audit_entry).await
3603 }
3604 } else {
3605 quote!(#root::sql::update_pool(pool, &_query).await)
3606 }
3607 };
3608 let pool_softdelete_run =
3609 make_pool_update(quote!(#root::audit::AuditOp::SoftDelete), soft_deleted_json);
3610 let pool_restore_run = make_pool_update(quote!(#root::audit::AuditOp::Restore), restored_json);
3611
3612 let save_ret = quote!(::core::result::Result<u64, #root::sql::ExecError>);
3616 let save_keep = quote!(matches!(_saved, ::core::result::Result::Ok(n) if n > 0));
3617 let save_partial_methods = |pk_ident: &syn::Ident,
3618 pk_column_lit: &str,
3619 run: TokenStream2|
3620 -> TokenStream2 {
3621 let assignments = &fields.update_assignments;
3622 let assignment_cols = &fields.upsert_update_columns;
3624 let write = restore_auto_now_unless(
3625 &fields.auto_now_idents,
3626 &save_ret,
3627 &save_keep,
3628 "e! {
3629 let mut _filtered: ::std::vec::Vec<#root::core::Assignment> =
3632 ::std::vec::Vec::with_capacity(_wanted_cols.len());
3633 #(
3634 if _wanted_cols.contains(#assignment_cols) {
3635 _filtered.push(#assignments);
3636 }
3637 )*
3638 if _filtered.is_empty() {
3639 #root::__tracing::warn!(
3644 target: "rustango::save_partial",
3645 model = _schema.name,
3646 "save_partial: every named field maps to a non-assignable column — no-op"
3647 );
3648 return ::core::result::Result::Ok(0);
3649 }
3650 let _query = #root::core::UpdateQuery::new(
3651 _schema,
3652 _filtered,
3653 #root::core::WhereExpr::Predicate(#root::core::Filter::new(
3654 #pk_column_lit,
3655 #root::core::Op::Eq,
3656 ::core::convert::Into::<#root::core::SqlValue>::into(
3657 ::core::clone::Clone::clone(&self.#pk_ident)
3658 ),
3659 )),
3660 );
3661 #run
3662 },
3663 );
3664 quote! {
3665 pub async fn save_partial(
3693 &mut self,
3694 fields: &[&str],
3695 pool: &#root::sql::Pool,
3696 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
3697 if fields.is_empty() {
3698 #root::__tracing::warn!(
3699 target: "rustango::save_partial",
3700 model = <Self as #root::core::Model>::SCHEMA.name,
3701 "save_partial called with empty field list — no-op"
3702 );
3703 return ::core::result::Result::Ok(0);
3704 }
3705 let _schema = <Self as #root::core::Model>::SCHEMA;
3706 let mut _wanted_cols: ::std::collections::HashSet<&'static str> =
3708 ::std::collections::HashSet::with_capacity(fields.len());
3709 for f in fields {
3710 match _schema.field(f) {
3711 ::core::option::Option::Some(fs) => {
3712 _wanted_cols.insert(fs.column);
3713 }
3714 ::core::option::Option::None => {
3715 return ::core::result::Result::Err(
3716 #root::sql::ExecError::Query(
3717 #root::core::QueryError::UnknownField {
3718 model: _schema.name,
3719 field: (*f).to_owned(),
3720 }
3721 )
3722 );
3723 }
3724 }
3725 }
3726 #write
3727 }
3728
3729 pub async fn save_partial_typed<
3749 L: #root::core::TypedFieldList<Self>,
3750 >(
3751 &mut self,
3752 fields: L,
3753 pool: &#root::sql::Pool,
3754 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
3755 let _names = fields.rust_field_names();
3756 let _refs: ::std::vec::Vec<&str> =
3757 _names.iter().copied().collect();
3758 self.save_partial(&_refs, pool).await
3759 }
3760 }
3761 };
3762 let pool_save_method = if let Some((pk_ident, pk_col)) = primary_key {
3763 let pk_column_lit = pk_col.as_str();
3764 if audited_fields.is_some() {
3765 quote!()
3766 } else {
3767 let partial = save_partial_methods(
3768 pk_ident,
3769 pk_column_lit,
3770 quote!(#root::sql::update_pool(pool, &_query).await),
3771 );
3772 quote! {
3773 pub async fn save(
3780 &mut self,
3781 pool: &#root::sql::Pool,
3782 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
3783 self.__rustango_save_exec(pool).await
3784 }
3785
3786 #partial
3787 }
3788 }
3789 } else {
3790 quote!()
3791 };
3792
3793 let pool_insert_method = if audited_fields.is_some() {
3800 if let Some(_) = primary_key {
3801 let returning_cols: Vec<proc_macro2::TokenStream> = if !fields.returning_cols.is_empty()
3803 {
3804 fields.returning_cols.clone()
3805 } else {
3806 primary_key
3813 .map(|(_, col)| {
3814 let lit = col.as_str();
3815 vec![quote!(#lit)]
3816 })
3817 .unwrap_or_default()
3818 };
3819 let assigned: Vec<syn::Ident> = fields
3821 .auto_field_idents
3822 .iter()
3823 .map(|(ident, _, _)| ident.clone())
3824 .collect();
3825 let body = restore_auto_now_unless(
3826 &assigned,
3827 "e!(::core::result::Result<(), #root::sql::ExecError>),
3828 "e!(_saved.is_ok()),
3829 "e! {
3830 let mut _query = self.__rustango_insert_query();
3831 if _query.returning.is_empty() {
3833 _query.returning = ::std::vec![ #( #returning_cols ),* ];
3834 }
3835 #root::audit::insert_one_with_audit(pool, &_query, self, |_m: &Self, _op| {
3836 _m.__rustango_audit_entry(_op)
3837 })
3838 .await
3839 },
3840 );
3841 quote! {
3842 pub async fn insert(
3850 &mut self,
3851 pool: &#root::sql::Pool,
3852 ) -> ::core::result::Result<(), #root::sql::ExecError> {
3853 #body
3854 }
3855 }
3856 } else {
3857 quote!()
3858 }
3859 } else {
3860 pool_insert_method
3862 };
3863
3864 let pool_save_method = if let Some(tracked) = audited_fields {
3885 if let Some((pk_ident, pk_col)) = primary_key {
3886 let pk_column_lit = pk_col.as_str();
3887 let partial = save_partial_methods(
3888 pk_ident,
3889 pk_column_lit,
3890 quote! {
3891 self.__rustango_save_with_diff(
3892 pool,
3893 &_query,
3894 ::core::option::Option::Some(&_wanted_cols),
3895 )
3896 .await
3897 },
3898 );
3899 let mk_before_pairs =
3904 |getter: &dyn Fn(&ColumnEntry) -> TokenStream2| -> Vec<TokenStream2> {
3905 tracked
3906 .iter()
3907 .map(|c| {
3908 let getter = getter(c);
3909 let column_lit = c.column.as_str();
3910 let value_ty = &c.value_ty;
3911 quote! {
3912 (
3913 #column_lit,
3914 match #getter::<#value_ty>(
3915 _audit_before_row, #column_lit,
3916 ) {
3917 ::core::result::Result::Ok(v) => {
3918 #root::core::__json_value(&v)
3919 }
3920 ::core::result::Result::Err(_) => #root::__serde_json::Value::Null,
3921 },
3922 )
3923 }
3924 })
3925 .collect()
3926 };
3927 let before_pairs_pg = mk_before_pairs(&|_| quote!(#root::sql::try_get_returning));
3928 let before_pairs_my = mk_before_pairs(&|c| {
3929 if c.uuid_column {
3930 quote!(#root::sql::try_get_flat_my)
3931 } else {
3932 quote!(#root::sql::try_get_returning_my)
3933 }
3934 });
3935 let before_pairs_sqlite =
3936 mk_before_pairs(&|_| quote!(#root::sql::try_get_returning_sqlite));
3937 let tracked_cols: Vec<&str> = tracked.iter().map(|c| c.column.as_str()).collect();
3938 let pk_value_for_bind = if fields.pk_is_auto {
3939 quote!(self.#pk_ident.get().copied().unwrap_or_default())
3940 } else {
3941 quote!(::core::clone::Clone::clone(&self.#pk_ident))
3942 };
3943 let assignments = &fields.update_assignments;
3944 let unset_dispatch = if fields.has_auto {
3945 quote! {
3946 if matches!(self.#pk_ident, #root::sql::Auto::Unset) {
3947 return self.insert(pool).await.map(|()| 1u64);
3948 }
3949 }
3950 } else {
3951 quote!()
3952 };
3953 let write = restore_auto_now_unless(
3954 &fields.auto_now_idents,
3955 &save_ret,
3956 &save_keep,
3957 "e! {
3958 let _query = #root::core::UpdateQuery::new(
3959 <Self as #root::core::Model>::SCHEMA,
3960 ::std::vec![ #( #assignments ),* ],
3961 #root::core::WhereExpr::Predicate(#root::core::Filter::new(
3962 #pk_column_lit,
3963 #root::core::Op::Eq,
3964 ::core::convert::Into::<#root::core::SqlValue>::into(
3965 ::core::clone::Clone::clone(&self.#pk_ident)
3966 ),
3967 )),
3968 );
3969 self.__rustango_save_with_diff(pool, &_query, ::core::option::Option::None)
3970 .await
3971 },
3972 );
3973 quote! {
3974 pub async fn save(
3986 &mut self,
3987 pool: &#root::sql::Pool,
3988 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
3989 #unset_dispatch
3990 #write
3991 }
3992
3993 #[doc(hidden)]
3995 async fn __rustango_save_with_diff(
3996 &self,
3997 pool: &#root::sql::Pool,
3998 query: &#root::core::UpdateQuery,
3999 only: ::core::option::Option<&::std::collections::HashSet<&'static str>>,
4000 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4001 let mut _after_pairs = self.__rustango_audit_pairs();
4002 if let ::core::option::Option::Some(only) = only {
4003 _after_pairs.retain(|(col, _)| only.contains(col));
4004 }
4005 let _before_query = #root::audit::before_image_query(
4006 <Self as #root::core::Model>::SCHEMA,
4007 #pk_column_lit,
4008 ::core::convert::Into::<#root::core::SqlValue>::into(
4009 #pk_value_for_bind,
4010 ),
4011 &[ #( #tracked_cols ),* ],
4012 );
4013 #root::audit::save_one_with_diff(
4014 pool,
4015 query,
4016 &_before_query,
4017 <Self as #root::core::Model>::SCHEMA.table,
4018 self.__rustango_audit_pk(),
4019 _after_pairs,
4020 |_audit_before_row| ::std::vec![ #( #before_pairs_pg ),* ],
4021 |_audit_before_row| ::std::vec![ #( #before_pairs_my ),* ],
4022 |_audit_before_row| ::std::vec![ #( #before_pairs_sqlite ),* ],
4023 ).await
4024 }
4025
4026 #partial
4027 }
4028 } else {
4029 quote!()
4030 }
4031 } else {
4032 pool_save_method
4033 };
4034
4035 let pool_delete_method = {
4042 let pk_column_lit = primary_key.map(|(_, col)| col.as_str()).unwrap_or("id");
4043 let pk_ident_for_pool = primary_key.map(|(ident, _)| ident);
4044 if let Some(pk_ident) = pk_ident_for_pool {
4045 if audited_fields.is_some() {
4046 quote! {
4047 pub async fn delete(
4054 &self,
4055 pool: &#root::sql::Pool,
4056 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4057 let _query = #root::core::DeleteQuery::by_pk(
4058 <Self as #root::core::Model>::SCHEMA,
4059 #pk_column_lit,
4060 ::core::convert::Into::<#root::core::SqlValue>::into(
4061 ::core::clone::Clone::clone(&self.#pk_ident)
4062 ),
4063 );
4064 let _audit_entry =
4065 self.__rustango_audit_entry(#root::audit::AuditOp::Delete);
4066 #root::audit::delete_one_with_audit(
4067 pool, &_query, &_audit_entry,
4068 ).await
4069 }
4070 }
4071 } else {
4072 quote! {
4073 pub async fn delete(
4081 &self,
4082 pool: &#root::sql::Pool,
4083 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4084 self.__rustango_delete_exec(pool).await
4085 }
4086 }
4087 }
4088 } else {
4089 quote!()
4090 }
4091 };
4092
4093 let (bulk_delete_run, destroy_run, bulk_update_run, truncate_run, truncate_cascade_run) =
4096 if audited_fields.is_some() {
4097 (
4098 quote!(#root::audit::delete_many_with_audit::<Self>(pool, &_query, |_r: &Self| {
4099 _r.__rustango_audit_entry(#root::audit::AuditOp::Delete)
4100 })
4101 .await),
4102 quote!(#root::audit::delete_each_with_audit::<Self>(pool, &_queries, |_r: &Self| {
4103 _r.__rustango_audit_entry(#root::audit::AuditOp::Delete)
4104 })
4105 .await),
4106 quote!(#root::audit::update_many_with_audit::<Self>(pool, &_query, |_r: &Self| {
4107 _r.__rustango_audit_entry(#root::audit::AuditOp::Update)
4108 })
4109 .await),
4110 quote!(#root::audit::truncate_with_audit(
4111 pool,
4112 <Self as #root::core::Model>::SCHEMA.table,
4113 #root::sql::TruncateMode::Plain,
4114 )
4115 .await),
4116 quote!(#root::audit::truncate_with_audit(
4117 pool,
4118 <Self as #root::core::Model>::SCHEMA.table,
4119 #root::sql::TruncateMode::Cascade,
4120 )
4121 .await
4122 .map(drop)),
4123 )
4124 } else {
4125 (
4126 quote!(#root::sql::delete_pool(pool, &_query).await),
4127 quote!(#root::sql::__rustango_delete_all_or_none(pool, _queries).await),
4128 quote!(#root::sql::update_pool(pool, &_query).await),
4129 quote!(#root::sql::truncate(pool, <Self as #root::core::Model>::SCHEMA).await),
4130 quote!(#root::sql::truncate_cascade(pool, <Self as #root::core::Model>::SCHEMA).await),
4131 )
4132 };
4133
4134 let refresh_replicate_methods = if let Some((pk_ident, _)) = primary_key {
4147 let other_field_clones: Vec<TokenStream2> = fields
4148 .column_entries
4149 .iter()
4150 .filter(|c| &c.ident != pk_ident)
4151 .map(|c| {
4152 let ident = &c.ident;
4153 quote! {
4154 #ident: ::core::clone::Clone::clone(&self.#ident)
4155 }
4156 })
4157 .collect();
4158 let pk_clone_token = if fields.pk_is_auto {
4159 quote! { #pk_ident: #root::sql::Auto::Unset }
4160 } else {
4161 quote! { #pk_ident: ::core::clone::Clone::clone(&self.#pk_ident) }
4162 };
4163 let replicate_doc = if fields.pk_is_auto {
4164 quote! {
4165 }
4177 } else {
4178 quote! {
4179 }
4185 };
4186 let column_names: ::std::collections::HashSet<String> = fields
4200 .column_entries
4201 .iter()
4202 .map(|c| ident_name(&c.ident))
4203 .collect();
4204 let emit_if_no_field_collision = |name: &str, tokens: TokenStream2| -> TokenStream2 {
4205 if column_names.contains(name) {
4206 quote! {}
4207 } else {
4208 tokens
4209 }
4210 };
4211 let count_method = emit_if_no_field_collision(
4212 "count",
4213 quote! {
4214 pub async fn count(
4226 pool: &#root::sql::Pool,
4227 ) -> ::core::result::Result<i64, #root::sql::ExecError> {
4228 use #root::sql::CounterPool as _;
4229 #root::query::QuerySet::<Self>::default()
4230 .count(pool)
4231 .await
4232 }
4233 },
4234 );
4235 let value_method = emit_if_no_field_collision(
4236 "value",
4237 quote! {
4238 pub async fn value<U>(
4248 col: &str,
4249 pool: &#root::sql::Pool,
4250 ) -> ::core::result::Result<
4251 ::core::option::Option<U>,
4252 #root::sql::ExecError,
4253 >
4254 where
4255 U: #root::sql::FlatScalar,
4256 {
4257 let _col_static: &'static str = Self::__resolve_col(col)?;
4258 #root::query::QuerySet::<Self>::default()
4259 .values_list_flat(_col_static)
4260 .first::<U>(pool)
4261 .await
4262 }
4263 },
4264 );
4265 let sum_method = emit_if_no_field_collision(
4266 "sum",
4267 quote! {
4268 pub async fn sum<U>(
4275 col: &str,
4276 pool: &#root::sql::Pool,
4277 ) -> ::core::result::Result<
4278 ::core::option::Option<U>,
4279 #root::sql::ExecError,
4280 >
4281 where
4282 (::core::option::Option<U>,): #root::sql::MaybePgFromRow
4283 + #root::sql::MaybeMyFromRow
4284 + #root::sql::MaybeSqliteFromRow
4285 + ::core::marker::Send
4286 + ::core::marker::Unpin,
4287 {
4288 #root::query::QuerySet::<Self>::default()
4289 .sum::<U>(col, pool)
4290 .await
4291 }
4292 },
4293 );
4294 let avg_method = emit_if_no_field_collision(
4295 "avg",
4296 quote! {
4297 pub async fn avg<U>(
4303 col: &str,
4304 pool: &#root::sql::Pool,
4305 ) -> ::core::result::Result<
4306 ::core::option::Option<U>,
4307 #root::sql::ExecError,
4308 >
4309 where
4310 (::core::option::Option<U>,): #root::sql::MaybePgFromRow
4311 + #root::sql::MaybeMyFromRow
4312 + #root::sql::MaybeSqliteFromRow
4313 + ::core::marker::Send
4314 + ::core::marker::Unpin,
4315 {
4316 #root::query::QuerySet::<Self>::default()
4317 .avg::<U>(col, pool)
4318 .await
4319 }
4320 },
4321 );
4322 let min_method = emit_if_no_field_collision(
4323 "min",
4324 quote! {
4325 pub async fn min<U>(
4331 col: &str,
4332 pool: &#root::sql::Pool,
4333 ) -> ::core::result::Result<
4334 ::core::option::Option<U>,
4335 #root::sql::ExecError,
4336 >
4337 where
4338 (::core::option::Option<U>,): #root::sql::MaybePgFromRow
4339 + #root::sql::MaybeMyFromRow
4340 + #root::sql::MaybeSqliteFromRow
4341 + ::core::marker::Send
4342 + ::core::marker::Unpin,
4343 {
4344 #root::query::QuerySet::<Self>::default()
4345 .min::<U>(col, pool)
4346 .await
4347 }
4348 },
4349 );
4350 let max_method = emit_if_no_field_collision(
4351 "max",
4352 quote! {
4353 pub async fn max<U>(
4359 col: &str,
4360 pool: &#root::sql::Pool,
4361 ) -> ::core::result::Result<
4362 ::core::option::Option<U>,
4363 #root::sql::ExecError,
4364 >
4365 where
4366 (::core::option::Option<U>,): #root::sql::MaybePgFromRow
4367 + #root::sql::MaybeMyFromRow
4368 + #root::sql::MaybeSqliteFromRow
4369 + ::core::marker::Send
4370 + ::core::marker::Unpin,
4371 {
4372 #root::query::QuerySet::<Self>::default()
4373 .max::<U>(col, pool)
4374 .await
4375 }
4376 },
4377 );
4378 let first_method = emit_if_no_field_collision(
4379 "first",
4380 quote! {
4381 pub async fn first(
4388 pool: &#root::sql::Pool,
4389 ) -> ::core::result::Result<
4390 ::core::option::Option<Self>,
4391 #root::sql::ExecError,
4392 > {
4393 #root::query::QuerySet::<Self>::default()
4394 .first(pool)
4395 .await
4396 }
4397 },
4398 );
4399 let last_method = emit_if_no_field_collision(
4400 "last",
4401 quote! {
4402 pub async fn last(
4415 pool: &#root::sql::Pool,
4416 ) -> ::core::result::Result<
4417 ::core::option::Option<Self>,
4418 #root::sql::ExecError,
4419 > {
4420 #root::query::QuerySet::<Self>::default()
4421 .last(pool)
4422 .await
4423 }
4424 },
4425 );
4426 quote! {
4427 pub async fn refresh_from_db(
4446 &mut self,
4447 pool: &#root::sql::Pool,
4448 ) -> ::core::result::Result<(), #root::sql::ExecError> {
4449 use #root::sql::FetcherPool as _;
4450 let _pk_val: #root::core::SqlValue = ::core::convert::Into::into(
4451 ::core::clone::Clone::clone(&self.#pk_ident),
4452 );
4453 let mut _rows: ::std::vec::Vec<Self> =
4454 #root::query::QuerySet::<Self>::default()
4455 .filter(::core::stringify!(#pk_ident), _pk_val)
4456 .limit(1)
4457 .fetch(pool)
4458 .await?;
4459 match _rows.into_iter().next() {
4460 ::core::option::Option::Some(_fresh) => {
4461 *self = _fresh;
4462 ::core::result::Result::Ok(())
4463 }
4464 ::core::option::Option::None => ::core::result::Result::Err(
4465 #root::sql::ExecError::Driver(
4466 #root::sql::sqlx::Error::RowNotFound,
4467 ),
4468 ),
4469 }
4470 }
4471
4472 pub async fn increment(
4492 &self,
4493 col: &str,
4494 by: i64,
4495 pool: &#root::sql::Pool,
4496 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4497 Self::__increment_one(self, col, by, pool).await
4498 }
4499
4500 pub async fn decrement(
4509 &self,
4510 col: &str,
4511 by: i64,
4512 pool: &#root::sql::Pool,
4513 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4514 Self::__increment_one(self, col, -by, pool).await
4515 }
4516
4517 pub async fn increment_each(
4527 col: &str,
4528 by: i64,
4529 pool: &#root::sql::Pool,
4530 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4531 Self::__increment_all(col, by, pool).await
4532 }
4533
4534 pub async fn decrement_each(
4539 col: &str,
4540 by: i64,
4541 pool: &#root::sql::Pool,
4542 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4543 Self::__increment_all(col, -by, pool).await
4544 }
4545
4546 #[doc(hidden)]
4552 pub async fn __increment_one(
4553 this: &Self,
4554 col: &str,
4555 by: i64,
4556 pool: &#root::sql::Pool,
4557 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4558 let _pk_val: #root::core::SqlValue = ::core::convert::Into::into(
4559 ::core::clone::Clone::clone(&this.#pk_ident),
4560 );
4561 #root::sql::model_shortcuts::increment_one_pool::<Self>(
4562 ::core::stringify!(#pk_ident),
4563 _pk_val,
4564 col,
4565 by,
4566 pool,
4567 )
4568 .await
4569 }
4570
4571 #[doc(hidden)]
4574 pub async fn __increment_all(
4575 col: &str,
4576 by: i64,
4577 pool: &#root::sql::Pool,
4578 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4579 let _query =
4580 #root::sql::model_shortcuts::increment_all_query::<Self>(col, by)?;
4581 #bulk_update_run
4582 }
4583
4584 #[doc(hidden)]
4587 pub fn __resolve_col(
4588 col: &str,
4589 ) -> ::core::result::Result<&'static str, #root::sql::ExecError> {
4590 #root::sql::model_shortcuts::resolve_col::<Self>(col)
4591 }
4592
4593 #[doc(hidden)]
4596 #[must_use]
4597 pub fn __add_signed_expr(
4598 col_static: &'static str,
4599 signed_by: i64,
4600 ) -> #root::core::Expr {
4601 #root::sql::model_shortcuts::add_signed_expr(col_static, signed_by)
4602 }
4603
4604 pub async fn fresh(
4624 &self,
4625 pool: &#root::sql::Pool,
4626 ) -> ::core::result::Result<
4627 ::core::option::Option<Self>,
4628 #root::sql::ExecError,
4629 > {
4630 use #root::sql::FetcherPool as _;
4631 let _pk_val: #root::core::SqlValue = ::core::convert::Into::into(
4632 ::core::clone::Clone::clone(&self.#pk_ident),
4633 );
4634 let _rows: ::std::vec::Vec<Self> =
4635 #root::query::QuerySet::<Self>::default()
4636 .filter(::core::stringify!(#pk_ident), _pk_val)
4637 .limit(1)
4638 .fetch(pool)
4639 .await?;
4640 ::core::result::Result::Ok(_rows.into_iter().next())
4641 }
4642
4643 #replicate_doc
4644 #[must_use]
4645 pub fn replicate(&self) -> Self {
4646 Self {
4647 #pk_clone_token,
4648 #( #other_field_clones, )*
4649 }
4650 }
4651
4652 #first_method
4653
4654 #last_method
4655
4656 pub async fn first_or_fail(
4666 pool: &#root::sql::Pool,
4667 ) -> ::core::result::Result<Self, #root::sql::ExecError> {
4668 match #root::query::QuerySet::<Self>::default().first(pool).await? {
4672 ::core::option::Option::Some(_row) => ::core::result::Result::Ok(_row),
4673 ::core::option::Option::None => ::core::result::Result::Err(
4674 #root::sql::ExecError::Driver(
4675 #root::sql::sqlx::Error::RowNotFound,
4676 ),
4677 ),
4678 }
4679 }
4680
4681 pub async fn pluck<U>(
4693 col: &'static str,
4694 pool: &#root::sql::Pool,
4695 ) -> ::core::result::Result<::std::vec::Vec<U>, #root::sql::ExecError>
4696 where
4697 U: #root::sql::FlatScalar,
4698 {
4699 #root::query::QuerySet::<Self>::default()
4700 .values_list_flat(col)
4701 .fetch::<U>(pool)
4702 .await
4703 }
4704
4705 pub async fn chunk<F, Fut>(
4731 n: i64,
4732 pool: &#root::sql::Pool,
4733 mut cb: F,
4734 ) -> ::core::result::Result<(), #root::sql::ExecError>
4735 where
4736 F: ::core::ops::FnMut(::std::vec::Vec<Self>) -> Fut,
4737 Fut: ::core::future::Future<
4738 Output = ::core::result::Result<(), #root::sql::ExecError>,
4739 >,
4740 {
4741 use #root::sql::FetcherPool as _;
4742 let pk_col = match <Self as #root::core::Model>::SCHEMA.primary_key() {
4744 ::core::option::Option::Some(f) => f.name,
4745 ::core::option::Option::None => {
4746 return ::core::result::Result::Ok(());
4747 }
4748 };
4749 let mut offset: i64 = 0;
4750 loop {
4751 let rows: ::std::vec::Vec<Self> =
4752 #root::query::QuerySet::<Self>::default()
4753 .order_by(&[(pk_col, false)])
4754 .limit(n)
4755 .offset(offset)
4756 .fetch(pool)
4757 .await?;
4758 if rows.is_empty() {
4759 return ::core::result::Result::Ok(());
4760 }
4761 let len = rows.len() as i64;
4762 cb(rows).await?;
4763 if len < n {
4764 return ::core::result::Result::Ok(());
4765 }
4766 offset += n;
4767 }
4768 }
4769
4770 pub async fn chunk_by_id<F, Fut>(
4782 n: i64,
4783 pool: &#root::sql::Pool,
4784 mut cb: F,
4785 ) -> ::core::result::Result<(), #root::sql::ExecError>
4786 where
4787 F: ::core::ops::FnMut(::std::vec::Vec<Self>) -> Fut,
4788 Fut: ::core::future::Future<
4789 Output = ::core::result::Result<(), #root::sql::ExecError>,
4790 >,
4791 {
4792 use #root::sql::FetcherPool as _;
4793 let pk_col = match <Self as #root::core::Model>::SCHEMA.primary_key() {
4795 ::core::option::Option::Some(f) => f.name,
4796 ::core::option::Option::None => {
4797 return ::core::result::Result::Ok(());
4798 }
4799 };
4800 let key = ::std::format!("{}__gt", pk_col);
4803 let mut last_seen: ::core::option::Option<#root::core::SqlValue> =
4804 ::core::option::Option::None;
4805 loop {
4806 let mut qs = #root::query::QuerySet::<Self>::default();
4807 if let ::core::option::Option::Some(v) = last_seen.take() {
4808 qs = qs.filter(key.as_str(), v);
4809 }
4810 let rows: ::std::vec::Vec<Self> = qs
4811 .order_by(&[(pk_col, false)])
4812 .limit(n)
4813 .fetch(pool)
4814 .await?;
4815 let len = rows.len() as i64;
4816 let ::core::option::Option::Some(max_pk) =
4819 rows.last().map(|r| r.__rustango_pk_value())
4820 else {
4821 return ::core::result::Result::Ok(());
4822 };
4823 cb(rows).await?;
4824 if len < n {
4825 return ::core::result::Result::Ok(());
4826 }
4827 last_seen = ::core::option::Option::Some(max_pk);
4828 }
4829 }
4830
4831 pub async fn each<F, Fut>(
4849 batch: i64,
4850 pool: &#root::sql::Pool,
4851 mut cb: F,
4852 ) -> ::core::result::Result<(), #root::sql::ExecError>
4853 where
4854 F: ::core::ops::FnMut(Self) -> Fut,
4855 Fut: ::core::future::Future<
4856 Output = ::core::result::Result<(), #root::sql::ExecError>,
4857 >,
4858 {
4859 use #root::sql::FetcherPool as _;
4860 let pk_col = match <Self as #root::core::Model>::SCHEMA.primary_key() {
4862 ::core::option::Option::Some(f) => f.name,
4863 ::core::option::Option::None => {
4864 return ::core::result::Result::Ok(());
4865 }
4866 };
4867 let key = ::std::format!("{}__gt", pk_col);
4868 let mut last_seen: ::core::option::Option<#root::core::SqlValue> =
4869 ::core::option::Option::None;
4870 loop {
4871 let mut qs = #root::query::QuerySet::<Self>::default();
4872 if let ::core::option::Option::Some(v) = last_seen.take() {
4873 qs = qs.filter(key.as_str(), v);
4874 }
4875 let rows: ::std::vec::Vec<Self> = qs
4876 .order_by(&[(pk_col, false)])
4877 .limit(batch)
4878 .fetch(pool)
4879 .await?;
4880 let len = rows.len() as i64;
4881 let ::core::option::Option::Some(max_pk) =
4882 rows.last().map(|r| r.__rustango_pk_value())
4883 else {
4884 return ::core::result::Result::Ok(());
4885 };
4886 for row in rows {
4887 cb(row).await?;
4888 }
4889 if len < batch {
4890 return ::core::result::Result::Ok(());
4891 }
4892 last_seen = ::core::option::Option::Some(max_pk);
4893 }
4894 }
4895
4896 pub async fn truncate(
4912 pool: &#root::sql::Pool,
4913 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4914 #truncate_run
4915 }
4916
4917 pub async fn truncate_cascade(
4926 pool: &#root::sql::Pool,
4927 ) -> ::core::result::Result<(), #root::sql::ExecError> {
4928 #truncate_cascade_run
4929 }
4930
4931 pub async fn destroy<V>(
4946 pks: impl ::core::iter::IntoIterator<Item = V>,
4947 pool: &#root::sql::Pool,
4948 ) -> ::core::result::Result<u64, #root::sql::ExecError>
4949 where
4950 V: ::core::convert::Into<#root::core::SqlValue>,
4951 {
4952 let _values: ::std::vec::Vec<#root::core::SqlValue> =
4953 pks.into_iter().map(::core::convert::Into::into).collect();
4954 if _values.is_empty() {
4955 return ::core::result::Result::Ok(0);
4956 }
4957 let _pk = <Self as #root::core::Model>::SCHEMA
4958 .primary_key()
4959 .ok_or_else(|| {
4960 #root::sql::ExecError::Sql(#root::sql::SqlError::MissingPrimaryKey)
4961 })?;
4962 let mut _queries = ::std::vec::Vec::new();
4964 for _keys in #root::sql::__rustango_in_batches::<Self>(pool, _values)? {
4965 _queries.push(
4966 #root::query::QuerySet::<Self>::default()
4967 .filter_op(
4968 _pk.name,
4969 #root::core::Op::In,
4970 #root::core::SqlValue::List(_keys),
4971 )
4972 .compile_delete()?,
4973 );
4974 }
4975 #destroy_run
4976 }
4977
4978 pub async fn where_(
4996 col: &str,
4997 val: impl ::core::convert::Into<#root::core::SqlValue>,
4998 pool: &#root::sql::Pool,
4999 ) -> ::core::result::Result<
5000 ::std::vec::Vec<Self>,
5001 #root::sql::ExecError,
5002 > {
5003 use #root::sql::FetcherPool as _;
5004 #root::query::QuerySet::<Self>::default()
5005 .filter(col, val)
5006 .fetch(pool)
5007 .await
5008 }
5009
5010 pub async fn where_in<V>(
5024 col: &str,
5025 vals: impl ::core::iter::IntoIterator<Item = V>,
5026 pool: &#root::sql::Pool,
5027 ) -> ::core::result::Result<
5028 ::std::vec::Vec<Self>,
5029 #root::sql::ExecError,
5030 >
5031 where
5032 V: ::core::convert::Into<#root::core::SqlValue>,
5033 {
5034 use #root::sql::FetcherPool as _;
5035 let _values: ::std::vec::Vec<#root::core::SqlValue> =
5036 vals.into_iter().map(::core::convert::Into::into).collect();
5037 if _values.is_empty() {
5038 return ::core::result::Result::Ok(::std::vec::Vec::new());
5039 }
5040 let _key = ::std::format!("{}__in", col);
5041 let _batches = #root::sql::__rustango_in_batches::<Self>(pool, _values)?;
5042 let _split = _batches.len() > 1;
5043 let mut _rows = ::std::vec::Vec::new();
5044 for _keys in _batches {
5045 _rows.extend(
5046 #root::query::QuerySet::<Self>::default()
5047 .filter(&_key, #root::core::SqlValue::List(_keys))
5048 .fetch(pool)
5049 .await?,
5050 );
5051 }
5052 if _split {
5053 _rows = #root::sql::__rustango_dedupe_by_pk(_rows);
5054 }
5055 ::core::result::Result::Ok(_rows)
5056 }
5057
5058 pub async fn where_not_in<V>(
5069 col: &str,
5070 vals: impl ::core::iter::IntoIterator<Item = V>,
5071 pool: &#root::sql::Pool,
5072 ) -> ::core::result::Result<
5073 ::std::vec::Vec<Self>,
5074 #root::sql::ExecError,
5075 >
5076 where
5077 V: ::core::convert::Into<#root::core::SqlValue>,
5078 {
5079 use #root::sql::FetcherPool as _;
5080 let _values: ::std::vec::Vec<#root::core::SqlValue> =
5081 vals.into_iter().map(::core::convert::Into::into).collect();
5082 if _values.is_empty() {
5083 return #root::query::QuerySet::<Self>::default()
5084 .fetch(pool)
5085 .await;
5086 }
5087 let _key = ::std::format!("{}__not_in", col);
5088 let _values = #root::sql::__rustango_one_in_list::<Self>(pool, _values)?;
5090 #root::query::QuerySet::<Self>::default()
5091 .filter(&_key, #root::core::SqlValue::List(_values))
5092 .fetch(pool)
5093 .await
5094 }
5095
5096 pub async fn where_null(
5104 col: &str,
5105 pool: &#root::sql::Pool,
5106 ) -> ::core::result::Result<
5107 ::std::vec::Vec<Self>,
5108 #root::sql::ExecError,
5109 > {
5110 use #root::sql::FetcherPool as _;
5111 let _key = ::std::format!("{}__isnull", col);
5112 #root::query::QuerySet::<Self>::default()
5113 .filter(&_key, #root::core::SqlValue::Bool(true))
5114 .fetch(pool)
5115 .await
5116 }
5117
5118 pub async fn where_not_null(
5126 col: &str,
5127 pool: &#root::sql::Pool,
5128 ) -> ::core::result::Result<
5129 ::std::vec::Vec<Self>,
5130 #root::sql::ExecError,
5131 > {
5132 use #root::sql::FetcherPool as _;
5133 let _key = ::std::format!("{}__isnull", col);
5134 #root::query::QuerySet::<Self>::default()
5135 .filter(&_key, #root::core::SqlValue::Bool(false))
5136 .fetch(pool)
5137 .await
5138 }
5139
5140 pub async fn random_n(
5153 n: i64,
5154 pool: &#root::sql::Pool,
5155 ) -> ::core::result::Result<
5156 ::std::vec::Vec<Self>,
5157 #root::sql::ExecError,
5158 > {
5159 use #root::sql::FetcherPool as _;
5160 #root::query::QuerySet::<Self>::default()
5161 .order_random()
5162 .limit(n)
5163 .fetch(pool)
5164 .await
5165 }
5166
5167 pub async fn random(
5176 pool: &#root::sql::Pool,
5177 ) -> ::core::result::Result<
5178 ::core::option::Option<Self>,
5179 #root::sql::ExecError,
5180 > {
5181 ::core::result::Result::Ok(
5182 Self::random_n(1, pool).await?.into_iter().next(),
5183 )
5184 }
5185
5186 pub async fn oldest(
5195 field: &str,
5196 pool: &#root::sql::Pool,
5197 ) -> ::core::result::Result<
5198 ::std::vec::Vec<Self>,
5199 #root::sql::ExecError,
5200 > {
5201 use #root::sql::FetcherPool as _;
5202 #root::query::QuerySet::<Self>::default()
5203 .order_by(&[(field, false)])
5204 .fetch(pool)
5205 .await
5206 }
5207
5208 pub async fn newest(
5217 field: &str,
5218 pool: &#root::sql::Pool,
5219 ) -> ::core::result::Result<
5220 ::std::vec::Vec<Self>,
5221 #root::sql::ExecError,
5222 > {
5223 use #root::sql::FetcherPool as _;
5224 #root::query::QuerySet::<Self>::default()
5225 .order_by(&[(field, true)])
5226 .fetch(pool)
5227 .await
5228 }
5229
5230 pub async fn where_year(
5240 col: &str,
5241 year: i64,
5242 pool: &#root::sql::Pool,
5243 ) -> ::core::result::Result<
5244 ::std::vec::Vec<Self>,
5245 #root::sql::ExecError,
5246 > {
5247 use #root::sql::FetcherPool as _;
5248 let _key = ::std::format!("{}__year", col);
5249 #root::query::QuerySet::<Self>::default()
5250 .filter(&_key, #root::core::SqlValue::I64(year))
5251 .fetch(pool)
5252 .await
5253 }
5254
5255 pub async fn where_month(
5264 col: &str,
5265 month: i64,
5266 pool: &#root::sql::Pool,
5267 ) -> ::core::result::Result<
5268 ::std::vec::Vec<Self>,
5269 #root::sql::ExecError,
5270 > {
5271 use #root::sql::FetcherPool as _;
5272 let _key = ::std::format!("{}__month", col);
5273 #root::query::QuerySet::<Self>::default()
5274 .filter(&_key, #root::core::SqlValue::I64(month))
5275 .fetch(pool)
5276 .await
5277 }
5278
5279 pub async fn where_day(
5288 col: &str,
5289 day: i64,
5290 pool: &#root::sql::Pool,
5291 ) -> ::core::result::Result<
5292 ::std::vec::Vec<Self>,
5293 #root::sql::ExecError,
5294 > {
5295 use #root::sql::FetcherPool as _;
5296 let _key = ::std::format!("{}__day", col);
5297 #root::query::QuerySet::<Self>::default()
5298 .filter(&_key, #root::core::SqlValue::I64(day))
5299 .fetch(pool)
5300 .await
5301 }
5302
5303 pub async fn where_hour(
5312 col: &str,
5313 hour: i64,
5314 pool: &#root::sql::Pool,
5315 ) -> ::core::result::Result<
5316 ::std::vec::Vec<Self>,
5317 #root::sql::ExecError,
5318 > {
5319 use #root::sql::FetcherPool as _;
5320 let _key = ::std::format!("{}__hour", col);
5321 #root::query::QuerySet::<Self>::default()
5322 .filter(&_key, #root::core::SqlValue::I64(hour))
5323 .fetch(pool)
5324 .await
5325 }
5326
5327 pub async fn where_minute(
5336 col: &str,
5337 minute: i64,
5338 pool: &#root::sql::Pool,
5339 ) -> ::core::result::Result<
5340 ::std::vec::Vec<Self>,
5341 #root::sql::ExecError,
5342 > {
5343 use #root::sql::FetcherPool as _;
5344 let _key = ::std::format!("{}__minute", col);
5345 #root::query::QuerySet::<Self>::default()
5346 .filter(&_key, #root::core::SqlValue::I64(minute))
5347 .fetch(pool)
5348 .await
5349 }
5350
5351 pub async fn where_like(
5361 col: &str,
5362 pattern: impl ::core::convert::Into<::std::string::String>,
5363 pool: &#root::sql::Pool,
5364 ) -> ::core::result::Result<
5365 ::std::vec::Vec<Self>,
5366 #root::sql::ExecError,
5367 > {
5368 use #root::sql::FetcherPool as _;
5369 let _key = ::std::format!("{}__like", col);
5370 #root::query::QuerySet::<Self>::default()
5371 .filter(
5372 &_key,
5373 #root::core::SqlValue::String(pattern.into()),
5374 )
5375 .fetch(pool)
5376 .await
5377 }
5378
5379 pub async fn where_ilike(
5388 col: &str,
5389 pattern: impl ::core::convert::Into<::std::string::String>,
5390 pool: &#root::sql::Pool,
5391 ) -> ::core::result::Result<
5392 ::std::vec::Vec<Self>,
5393 #root::sql::ExecError,
5394 > {
5395 use #root::sql::FetcherPool as _;
5396 let _key = ::std::format!("{}__ilike", col);
5397 #root::query::QuerySet::<Self>::default()
5398 .filter(
5399 &_key,
5400 #root::core::SqlValue::String(pattern.into()),
5401 )
5402 .fetch(pool)
5403 .await
5404 }
5405
5406 pub async fn where_starts_with(
5415 col: &str,
5416 prefix: impl ::core::convert::Into<::std::string::String>,
5417 pool: &#root::sql::Pool,
5418 ) -> ::core::result::Result<
5419 ::std::vec::Vec<Self>,
5420 #root::sql::ExecError,
5421 > {
5422 use #root::sql::FetcherPool as _;
5423 let _key = ::std::format!("{}__startswith", col);
5424 #root::query::QuerySet::<Self>::default()
5425 .filter(
5426 &_key,
5427 #root::core::SqlValue::String(prefix.into()),
5428 )
5429 .fetch(pool)
5430 .await
5431 }
5432
5433 pub async fn where_ends_with(
5442 col: &str,
5443 suffix: impl ::core::convert::Into<::std::string::String>,
5444 pool: &#root::sql::Pool,
5445 ) -> ::core::result::Result<
5446 ::std::vec::Vec<Self>,
5447 #root::sql::ExecError,
5448 > {
5449 use #root::sql::FetcherPool as _;
5450 let _key = ::std::format!("{}__endswith", col);
5451 #root::query::QuerySet::<Self>::default()
5452 .filter(
5453 &_key,
5454 #root::core::SqlValue::String(suffix.into()),
5455 )
5456 .fetch(pool)
5457 .await
5458 }
5459
5460 pub async fn where_contains(
5469 col: &str,
5470 substr: impl ::core::convert::Into<::std::string::String>,
5471 pool: &#root::sql::Pool,
5472 ) -> ::core::result::Result<
5473 ::std::vec::Vec<Self>,
5474 #root::sql::ExecError,
5475 > {
5476 use #root::sql::FetcherPool as _;
5477 let _key = ::std::format!("{}__contains", col);
5478 #root::query::QuerySet::<Self>::default()
5479 .filter(
5480 &_key,
5481 #root::core::SqlValue::String(substr.into()),
5482 )
5483 .fetch(pool)
5484 .await
5485 }
5486
5487 pub async fn where_gt(
5495 col: &str,
5496 val: impl ::core::convert::Into<#root::core::SqlValue>,
5497 pool: &#root::sql::Pool,
5498 ) -> ::core::result::Result<
5499 ::std::vec::Vec<Self>,
5500 #root::sql::ExecError,
5501 > {
5502 use #root::sql::FetcherPool as _;
5503 let _key = ::std::format!("{}__gt", col);
5504 #root::query::QuerySet::<Self>::default()
5505 .filter(&_key, val)
5506 .fetch(pool)
5507 .await
5508 }
5509
5510 pub async fn where_gte(
5518 col: &str,
5519 val: impl ::core::convert::Into<#root::core::SqlValue>,
5520 pool: &#root::sql::Pool,
5521 ) -> ::core::result::Result<
5522 ::std::vec::Vec<Self>,
5523 #root::sql::ExecError,
5524 > {
5525 use #root::sql::FetcherPool as _;
5526 let _key = ::std::format!("{}__gte", col);
5527 #root::query::QuerySet::<Self>::default()
5528 .filter(&_key, val)
5529 .fetch(pool)
5530 .await
5531 }
5532
5533 pub async fn where_lt(
5541 col: &str,
5542 val: impl ::core::convert::Into<#root::core::SqlValue>,
5543 pool: &#root::sql::Pool,
5544 ) -> ::core::result::Result<
5545 ::std::vec::Vec<Self>,
5546 #root::sql::ExecError,
5547 > {
5548 use #root::sql::FetcherPool as _;
5549 let _key = ::std::format!("{}__lt", col);
5550 #root::query::QuerySet::<Self>::default()
5551 .filter(&_key, val)
5552 .fetch(pool)
5553 .await
5554 }
5555
5556 pub async fn where_lte(
5564 col: &str,
5565 val: impl ::core::convert::Into<#root::core::SqlValue>,
5566 pool: &#root::sql::Pool,
5567 ) -> ::core::result::Result<
5568 ::std::vec::Vec<Self>,
5569 #root::sql::ExecError,
5570 > {
5571 use #root::sql::FetcherPool as _;
5572 let _key = ::std::format!("{}__lte", col);
5573 #root::query::QuerySet::<Self>::default()
5574 .filter(&_key, val)
5575 .fetch(pool)
5576 .await
5577 }
5578
5579 pub async fn where_ne(
5587 col: &str,
5588 val: impl ::core::convert::Into<#root::core::SqlValue>,
5589 pool: &#root::sql::Pool,
5590 ) -> ::core::result::Result<
5591 ::std::vec::Vec<Self>,
5592 #root::sql::ExecError,
5593 > {
5594 use #root::sql::FetcherPool as _;
5595 let _key = ::std::format!("{}__ne", col);
5596 #root::query::QuerySet::<Self>::default()
5597 .filter(&_key, val)
5598 .fetch(pool)
5599 .await
5600 }
5601
5602 pub async fn where_any(
5616 cols: &[&str],
5617 val: impl ::core::convert::Into<#root::core::SqlValue>,
5618 pool: &#root::sql::Pool,
5619 ) -> ::core::result::Result<
5620 ::std::vec::Vec<Self>,
5621 #root::sql::ExecError,
5622 > {
5623 Self::__where_multi(cols, val, false, pool).await
5624 }
5625
5626 pub async fn where_all(
5633 cols: &[&str],
5634 val: impl ::core::convert::Into<#root::core::SqlValue>,
5635 pool: &#root::sql::Pool,
5636 ) -> ::core::result::Result<
5637 ::std::vec::Vec<Self>,
5638 #root::sql::ExecError,
5639 > {
5640 Self::__where_multi(cols, val, true, pool).await
5641 }
5642
5643 #[doc(hidden)]
5647 pub async fn __where_multi(
5648 cols: &[&str],
5649 val: impl ::core::convert::Into<#root::core::SqlValue>,
5650 all: bool,
5651 pool: &#root::sql::Pool,
5652 ) -> ::core::result::Result<
5653 ::std::vec::Vec<Self>,
5654 #root::sql::ExecError,
5655 > {
5656 #root::sql::model_shortcuts::where_multi_pool::<Self>(cols, val, all, pool)
5657 .await
5658 }
5659
5660 pub async fn take(
5670 n: i64,
5671 pool: &#root::sql::Pool,
5672 ) -> ::core::result::Result<
5673 ::std::vec::Vec<Self>,
5674 #root::sql::ExecError,
5675 > {
5676 use #root::sql::FetcherPool as _;
5677 #root::query::QuerySet::<Self>::default()
5678 .limit(n)
5679 .fetch(pool)
5680 .await
5681 }
5682
5683 pub async fn for_page(
5695 page: i64,
5696 per_page: i64,
5697 pool: &#root::sql::Pool,
5698 ) -> ::core::result::Result<
5699 ::std::vec::Vec<Self>,
5700 #root::sql::ExecError,
5701 > {
5702 use #root::sql::FetcherPool as _;
5703 let _offset = #root::list_params::page_offset(page, per_page);
5704 #root::query::QuerySet::<Self>::default()
5705 .limit(per_page)
5706 .offset(_offset)
5707 .fetch(pool)
5708 .await
5709 }
5710
5711 pub async fn paginate(
5727 page: i64,
5728 per_page: i64,
5729 pool: &#root::sql::Pool,
5730 ) -> ::core::result::Result<
5731 (::std::vec::Vec<Self>, i64),
5732 #root::sql::ExecError,
5733 > {
5734 let total = {
5740 use #root::sql::CounterPool as _;
5741 #root::query::QuerySet::<Self>::default()
5742 .count(pool)
5743 .await?
5744 };
5745 let rows = Self::for_page(page, per_page, pool).await?;
5746 ::core::result::Result::Ok((rows, total))
5747 }
5748
5749 pub async fn update_where(
5763 where_col: &str,
5764 where_val: impl ::core::convert::Into<#root::core::SqlValue>,
5765 set_col: &str,
5766 set_val: impl ::core::convert::Into<#root::core::SqlValue>,
5767 pool: &#root::sql::Pool,
5768 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
5769 let _query = #root::query::QuerySet::<Self>::default()
5770 .filter(where_col, where_val)
5771 .update()
5772 .set(set_col, set_val)
5773 .compile()?;
5774 #bulk_update_run
5775 }
5776
5777 pub async fn delete_where(
5785 where_col: &str,
5786 where_val: impl ::core::convert::Into<#root::core::SqlValue>,
5787 pool: &#root::sql::Pool,
5788 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
5789 let _query = #root::query::QuerySet::<Self>::default()
5790 .filter_op(where_col, #root::core::Op::Eq, where_val)
5791 .compile_delete()?;
5792 #bulk_delete_run
5793 }
5794
5795 pub async fn update_all(
5808 set_col: &str,
5809 set_val: impl ::core::convert::Into<#root::core::SqlValue>,
5810 pool: &#root::sql::Pool,
5811 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
5812 let _query = #root::query::QuerySet::<Self>::default()
5813 .update()
5814 .set(set_col, set_val)
5815 .compile()?;
5816 #bulk_update_run
5817 }
5818
5819 pub async fn where_not_like(
5828 col: &str,
5829 pattern: impl ::core::convert::Into<::std::string::String>,
5830 pool: &#root::sql::Pool,
5831 ) -> ::core::result::Result<
5832 ::std::vec::Vec<Self>,
5833 #root::sql::ExecError,
5834 > {
5835 use #root::sql::FetcherPool as _;
5836 let _key = ::std::format!("{}__not_like", col);
5837 #root::query::QuerySet::<Self>::default()
5838 .filter(
5839 &_key,
5840 #root::core::SqlValue::String(pattern.into()),
5841 )
5842 .fetch(pool)
5843 .await
5844 }
5845
5846 pub async fn where_not_ilike(
5855 col: &str,
5856 pattern: impl ::core::convert::Into<::std::string::String>,
5857 pool: &#root::sql::Pool,
5858 ) -> ::core::result::Result<
5859 ::std::vec::Vec<Self>,
5860 #root::sql::ExecError,
5861 > {
5862 use #root::sql::FetcherPool as _;
5863 let _key = ::std::format!("{}__not_ilike", col);
5864 #root::query::QuerySet::<Self>::default()
5865 .filter(
5866 &_key,
5867 #root::core::SqlValue::String(pattern.into()),
5868 )
5869 .fetch(pool)
5870 .await
5871 }
5872
5873 pub async fn where_not_between(
5882 col: &str,
5883 lo: impl ::core::convert::Into<#root::core::SqlValue>,
5884 hi: impl ::core::convert::Into<#root::core::SqlValue>,
5885 pool: &#root::sql::Pool,
5886 ) -> ::core::result::Result<
5887 ::std::vec::Vec<Self>,
5888 #root::sql::ExecError,
5889 > {
5890 use #root::sql::FetcherPool as _;
5891 let _key = ::std::format!("{}__not_between", col);
5892 let _vals = #root::core::SqlValue::List(::std::vec![
5893 ::core::convert::Into::into(lo),
5894 ::core::convert::Into::into(hi),
5895 ]);
5896 #root::query::QuerySet::<Self>::default()
5897 .filter(&_key, _vals)
5898 .fetch(pool)
5899 .await
5900 }
5901
5902 #[must_use]
5905 pub fn table_name() -> &'static str {
5906 <Self as #root::core::Model>::SCHEMA.table
5907 }
5908
5909 #[must_use]
5914 pub fn primary_key_column() -> ::core::option::Option<&'static str> {
5915 <Self as #root::core::Model>::SCHEMA
5916 .primary_key()
5917 .map(|f| f.column)
5918 }
5919
5920 pub async fn where_between(
5929 col: &str,
5930 lo: impl ::core::convert::Into<#root::core::SqlValue>,
5931 hi: impl ::core::convert::Into<#root::core::SqlValue>,
5932 pool: &#root::sql::Pool,
5933 ) -> ::core::result::Result<
5934 ::std::vec::Vec<Self>,
5935 #root::sql::ExecError,
5936 > {
5937 use #root::sql::FetcherPool as _;
5938 let _key = ::std::format!("{}__between", col);
5939 let _vals = #root::core::SqlValue::List(::std::vec![
5940 ::core::convert::Into::into(lo),
5941 ::core::convert::Into::into(hi),
5942 ]);
5943 #root::query::QuerySet::<Self>::default()
5944 .filter(&_key, _vals)
5945 .fetch(pool)
5946 .await
5947 }
5948
5949 pub async fn first_where(
5964 col: &str,
5965 val: impl ::core::convert::Into<#root::core::SqlValue>,
5966 pool: &#root::sql::Pool,
5967 ) -> ::core::result::Result<
5968 ::core::option::Option<Self>,
5969 #root::sql::ExecError,
5970 > {
5971 #root::query::QuerySet::<Self>::default()
5972 .filter(col, val)
5973 .first(pool)
5974 .await
5975 }
5976
5977 #value_method
5978
5979 pub async fn latest(
5993 field: &str,
5994 pool: &#root::sql::Pool,
5995 ) -> ::core::result::Result<
5996 ::core::option::Option<Self>,
5997 #root::sql::ExecError,
5998 > {
5999 #root::query::QuerySet::<Self>::default()
6000 .latest(field, pool)
6001 .await
6002 }
6003
6004 pub async fn earliest(
6010 field: &str,
6011 pool: &#root::sql::Pool,
6012 ) -> ::core::result::Result<
6013 ::core::option::Option<Self>,
6014 #root::sql::ExecError,
6015 > {
6016 #root::query::QuerySet::<Self>::default()
6017 .earliest(field, pool)
6018 .await
6019 }
6020
6021 #count_method
6022
6023 pub async fn exists(
6032 pool: &#root::sql::Pool,
6033 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
6034 use #root::sql::ExistsPool as _;
6035 #root::query::QuerySet::<Self>::default()
6036 .exists(pool)
6037 .await
6038 }
6039
6040 pub async fn doesnt_exist(
6047 pool: &#root::sql::Pool,
6048 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
6049 Self::exists(pool).await.map(|e| !e)
6050 }
6051
6052 pub async fn contains_pk(
6066 pk: impl ::core::convert::Into<#root::core::SqlValue> + ::core::marker::Send,
6067 pool: &#root::sql::Pool,
6068 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
6069 use #root::sql::ExistsPool as _;
6070 #root::query::QuerySet::<Self>::default()
6071 .contains_pk(pool, pk)
6072 .await
6073 }
6074
6075 #sum_method
6076 #avg_method
6077 #min_method
6078 #max_method
6079
6080 pub async fn all(
6096 pool: &#root::sql::Pool,
6097 ) -> ::core::result::Result<::std::vec::Vec<Self>, #root::sql::ExecError>
6098 {
6099 use #root::sql::FetcherPool as _;
6100 #root::query::QuerySet::<Self>::default()
6101 .fetch(pool)
6102 .await
6103 }
6104
6105 pub async fn find_many<V>(
6128 pks: impl ::core::iter::IntoIterator<Item = V>,
6129 pool: &#root::sql::Pool,
6130 ) -> ::core::result::Result<
6131 ::std::vec::Vec<Self>,
6132 #root::sql::ExecError,
6133 >
6134 where
6135 V: ::core::convert::Into<#root::core::SqlValue>,
6136 {
6137 use #root::sql::FetcherPool as _;
6138 let _values: ::std::vec::Vec<#root::core::SqlValue> =
6139 pks.into_iter().map(::core::convert::Into::into).collect();
6140 if _values.is_empty() {
6141 return ::core::result::Result::Ok(::std::vec::Vec::new());
6142 }
6143 let _key = ::std::format!("{}__in", ::core::stringify!(#pk_ident));
6144 let _batches = #root::sql::__rustango_in_batches::<Self>(pool, _values)?;
6145 let _split = _batches.len() > 1;
6146 let mut _rows = ::std::vec::Vec::new();
6147 for _keys in _batches {
6148 _rows.extend(
6149 #root::query::QuerySet::<Self>::default()
6150 .filter(&_key, #root::core::SqlValue::List(_keys))
6151 .fetch(pool)
6152 .await?,
6153 );
6154 }
6155 if _split {
6156 _rows = #root::sql::__rustango_dedupe_by_pk(_rows);
6157 }
6158 ::core::result::Result::Ok(_rows)
6159 }
6160
6161 pub async fn find(
6176 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6177 pool: &#root::sql::Pool,
6178 ) -> ::core::result::Result<::core::option::Option<Self>, #root::sql::ExecError>
6179 {
6180 use #root::sql::FetcherPool as _;
6181 let _pk_val: #root::core::SqlValue = pk.into();
6182 let mut _rows: ::std::vec::Vec<Self> =
6183 #root::query::QuerySet::<Self>::default()
6184 .filter(::core::stringify!(#pk_ident), _pk_val)
6185 .limit(1)
6186 .fetch(pool)
6187 .await?;
6188 ::core::result::Result::Ok(_rows.into_iter().next())
6189 }
6190
6191 pub async fn find_or_fail(
6208 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6209 pool: &#root::sql::Pool,
6210 ) -> ::core::result::Result<Self, #root::sql::ExecError> {
6211 match Self::find(pk, pool).await? {
6212 ::core::option::Option::Some(_row) => ::core::result::Result::Ok(_row),
6213 ::core::option::Option::None => ::core::result::Result::Err(
6214 #root::sql::ExecError::Driver(
6215 #root::sql::sqlx::Error::RowNotFound,
6216 ),
6217 ),
6218 }
6219 }
6220
6221 pub async fn find_many_or_fail<V>(
6241 pks: impl ::core::iter::IntoIterator<Item = V>,
6242 pool: &#root::sql::Pool,
6243 ) -> ::core::result::Result<
6244 ::std::vec::Vec<Self>,
6245 #root::sql::ExecError,
6246 >
6247 where
6248 V: ::core::convert::Into<#root::core::SqlValue>,
6249 {
6250 let _values: ::std::vec::Vec<#root::core::SqlValue> =
6251 pks.into_iter().map(::core::convert::Into::into).collect();
6252 if _values.is_empty() {
6253 return ::core::result::Result::Ok(::std::vec::Vec::new());
6254 }
6255 use #root::sql::ExistsPool as _;
6256 let mut _seen: ::std::collections::HashSet<
6257 ::std::string::String,
6258 > = ::std::collections::HashSet::new();
6259 let mut _keys: ::std::vec::Vec<#root::core::SqlValue> =
6260 ::std::vec::Vec::new();
6261 for v in &_values {
6262 if _seen.insert(v.to_display_string()) {
6263 _keys.push(::core::clone::Clone::clone(v));
6264 }
6265 }
6266 let _rows = Self::find_many(_values, pool).await?;
6267 let _found: ::std::collections::HashSet<::std::string::String> = _rows
6268 .iter()
6269 .map(|r| r.__rustango_pk_value().to_display_string())
6270 .collect();
6271 for _key in _keys {
6274 if _found.contains(&_key.to_display_string()) {
6275 continue;
6276 }
6277 let _hit = #root::query::QuerySet::<Self>::default()
6278 .filter(::core::stringify!(#pk_ident), _key)
6279 .exists(pool)
6280 .await?;
6281 if !_hit {
6282 return ::core::result::Result::Err(
6283 #root::sql::ExecError::Driver(
6284 #root::sql::sqlx::Error::RowNotFound,
6285 ),
6286 );
6287 }
6288 }
6289 ::core::result::Result::Ok(_rows)
6290 }
6291
6292 pub async fn find_or<F>(
6307 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6308 pool: &#root::sql::Pool,
6309 fallback: F,
6310 ) -> ::core::result::Result<Self, #root::sql::ExecError>
6311 where
6312 F: ::core::ops::FnOnce() -> Self,
6313 {
6314 ::core::result::Result::Ok(
6315 Self::find(pk, pool).await?.unwrap_or_else(fallback),
6316 )
6317 }
6318
6319 pub async fn find_or_new<F>(
6333 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6334 pool: &#root::sql::Pool,
6335 fallback: F,
6336 ) -> ::core::result::Result<(Self, bool), #root::sql::ExecError>
6337 where
6338 F: ::core::ops::FnOnce() -> Self,
6339 {
6340 match Self::find(pk, pool).await? {
6341 ::core::option::Option::Some(_row) => ::core::result::Result::Ok((_row, true)),
6342 ::core::option::Option::None => {
6343 ::core::result::Result::Ok((fallback(), false))
6344 }
6345 }
6346 }
6347
6348 pub async fn find_or_insert<F>(
6372 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6373 pool: &#root::sql::Pool,
6374 fallback: F,
6375 ) -> ::core::result::Result<(Self, bool), #root::sql::ExecError>
6376 where
6377 F: ::core::ops::FnOnce() -> Self,
6378 {
6379 if let ::core::option::Option::Some(_row) = Self::find(pk, pool).await? {
6380 return ::core::result::Result::Ok((_row, true));
6381 }
6382 let mut _new = fallback();
6383 _new.save(pool).await?;
6384 ::core::result::Result::Ok((_new, false))
6385 }
6386
6387 pub async fn first_or<F>(
6394 pool: &#root::sql::Pool,
6395 fallback: F,
6396 ) -> ::core::result::Result<Self, #root::sql::ExecError>
6397 where
6398 F: ::core::ops::FnOnce() -> Self,
6399 {
6400 ::core::result::Result::Ok(
6404 #root::query::QuerySet::<Self>::default()
6405 .first(pool)
6406 .await?
6407 .unwrap_or_else(fallback),
6408 )
6409 }
6410
6411 pub async fn sole(
6421 col: &str,
6422 val: impl ::core::convert::Into<#root::core::SqlValue>,
6423 pool: &#root::sql::Pool,
6424 ) -> ::core::result::Result<Self, #root::sql::ExecError> {
6425 let mut _rows = Self::where_(col, val, pool).await?;
6426 match _rows.len() {
6427 0 => ::core::result::Result::Err(
6428 #root::sql::ExecError::Driver(
6429 #root::sql::sqlx::Error::RowNotFound,
6430 ),
6431 ),
6432 1 => ::core::result::Result::Ok(_rows.remove(0)),
6433 n => ::core::result::Result::Err(
6434 #root::sql::ExecError::MultipleRowsReturned {
6435 op: "sole",
6436 table: <Self as #root::core::Model>::SCHEMA.name,
6437 count: n,
6438 },
6439 ),
6440 }
6441 }
6442 }
6443 } else {
6444 quote!()
6445 };
6446
6447 let tx_emit = |op: TokenStream2| -> TokenStream2 {
6450 if audited_fields.is_some() && primary_key.is_some() {
6451 quote! {
6452 #root::audit::__emit_many_tx(
6453 tx,
6454 &[self.__rustango_audit_entry(#root::audit::AuditOp::#op)],
6455 )
6456 .await?;
6457 }
6458 } else {
6459 quote!()
6460 }
6461 };
6462 let tx_insert_method = {
6463 let self_ref = if fields.has_auto {
6464 quote!(&mut self)
6465 } else {
6466 quote!(&self)
6467 };
6468 let emit = tx_emit(quote!(Create));
6469 let refuse_pkless = if audited_fields.is_some() && primary_key.is_none() {
6470 quote! {
6471 return ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
6472 table: <Self as #root::core::Model>::SCHEMA.table,
6473 });
6474 }
6475 } else {
6476 quote!()
6477 };
6478 quote! {
6479 #[allow(unreachable_code)]
6485 pub async fn insert_tx(
6486 #self_ref,
6487 tx: &mut #root::sql::PoolTx<'_>,
6488 ) -> ::core::result::Result<(), #root::sql::ExecError> {
6489 #refuse_pkless
6490 self.__rustango_insert_exec(&mut *tx).await?;
6491 #emit
6492 ::core::result::Result::Ok(())
6493 }
6494 }
6495 };
6496 let tx_save_method = if let Some((pk_ident, _)) = primary_key {
6497 let emit_create = tx_emit(quote!(Create));
6498 let emit_update = tx_emit(quote!(Update));
6499 let was_unset = if fields.pk_is_auto {
6500 quote!(matches!(self.#pk_ident, #root::sql::Auto::Unset))
6501 } else {
6502 quote!(false)
6503 };
6504 quote! {
6505 pub async fn save_tx(
6511 &mut self,
6512 tx: &mut #root::sql::PoolTx<'_>,
6513 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6514 let _was_unset = #was_unset;
6515 let _n = self.__rustango_save_exec(&mut *tx).await?;
6516 if _n > 0 {
6517 if _was_unset {
6518 #emit_create
6519 } else {
6520 #emit_update
6521 }
6522 }
6523 ::core::result::Result::Ok(_n)
6524 }
6525 }
6526 } else {
6527 quote!()
6528 };
6529 let tx_delete_method = if primary_key.is_some() {
6530 let emit = tx_emit(quote!(Delete));
6531 quote! {
6532 pub async fn delete_tx(
6538 &self,
6539 tx: &mut #root::sql::PoolTx<'_>,
6540 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6541 let _n = self.__rustango_delete_exec(&mut *tx).await?;
6542 if _n > 0 {
6543 #emit
6544 }
6545 ::core::result::Result::Ok(_n)
6546 }
6547 }
6548 } else {
6549 quote!()
6550 };
6551
6552 let refuse = |method: &str| -> TokenStream2 {
6556 let reason = format!("`{method}` cannot audit; use the `&Pool` method");
6557 quote!(#root::sql::__macro_internals::refuse_audited::<Self>(#reason)?;)
6558 };
6559 let refuse_save = refuse("save_on");
6560 let save_method = if let Some((pk_ident, pk_column)) = primary_key {
6561 let pk_column_lit = pk_column.as_str();
6562 let assignments = &fields.update_assignments;
6563 let dispatch_unset = if fields.pk_is_auto {
6564 quote! {
6565 if matches!(self.#pk_ident, #root::sql::Auto::Unset) {
6566 return self.__rustango_insert_exec(_executor).await.map(|()| 1u64);
6567 }
6568 }
6569 } else {
6570 quote!()
6571 };
6572 let write = restore_auto_now_unless(
6573 &fields.auto_now_idents,
6574 &save_ret,
6575 &save_keep,
6576 "e! {
6577 let _query = #root::core::UpdateQuery::new(
6578 <Self as #root::core::Model>::SCHEMA,
6579 ::std::vec![ #( #assignments ),* ],
6580 #root::core::WhereExpr::Predicate(#root::core::Filter::new(
6581 #pk_column_lit,
6582 #root::core::Op::Eq,
6583 ::core::convert::Into::<#root::core::SqlValue>::into(
6584 ::core::clone::Clone::clone(&self.#pk_ident)
6585 ),
6586 )),
6587 );
6588 #root::sql::__macro_internals::update_on(_executor, &_query).await
6589 },
6590 );
6591 let upsert = if fields.pk_is_auto {
6592 let upsert_cols = &fields.upsert_update_columns;
6593 let upsert_pushes = &fields.insert_pushes;
6594 let (gen_idents, gen_cols): (Vec<_>, Vec<_>) =
6595 fields.uuid_v7_fields.iter().cloned().unzip();
6596 let upsert_returning = &fields.returning_cols;
6597 let auto_now_idents = &fields.auto_now_idents;
6598 let upsert_target_columns: Vec<String> =
6601 upsert_unique.map_or_else(|| vec![pk_column.clone()], <[String]>::to_vec);
6602 let upsert_target_lits = upsert_target_columns
6603 .iter()
6604 .map(String::as_str)
6605 .collect::<Vec<_>>();
6606 let conflict_clause = if fields.upsert_update_columns.is_empty() {
6607 quote!(#root::core::ConflictClause::DoNothing)
6608 } else {
6609 quote!(#root::core::ConflictClause::DoUpdate {
6610 target: ::std::vec![ #( #upsert_target_lits ),* ],
6611 update_columns: ::std::vec![ #( #upsert_cols ),* ],
6612 })
6613 };
6614 let unit_ret = quote!(::core::result::Result<(), #root::sql::ExecError>);
6615 let unit_keep = quote!(_saved.is_ok());
6616 let exec_body = restore_auto_now_unless(
6617 auto_now_idents,
6618 &unit_ret,
6619 &unit_keep,
6620 "e! {
6621 let _query = self.__rustango_upsert_query();
6622 let _row = #root::sql::__macro_internals::insert_returning_on(
6623 _executor, &_query,
6624 )
6625 .await?;
6626 #root::sql::apply_auto_pk(_row, self)
6627 },
6628 );
6629 let pool_body = if audited_fields.is_some() {
6630 restore_auto_now_unless(
6631 auto_now_idents,
6632 &unit_ret,
6633 &unit_keep,
6634 "e! {
6635 let _query = self.__rustango_upsert_query();
6636 #root::audit::insert_one_with_audit(pool, &_query, self, |_m: &Self, _op| {
6637 _m.__rustango_audit_entry(_op)
6638 })
6639 .await
6640 },
6641 )
6642 } else {
6643 quote!(self.__rustango_upsert_exec(pool).await)
6644 };
6645 let refuse_upsert = refuse("upsert_on");
6646 quote! {
6647 pub async fn upsert(
6655 &mut self,
6656 pool: &#root::sql::Pool,
6657 ) -> ::core::result::Result<(), #root::sql::ExecError> {
6658 #pool_body
6659 }
6660
6661 pub async fn upsert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6667 &mut self,
6668 _executor: _E,
6669 ) -> ::core::result::Result<(), #root::sql::ExecError> {
6670 #refuse_upsert
6671 self.__rustango_upsert_exec(_executor).await
6672 }
6673
6674 #[doc(hidden)]
6675 pub async fn __rustango_upsert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6676 &mut self,
6677 _executor: _E,
6678 ) -> ::core::result::Result<(), #root::sql::ExecError> {
6679 #exec_body
6680 }
6681
6682 #[doc(hidden)]
6684 pub fn __rustango_upsert_query(&mut self) -> #root::core::InsertQuery {
6685 let mut _columns: ::std::vec::Vec<&'static str> = ::std::vec::Vec::new();
6686 let mut _values: ::std::vec::Vec<#root::core::SqlValue> =
6687 ::std::vec::Vec::new();
6688 #[allow(unused_mut)]
6689 let mut _generated: ::std::vec::Vec<&'static str> = ::std::vec::Vec::new();
6690 #(
6691 if matches!(&self.#gen_idents, #root::sql::Auto::Unset) {
6692 _generated.push(#gen_cols);
6693 }
6694 )*
6695 #( self.#auto_now_idents = #root::sql::Auto::Set(#root::__chrono::Utc::now()); )*
6696 #( #upsert_pushes )*
6697 #root::core::InsertQuery::new(
6698 <Self as #root::core::Model>::SCHEMA,
6699 _columns,
6700 _values,
6701 )
6702 .returning(::std::vec![ #( #upsert_returning ),* ])
6703 .on_conflict(#conflict_clause)
6704 .generated(_generated)
6705 }
6706 }
6707 } else {
6708 quote!()
6709 };
6710 Some(quote! {
6711 pub async fn save_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6718 &mut self,
6719 _executor: _E,
6720 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6721 #refuse_save
6722 self.__rustango_save_exec(_executor).await
6723 }
6724
6725 #[doc(hidden)]
6726 pub async fn __rustango_save_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6727 &mut self,
6728 _executor: _E,
6729 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6730 #dispatch_unset
6731 #write
6732 }
6733
6734 #[deprecated(since = "0.62.0", note = "use `save`")]
6739 pub async fn save_pool(
6740 &mut self,
6741 pool: &#root::sql::Pool,
6742 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6743 self.save(pool).await
6744 }
6745
6746 #upsert
6747 })
6748 } else {
6749 None
6750 };
6751
6752 let pk_methods = primary_key.map(|(pk_ident, pk_column)| {
6753 let pk_column_lit = pk_column.as_str();
6754 let refuse_delete = refuse("delete_on");
6755 let soft_delete_methods = if let Some(col) = fields.soft_delete_column.as_deref() {
6762 let col_lit = col;
6763 let sd_field_ident = fields
6764 .soft_delete_field_ident
6765 .clone()
6766 .expect("soft_delete_column without ident");
6767 let refuse_soft_delete = refuse("soft_delete_on");
6768 let refuse_restore = refuse("restore_on");
6769 quote! {
6770 pub async fn soft_delete_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6777 &self,
6778 _executor: _E,
6779 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6780 #refuse_soft_delete
6781 let _query = #root::soft_delete::__mark_query(
6782 <Self as #root::core::Model>::SCHEMA,
6783 #col_lit,
6784 #pk_column_lit,
6785 ::core::convert::Into::<#root::core::SqlValue>::into(
6786 ::core::clone::Clone::clone(&self.#pk_ident)
6787 ),
6788 ::core::option::Option::Some(#root::__chrono::Utc::now()),
6789 );
6790 #root::sql::__macro_internals::update_on(_executor, &_query).await
6791 }
6792
6793 pub async fn restore_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6799 &self,
6800 _executor: _E,
6801 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6802 #refuse_restore
6803 let _query = #root::soft_delete::__mark_query(
6804 <Self as #root::core::Model>::SCHEMA,
6805 #col_lit,
6806 #pk_column_lit,
6807 ::core::convert::Into::<#root::core::SqlValue>::into(
6808 ::core::clone::Clone::clone(&self.#pk_ident)
6809 ),
6810 ::core::option::Option::None,
6811 );
6812 #root::sql::__macro_internals::update_on(_executor, &_query).await
6813 }
6814
6815 pub async fn soft_delete(
6829 &self,
6830 pool: &#root::sql::Pool,
6831 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6832 let _now = #root::__chrono::Utc::now();
6833 let _query = #root::soft_delete::__mark_query(
6834 <Self as #root::core::Model>::SCHEMA,
6835 #col_lit,
6836 #pk_column_lit,
6837 ::core::convert::Into::<#root::core::SqlValue>::into(
6838 ::core::clone::Clone::clone(&self.#pk_ident)
6839 ),
6840 ::core::option::Option::Some(_now),
6841 );
6842 #pool_softdelete_run
6843 }
6844
6845 pub async fn restore(
6854 &self,
6855 pool: &#root::sql::Pool,
6856 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6857 let _query = #root::soft_delete::__mark_query(
6858 <Self as #root::core::Model>::SCHEMA,
6859 #col_lit,
6860 #pk_column_lit,
6861 ::core::convert::Into::<#root::core::SqlValue>::into(
6862 ::core::clone::Clone::clone(&self.#pk_ident)
6863 ),
6864 ::core::option::Option::None,
6865 );
6866 #pool_restore_run
6867 }
6868
6869 pub async fn force_delete(
6883 &self,
6884 pool: &#root::sql::Pool,
6885 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6886 Self::delete(self, pool).await
6887 }
6888
6889 pub fn trashed(&self) -> bool {
6898 ::core::option::Option::is_some(&self.#sd_field_ident)
6899 }
6900
6901 pub async fn active(
6914 pool: &#root::sql::Pool,
6915 ) -> ::core::result::Result<
6916 ::std::vec::Vec<Self>,
6917 #root::sql::ExecError,
6918 > {
6919 use #root::sql::FetcherPool as _;
6920 #root::query::QuerySet::<Self>::default()
6921 .active()
6922 .fetch(pool)
6923 .await
6924 }
6925
6926 pub async fn only_trashed(
6938 pool: &#root::sql::Pool,
6939 ) -> ::core::result::Result<
6940 ::std::vec::Vec<Self>,
6941 #root::sql::ExecError,
6942 > {
6943 use #root::sql::FetcherPool as _;
6944 #root::query::QuerySet::<Self>::default()
6945 .only_trashed()
6946 .fetch(pool)
6947 .await
6948 }
6949
6950 pub async fn with_trashed(
6966 pool: &#root::sql::Pool,
6967 ) -> ::core::result::Result<
6968 ::std::vec::Vec<Self>,
6969 #root::sql::ExecError,
6970 > {
6971 use #root::sql::FetcherPool as _;
6972 #root::query::QuerySet::<Self>::default()
6973 .with_trashed()
6974 .fetch(pool)
6975 .await
6976 }
6977
6978 }
6979 } else {
6980 quote!()
6981 };
6982 quote! {
6983
6984
6985 pub async fn delete_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6991 &self,
6992 _executor: _E,
6993 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6994 #refuse_delete
6995 self.__rustango_delete_exec(_executor).await
6996 }
6997
6998 #[doc(hidden)]
6999 pub async fn __rustango_delete_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7000 &self,
7001 _executor: _E,
7002 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
7003 let _query = #root::core::DeleteQuery::by_pk(
7004 <Self as #root::core::Model>::SCHEMA,
7005 #pk_column_lit,
7006 ::core::convert::Into::<#root::core::SqlValue>::into(
7007 ::core::clone::Clone::clone(&self.#pk_ident)
7008 ),
7009 );
7010 #root::sql::__macro_internals::delete_on(_executor, &_query).await
7011 }
7012
7013 #[deprecated(since = "0.62.0", note = "use `delete`")]
7018 pub async fn delete_pool(
7019 &self,
7020 pool: &#root::sql::Pool,
7021 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
7022 self.delete(pool).await
7023 }
7024
7025
7026 #pool_delete_method
7027 #pool_insert_method
7028 #audit_entry_method
7029 #pool_save_method
7030 #refresh_replicate_methods
7031 #tx_delete_method
7032 #tx_insert_method
7033 #tx_save_method
7034 #soft_delete_methods
7035
7036 pub fn is(&self, other: &Self) -> bool {
7045 self.#pk_ident == other.#pk_ident
7046 }
7047
7048 pub fn is_not(&self, other: &Self) -> bool {
7051 self.#pk_ident != other.#pk_ident
7052 }
7053
7054 #[must_use]
7062 pub fn get_key(&self) -> #root::core::SqlValue {
7063 ::core::convert::Into::into(::core::clone::Clone::clone(&self.#pk_ident))
7064 }
7065
7066 }
7067 });
7068
7069 let refuse_insert = refuse("insert_on");
7070 let audited_insert = audited_fields.is_some() && primary_key.is_some();
7073 let insert_pool_body = if audited_fields.is_some() {
7074 quote! {
7075 ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
7076 table: <Self as #root::core::Model>::SCHEMA.table,
7077 })
7078 }
7079 } else {
7080 quote!(self.__rustango_insert_exec(pool).await)
7081 };
7082 let plain_insert_auto = if audited_insert {
7083 quote!()
7084 } else {
7085 quote! {
7086 pub async fn insert(
7092 &mut self,
7093 pool: &#root::sql::Pool,
7094 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7095 #insert_pool_body
7096 }
7097 }
7098 };
7099 let plain_insert_plain = if audited_insert {
7100 quote!()
7101 } else {
7102 quote! {
7103 pub async fn insert(
7108 &self,
7109 pool: &#root::sql::Pool,
7110 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7111 #insert_pool_body
7112 }
7113 }
7114 };
7115 let insert_method = if fields.has_auto {
7116 let pushes = &fields.insert_pushes;
7117 let returning_cols = &fields.returning_cols;
7118 quote! {
7119 #plain_insert_auto
7120 pub async fn insert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7126 &mut self,
7127 _executor: _E,
7128 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7129 #refuse_insert
7130 self.__rustango_insert_exec(_executor).await
7131 }
7132
7133 #[doc(hidden)]
7134 pub async fn __rustango_insert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7135 &mut self,
7136 _executor: _E,
7137 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7138 let _query = self.__rustango_insert_query();
7139 if _query.returning.is_empty() {
7141 return #root::sql::__macro_internals::insert_on(_executor, &_query).await;
7142 }
7143 let _row = #root::sql::__macro_internals::insert_returning_on(
7144 _executor, &_query,
7145 )
7146 .await?;
7147 #root::sql::apply_auto_pk(_row, self)
7148 }
7149
7150 #[doc(hidden)]
7152 pub fn __rustango_insert_query(&mut self) -> #root::core::InsertQuery {
7153 let mut _columns: ::std::vec::Vec<&'static str> = ::std::vec::Vec::new();
7154 let mut _values: ::std::vec::Vec<#root::core::SqlValue> = ::std::vec::Vec::new();
7155 #( #pushes )*
7156 #root::core::InsertQuery::new(
7157 <Self as #root::core::Model>::SCHEMA,
7158 _columns,
7159 _values,
7160 )
7161 .returning(::std::vec![ #( #returning_cols ),* ])
7162 }
7163
7164 #[deprecated(since = "0.62.0", note = "use `insert`")]
7169 pub async fn insert_pool(
7170 &mut self,
7171 pool: &#root::sql::Pool,
7172 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7173 self.insert(pool).await
7174 }
7175 }
7176 } else {
7177 let insert_columns = &fields.insert_columns;
7178 let insert_values = &fields.insert_values;
7179 let pool_self = if audited_fields.is_some() {
7181 quote!(&mut self)
7182 } else {
7183 quote!(&self)
7184 };
7185 quote! {
7186 #plain_insert_plain
7187 pub async fn insert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7193 &self,
7194 _executor: _E,
7195 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7196 #refuse_insert
7197 self.__rustango_insert_exec(_executor).await
7198 }
7199
7200 #[doc(hidden)]
7201 pub async fn __rustango_insert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7202 &self,
7203 _executor: _E,
7204 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7205 #root::sql::__macro_internals::insert_on(_executor, &self.__rustango_insert_query())
7206 .await
7207 }
7208
7209 #[doc(hidden)]
7211 pub fn __rustango_insert_query(&self) -> #root::core::InsertQuery {
7212 #root::core::InsertQuery::new(
7213 <Self as #root::core::Model>::SCHEMA,
7214 ::std::vec![ #( #insert_columns ),* ],
7215 ::std::vec![ #( #insert_values ),* ],
7216 )
7217 }
7218
7219 #[deprecated(since = "0.62.0", note = "use `insert`")]
7224 pub async fn insert_pool(
7225 #pool_self,
7226 pool: &#root::sql::Pool,
7227 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7228 self.insert(pool).await
7229 }
7230 }
7231 };
7232
7233 let bulk_pool_body = if audited_fields.is_some() {
7236 quote! {
7237 let mut _tx = #root::sql::write_transaction(pool).await?;
7238 Self::__rustango_bulk_insert_exec(rows, &mut _tx).await?;
7239 if rows.iter().any(|_row| _row.__rustango_audit_pk().is_empty()) {
7241 return ::core::result::Result::Err(#root::sql::ExecError::AuditUnsupported {
7242 table: <Self as #root::core::Model>::SCHEMA.table,
7243 reason: "MySQL has no RETURNING to read the written rows",
7244 });
7245 }
7246 let _entries: ::std::vec::Vec<#root::audit::PendingEntry> = rows
7247 .iter()
7248 .map(|_row| _row.__rustango_audit_entry(#root::audit::AuditOp::Create))
7249 .collect();
7250 #root::audit::__emit_many_tx(&mut _tx, &_entries).await?;
7251 _tx.commit().await?;
7252 ::core::result::Result::Ok(())
7253 }
7254 } else {
7255 quote!(Self::__rustango_bulk_insert_exec(rows, pool).await)
7256 };
7257 let refuse_bulk = refuse("bulk_insert_on");
7258 let bulk_insert_method = if fields.has_auto {
7259 let cols_no_auto = &fields.bulk_columns_no_auto;
7260 let cols_all = &fields.bulk_columns_all;
7261 let pushes_no_auto = &fields.bulk_pushes_no_auto;
7262 let pushes_all = &fields.bulk_pushes_all;
7263 let returning_cols = &fields.returning_cols;
7264 let uniformity = &fields.bulk_auto_uniformity;
7265 let fill_in_place = &fields.bulk_fill_in_place;
7266 let first_auto_ident = fields
7268 .first_db_auto
7269 .as_ref()
7270 .map(|(ident, _)| ident)
7271 .or(fields.first_auto_ident.as_ref())
7272 .expect("has_auto implies first_auto_ident is Some");
7273 quote! {
7274 pub async fn bulk_insert(
7284 rows: &mut [Self],
7285 pool: &#root::sql::Pool,
7286 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7287 #bulk_pool_body
7288 }
7289
7290 pub async fn bulk_insert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7296 rows: &mut [Self],
7297 _executor: _E,
7298 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7299 #refuse_bulk
7300 Self::__rustango_bulk_insert_exec(rows, _executor).await
7301 }
7302
7303 #[doc(hidden)]
7304 pub async fn __rustango_bulk_insert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7305 rows: &mut [Self],
7306 _executor: _E,
7307 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7308 if rows.is_empty() {
7309 return ::core::result::Result::Ok(());
7310 }
7311 let _first_unset = matches!(
7312 rows[0].#first_auto_ident,
7313 #root::sql::Auto::Unset
7314 );
7315 #( #uniformity )*
7316 for _row in rows.iter_mut() {
7317 #( #fill_in_place )*
7318 }
7319 let mut _all_rows: ::std::vec::Vec<
7320 ::std::vec::Vec<#root::core::SqlValue>,
7321 > = ::std::vec::Vec::with_capacity(rows.len());
7322 let _columns: ::std::vec::Vec<&'static str> = if _first_unset {
7323 for _row in rows.iter() {
7324 let mut _row_vals: ::std::vec::Vec<#root::core::SqlValue> =
7325 ::std::vec::Vec::new();
7326 #( #pushes_no_auto )*
7327 _all_rows.push(_row_vals);
7328 }
7329 ::std::vec![ #( #cols_no_auto ),* ]
7330 } else {
7331 for _row in rows.iter() {
7332 let mut _row_vals: ::std::vec::Vec<#root::core::SqlValue> =
7333 ::std::vec::Vec::new();
7334 #( #pushes_all )*
7335 _all_rows.push(_row_vals);
7336 }
7337 ::std::vec![ #( #cols_all ),* ]
7338 };
7339 let _query = #root::core::BulkInsertQuery::new(
7340 <Self as #root::core::Model>::SCHEMA,
7341 _columns,
7342 _all_rows,
7343 )
7344 .returning(::std::vec![ #( #returning_cols ),* ]);
7345 let _returned =
7346 #root::sql::__macro_internals::bulk_insert_on(_executor, &_query).await?;
7347 if _returned.is_empty() {
7348 return ::core::result::Result::Ok(());
7349 }
7350 if _returned.len() != rows.len() {
7351 return ::core::result::Result::Err(#root::sql::ExecError::Sql(
7352 #root::sql::SqlError::BulkInsertReturningMismatch {
7353 expected: rows.len(),
7354 actual: _returned.len(),
7355 },
7356 ));
7357 }
7358 for (_row, _row_mut) in _returned.into_iter().zip(rows.iter_mut()) {
7359 #root::sql::apply_auto_pk(_row, _row_mut)?;
7360 }
7361 ::core::result::Result::Ok(())
7362 }
7363 }
7364 } else {
7365 let cols_all = &fields.bulk_columns_all;
7366 let pushes_all = &fields.bulk_pushes_all;
7367 quote! {
7368 pub async fn bulk_insert(
7375 rows: &[Self],
7376 pool: &#root::sql::Pool,
7377 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7378 #bulk_pool_body
7379 }
7380
7381 pub async fn bulk_insert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7387 rows: &[Self],
7388 _executor: _E,
7389 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7390 #refuse_bulk
7391 Self::__rustango_bulk_insert_exec(rows, _executor).await
7392 }
7393
7394 #[doc(hidden)]
7395 pub async fn __rustango_bulk_insert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7396 rows: &[Self],
7397 _executor: _E,
7398 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7399 if rows.is_empty() {
7400 return ::core::result::Result::Ok(());
7401 }
7402 let mut _all_rows: ::std::vec::Vec<
7403 ::std::vec::Vec<#root::core::SqlValue>,
7404 > = ::std::vec::Vec::with_capacity(rows.len());
7405 for _row in rows.iter() {
7406 let mut _row_vals: ::std::vec::Vec<#root::core::SqlValue> =
7407 ::std::vec::Vec::new();
7408 #( #pushes_all )*
7409 _all_rows.push(_row_vals);
7410 }
7411 let _query = #root::core::BulkInsertQuery::new(
7412 <Self as #root::core::Model>::SCHEMA,
7413 ::std::vec![ #( #cols_all ),* ],
7414 _all_rows,
7415 );
7416 #root::sql::__macro_internals::bulk_insert_on(_executor, &_query)
7417 .await
7418 .map(drop)
7419 }
7420 }
7421 };
7422
7423 let run_conflict_bulk_insert = match (audited_fields.is_some(), primary_key.is_some()) {
7433 (false, _) => quote!(#root::sql::bulk_insert_pool(pool, &_query).await),
7434 (true, true) => quote! {
7435 #root::audit::bulk_insert_with_audit::<Self>(pool, &_query, |_r: &Self, _op| {
7436 _r.__rustango_audit_entry(_op)
7437 })
7438 .await
7439 },
7440 (true, false) => quote! {
7441 ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
7442 table: <Self as #root::core::Model>::SCHEMA.table,
7443 })
7444 },
7445 };
7446 let bulk_upsert_pool_method = {
7447 let (gen_idents, gen_cols): (Vec<_>, Vec<_>) =
7448 fields.uuid_v7_fields.iter().cloned().unzip();
7449 let (upsert_cols, upsert_pushes): (Vec<_>, Vec<_>) = if fields.has_auto {
7452 (
7453 fields.bulk_columns_no_auto.clone(),
7454 fields.bulk_pushes_no_auto.clone(),
7455 )
7456 } else {
7457 (
7458 fields.bulk_columns_all.clone(),
7459 fields.bulk_pushes_all.clone(),
7460 )
7461 };
7462 quote! {
7463 pub async fn bulk_upsert_pool(
7492 rows: &[Self],
7493 target: &[&'static str],
7494 update_cols: &[&'static str],
7495 pool: &#root::sql::Pool,
7496 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7497 if rows.is_empty() {
7498 return ::core::result::Result::Ok(());
7499 }
7500 let mut _all_rows: ::std::vec::Vec<
7501 ::std::vec::Vec<#root::core::SqlValue>,
7502 > = ::std::vec::Vec::with_capacity(rows.len());
7503 for _row in rows.iter() {
7504 let mut _row_vals: ::std::vec::Vec<#root::core::SqlValue> =
7505 ::std::vec::Vec::new();
7506 #( #upsert_pushes )*
7507 _all_rows.push(_row_vals);
7508 }
7509 #[allow(unused_mut)]
7510 let mut _generated: ::std::vec::Vec<&'static str> = ::std::vec::Vec::new();
7511 #(
7512 if rows.iter().all(|_r| matches!(&_r.#gen_idents, #root::sql::Auto::Unset)) {
7513 _generated.push(#gen_cols);
7514 }
7515 )*
7516 let _query = #root::core::BulkInsertQuery::new(
7517 <Self as #root::core::Model>::SCHEMA,
7518 ::std::vec![ #( #upsert_cols ),* ],
7519 _all_rows,
7520 )
7521 .on_conflict_do_update(target, update_cols)
7522 .generated(_generated);
7523 #run_conflict_bulk_insert
7524 }
7525
7526 pub async fn bulk_insert_or_ignore_pool(
7536 rows: &[Self],
7537 pool: &#root::sql::Pool,
7538 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7539 if rows.is_empty() {
7540 return ::core::result::Result::Ok(());
7541 }
7542 let mut _all_rows: ::std::vec::Vec<
7543 ::std::vec::Vec<#root::core::SqlValue>,
7544 > = ::std::vec::Vec::with_capacity(rows.len());
7545 for _row in rows.iter() {
7546 let mut _row_vals: ::std::vec::Vec<#root::core::SqlValue> =
7547 ::std::vec::Vec::new();
7548 #( #upsert_pushes )*
7549 _all_rows.push(_row_vals);
7550 }
7551 let _query = #root::core::BulkInsertQuery::new(
7552 <Self as #root::core::Model>::SCHEMA,
7553 ::std::vec![ #( #upsert_cols ),* ],
7554 _all_rows,
7555 )
7556 .on_conflict_do_nothing();
7557 #run_conflict_bulk_insert
7558 }
7559 }
7560 };
7561
7562 let bulk_update_rows_run = if audited_fields.is_some() {
7571 quote!(#root::audit::bulk_update_with_audit::<Self>(pool, &_query, |_r: &Self| {
7572 _r.__rustango_audit_entry(#root::audit::AuditOp::Update)
7573 })
7574 .await)
7575 } else {
7576 quote!(#root::sql::bulk_update_pool(pool, &_query).await)
7577 };
7578 let bulk_update_method = match &fields.primary_key {
7579 None => quote! {},
7580 Some((pk_ident, pk_col)) => {
7581 let mut col_arms: Vec<TokenStream2> = Vec::new();
7585 let mut val_arms: Vec<TokenStream2> = Vec::new();
7586 for entry in &fields.column_entries {
7587 if &entry.column == pk_col {
7588 continue;
7589 }
7590 let col = &entry.column;
7591 let ident = &entry.ident;
7592 col_arms.push(quote! { #col => #col, });
7593 val_arms.push(quote! {
7594 #col => _row_vals.push(
7595 ::core::convert::Into::<#root::core::SqlValue>::into(
7596 ::core::clone::Clone::clone(&_o.#ident)
7597 )
7598 ),
7599 });
7600 }
7601 quote! {
7602 pub async fn bulk_update(
7628 objs: &[Self],
7629 fields: &[&str],
7630 pool: &#root::sql::Pool,
7631 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
7632 if objs.is_empty() || fields.is_empty() {
7633 return ::core::result::Result::Ok(0);
7634 }
7635 let _model_name = <Self as #root::core::Model>::SCHEMA.name;
7636 let mut _update_columns: ::std::vec::Vec<&'static str> =
7637 ::std::vec::Vec::with_capacity(fields.len());
7638 for &_f in fields {
7639 let _col: &'static str = match _f {
7640 #pk_col => {
7641 return ::core::result::Result::Err(
7642 ::core::convert::Into::into(
7643 #root::core::QueryError::BulkUpdatePrimaryKey {
7644 model: _model_name,
7645 field: ::std::string::ToString::to_string(_f),
7646 }
7647 )
7648 );
7649 }
7650 #( #col_arms )*
7651 _ => {
7652 return ::core::result::Result::Err(
7653 ::core::convert::Into::into(
7654 #root::core::QueryError::UnknownField {
7655 model: _model_name,
7656 field: ::std::string::ToString::to_string(_f),
7657 }
7658 )
7659 );
7660 }
7661 };
7662 #[allow(unreachable_code)]
7671 _update_columns.push(_col);
7672 }
7673 let mut _rows: ::std::vec::Vec<
7674 ::std::vec::Vec<#root::core::SqlValue>,
7675 > = ::std::vec::Vec::with_capacity(objs.len());
7676 for _o in objs.iter() {
7677 let mut _row_vals: ::std::vec::Vec<#root::core::SqlValue> =
7678 ::std::vec::Vec::with_capacity(fields.len() + 1);
7679 _row_vals.push(
7681 ::core::convert::Into::<#root::core::SqlValue>::into(
7682 ::core::clone::Clone::clone(&_o.#pk_ident)
7683 )
7684 );
7685 for &_f in fields {
7686 match _f {
7687 #( #val_arms )*
7688 _ => {}
7691 }
7692 }
7693 _rows.push(_row_vals);
7694 }
7695 let _query = #root::core::BulkUpdateQuery::new(
7696 <Self as #root::core::Model>::SCHEMA,
7697 _update_columns,
7698 _rows,
7699 );
7700 #bulk_update_rows_run
7701 }
7702 }
7703 }
7704 };
7705
7706 let pk_value_helper = primary_key.map(|(pk_ident, _)| {
7707 quote! {
7708 #[doc(hidden)]
7713 pub fn __rustango_pk_value(&self) -> #root::core::SqlValue {
7714 ::core::convert::Into::<#root::core::SqlValue>::into(
7715 ::core::clone::Clone::clone(&self.#pk_ident)
7716 )
7717 }
7718 }
7719 });
7720
7721 let has_pk_value_impl = primary_key.map(|(pk_ident, _)| {
7722 let column_arms = fields.column_entries.iter().map(|entry| {
7723 let col = &entry.column;
7724 let ident = &entry.ident;
7725 quote! {
7726 #col => ::core::option::Option::Some(
7727 ::core::convert::Into::<#root::core::SqlValue>::into(
7728 ::core::clone::Clone::clone(&self.#ident)
7729 )
7730 ),
7731 }
7732 });
7733 quote! {
7734 impl #root::sql::HasPkValue for #struct_name {
7735 fn __rustango_pk_value_impl(&self) -> #root::core::SqlValue {
7736 ::core::convert::Into::<#root::core::SqlValue>::into(
7737 ::core::clone::Clone::clone(&self.#pk_ident)
7738 )
7739 }
7740 fn __rustango_column_value(
7741 &self,
7742 column: &str,
7743 ) -> ::core::option::Option<#root::core::SqlValue> {
7744 match column {
7745 #( #column_arms )*
7746 _ => ::core::option::Option::None,
7747 }
7748 }
7749 }
7750 }
7751 });
7752
7753 let fk_pk_access_impl = fk_pk_access_impl_tokens(struct_name, &fields.fk_relations);
7754
7755 let assign_auto_pk_pool_impl = {
7761 let auto_assigns = &fields.auto_assigns;
7762 let auto_assigns_sqlite: Vec<TokenStream2> = fields
7768 .auto_field_idents
7769 .iter()
7770 .map(|(ident, column, uuid)| {
7771 if *uuid {
7773 return quote! {
7774 self.#ident = #root::sql::Auto::Set(#root::sql::try_get_flat_sqlite(
7775 _returning_row, #column
7776 )?);
7777 };
7778 }
7779 quote! {
7780 self.#ident = #root::sql::try_get_returning_sqlite(
7781 _returning_row, #column
7782 )?;
7783 }
7784 })
7785 .collect();
7786 let generated_assigns: Vec<TokenStream2> = fields
7790 .generated_field_idents
7791 .iter()
7792 .map(|(ident, column)| {
7793 quote! {
7794 self.#ident = #root::sql::try_get_returning(_returning_row, #column)?;
7795 }
7796 })
7797 .collect();
7798 let generated_assigns_sqlite: Vec<TokenStream2> = fields
7799 .generated_field_idents
7800 .iter()
7801 .map(|(ident, column)| {
7802 quote! {
7803 self.#ident = #root::sql::try_get_returning_sqlite(
7804 _returning_row, #column
7805 )?;
7806 }
7807 })
7808 .collect();
7809 let mysql_body = if let Some((first, value_ty)) = fields.first_db_auto.as_ref() {
7810 quote! {
7828 let _converted = <#value_ty as #root::sql::MysqlAutoIdSet>
7829 ::rustango_from_mysql_auto_id(_id)?;
7830 self.#first = #root::sql::Auto::Set(_converted);
7831 ::core::result::Result::Ok(())
7832 }
7833 } else {
7834 quote! {
7835 let _ = _id;
7836 ::core::result::Result::Ok(())
7837 }
7838 };
7839 quote! {
7840 impl #root::sql::AssignAutoPkPool for #struct_name {
7841 fn __rustango_assign_from_pg_row(
7842 &mut self,
7843 _returning_row: &#root::sql::PgReturningRow,
7844 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7845 #( #auto_assigns )*
7846 #( #generated_assigns )*
7847 ::core::result::Result::Ok(())
7848 }
7849 fn __rustango_assign_from_mysql_id(
7850 &mut self,
7851 _id: i64,
7852 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7853 #mysql_body
7854 }
7855 fn __rustango_assign_from_sqlite_row(
7856 &mut self,
7857 _returning_row: &#root::sql::SqliteReturningRow,
7858 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7859 #( #auto_assigns_sqlite )*
7860 #( #generated_assigns_sqlite )*
7861 ::core::result::Result::Ok(())
7862 }
7863 }
7864 }
7865 };
7866
7867 let from_aliased_row_inits = &fields.from_aliased_row_inits;
7868 let aliased_row_helper = quote! {
7869 #[doc(hidden)]
7875 #[cfg(feature = "postgres")]
7876 pub fn __rustango_from_aliased_row(
7877 row: &#root::sql::sqlx::postgres::PgRow,
7878 prefix: &str,
7879 ) -> ::core::result::Result<Self, #root::sql::sqlx::Error> {
7880 ::core::result::Result::Ok(Self {
7881 #( #from_aliased_row_inits ),*
7882 })
7883 }
7884 };
7885 let from_aliased_row_inits_my = &fields.from_aliased_row_inits_my;
7888 let aliased_row_helper_my = quote! {
7889 #root::__impl_my_aliased_row_decoder!(#struct_name, |row, prefix| {
7890 #( #from_aliased_row_inits_my ),*
7891 });
7892 };
7893
7894 let aliased_row_helper_sqlite = quote! {
7897 #root::__impl_sqlite_aliased_row_decoder!(#struct_name, |row, prefix| {
7898 #( #from_aliased_row_inits ),*
7899 });
7900 };
7901
7902 let load_related_impl = load_related_impl_tokens(struct_name, &fields.fk_relations);
7903 let load_related_impl_my = load_related_impl_my_tokens(struct_name, &fields.fk_relations);
7904 let load_related_impl_sqlite =
7905 load_related_impl_sqlite_tokens(struct_name, &fields.fk_relations);
7906
7907 let extra_manager_fns: Vec<TokenStream2> = manager_fns
7913 .iter()
7914 .map(|fn_ident| {
7915 let model_name_str = struct_name.to_string();
7916 let fn_name_str = fn_ident.to_string();
7917 let doc = format!(
7918 "Custom-named QuerySet accessor for [`{model_name_str}`]. \
7919 Generated by `#[rustango(manager_fn = \"{fn_name_str}\")]` — \
7920 equivalent to `Self::objects()`. Chains with any \
7921 `impl ... for QuerySet<{model_name_str}> {{ ... }}` \
7922 extension methods."
7923 );
7924 quote! {
7925 #[doc = #doc]
7926 #[must_use]
7927 pub fn #fn_ident() -> #root::query::QuerySet<#struct_name> {
7928 #root::query::QuerySet::new()
7929 }
7930 }
7931 })
7932 .collect();
7933
7934 quote! {
7935 impl #struct_name {
7936 #[must_use]
7938 pub fn objects() -> #root::query::QuerySet<#struct_name> {
7939 #root::query::QuerySet::new()
7940 }
7941
7942 #[must_use]
7955 pub fn query() -> #root::query::QuerySet<#struct_name> {
7956 #root::query::QuerySet::new()
7957 }
7958
7959 #( #extra_manager_fns )*
7960
7961 #insert_method
7962
7963 #bulk_insert_method
7964
7965 #bulk_upsert_pool_method
7966
7967 #bulk_update_method
7968
7969 #save_method
7970
7971 #pk_methods
7972
7973 #pk_value_helper
7974
7975 #aliased_row_helper
7976
7977 #column_consts
7978 }
7979
7980 #aliased_row_helper_my
7981
7982 #aliased_row_helper_sqlite
7983
7984 #load_related_impl
7985
7986 #load_related_impl_my
7987
7988 #load_related_impl_sqlite
7989
7990 #has_pk_value_impl
7991
7992 #fk_pk_access_impl
7993
7994 #assign_auto_pk_pool_impl
7995 }
7996}
7997
7998fn column_const_tokens(module_ident: &syn::Ident, entries: &[ColumnEntry]) -> TokenStream2 {
8000 let lines = entries.iter().map(|e| {
8001 let ident = &e.ident;
8002 let col_ty = column_type_ident(ident);
8003 quote! {
8004 #[allow(non_upper_case_globals)]
8005 pub const #ident: #module_ident::#col_ty = #module_ident::#col_ty;
8006 }
8007 });
8008 quote! { #(#lines)* }
8009}
8010
8011fn column_module_tokens(
8014 module_ident: &syn::Ident,
8015 struct_name: &syn::Ident,
8016 entries: &[ColumnEntry],
8017) -> TokenStream2 {
8018 let root = rustango_root();
8019 let items = entries.iter().map(|e| {
8020 let col_ty = column_type_ident(&e.ident);
8021 let value_ty = &e.value_ty;
8022 let name = &e.name;
8023 let column = &e.column;
8024 let field_type_tokens = &e.field_type_tokens;
8025 quote! {
8026 #[derive(::core::clone::Clone, ::core::marker::Copy)]
8027 pub struct #col_ty;
8028
8029 impl #root::core::Column for #col_ty {
8030 type Model = super::#struct_name;
8031 type Value = #value_ty;
8032 const NAME: &'static str = #name;
8033 const COLUMN: &'static str = #column;
8034 const FIELD_TYPE: #root::core::FieldType = #field_type_tokens;
8035 }
8036 }
8037 });
8038 quote! {
8039 #[doc(hidden)]
8040 #[allow(non_camel_case_types, non_snake_case)]
8041 pub mod #module_ident {
8042 #[allow(unused_imports)]
8047 use super::*;
8048 #(#items)*
8049 }
8050 }
8051}
8052
8053fn column_type_ident(field_ident: &syn::Ident) -> syn::Ident {
8054 let name = ident_name(field_ident);
8055 syn::Ident::new(&format!("{name}_col"), field_ident.span())
8056}
8057
8058fn restore_auto_now_unless(
8061 idents: &[syn::Ident],
8062 ret: &TokenStream2,
8063 keep: &TokenStream2,
8064 body: &TokenStream2,
8065) -> TokenStream2 {
8066 if idents.is_empty() {
8067 return body.clone();
8068 }
8069 let prev: Vec<syn::Ident> = (0..idents.len())
8070 .map(|i| quote::format_ident!("__rustango_auto_now_prev_{}", i))
8071 .collect();
8072 quote! {
8073 #( let #prev = ::core::clone::Clone::clone(&self.#idents); )*
8074 let _saved: #ret = async { #body }.await;
8075 if !(#keep) {
8076 #( self.#idents = #prev; )*
8077 }
8078 _saved
8079 }
8080}
8081
8082fn ident_name(ident: &syn::Ident) -> String {
8084 syn::ext::IdentExt::unraw(ident).to_string()
8085}
8086
8087fn ident_from_name(name: &str, span: proc_macro2::Span) -> syn::Ident {
8089 match syn::parse_str::<syn::Ident>(name) {
8090 Ok(mut ident) => {
8091 ident.set_span(span);
8092 ident
8093 }
8094 Err(_) => syn::Ident::new_raw(name, span),
8095 }
8096}
8097
8098fn column_module_ident(struct_name: &syn::Ident) -> syn::Ident {
8099 syn::Ident::new(
8100 &format!("__rustango_cols_{struct_name}"),
8101 struct_name.span(),
8102 )
8103}
8104
8105fn from_row_impl_tokens(
8106 struct_name: &syn::Ident,
8107 from_row_inits: &[TokenStream2],
8108 from_row_inits_my: &[TokenStream2],
8109) -> TokenStream2 {
8110 let root = rustango_root();
8111 quote! {
8122 #[cfg(feature = "postgres")]
8123 impl<'r> #root::sql::sqlx::FromRow<'r, #root::sql::sqlx::postgres::PgRow>
8124 for #struct_name
8125 {
8126 fn from_row(
8127 row: &'r #root::sql::sqlx::postgres::PgRow,
8128 ) -> ::core::result::Result<Self, #root::sql::sqlx::Error> {
8129 ::core::result::Result::Ok(Self {
8130 #( #from_row_inits ),*
8131 })
8132 }
8133 }
8134
8135 #root::__impl_my_from_row!(#struct_name, |row| {
8136 #( #from_row_inits_my ),*
8137 });
8138
8139 #root::__impl_sqlite_from_row!(#struct_name, |row| {
8140 #( #from_row_inits ),*
8141 });
8142 }
8143}
8144
8145struct ContainerAttrs {
8146 table: Option<String>,
8147 display: Option<(String, proc_macro2::Span)>,
8148 app: Option<String>,
8155 admin: Option<AdminAttrs>,
8160 audit: Option<AuditAttrs>,
8166 permissions: bool,
8170 m2m: Vec<M2MAttr>,
8174 generic_m2m: Vec<GenericM2MAttr>,
8179 indexes: Vec<IndexAttr>,
8185 checks: Vec<CheckAttr>,
8188 excludes: Vec<ExcludeAttr>,
8194 composite_fks: Vec<CompositeFkAttr>,
8198 generic_fks: Vec<GenericFkAttr>,
8202 scope: Option<String>,
8208 manager_fns: Vec<syn::Ident>,
8213 default_order: Vec<(String, bool, proc_macro2::Span)>,
8219 is_view: bool,
8224 managed: bool,
8230 base_manager_name: Option<String>,
8234 order_with_respect_to: Option<String>,
8239 proxy: bool,
8245 required_db_features: Vec<String>,
8250 required_db_vendor: Option<String>,
8256 default_related_name: Option<String>,
8262 db_table_comment: Option<String>,
8267 get_latest_by: Option<(String, bool, proc_macro2::Span)>,
8273 extra_permissions: Vec<(String, String)>,
8278 default_permissions: Vec<String>,
8285 verbose_name: Option<String>,
8291 verbose_name_plural: Option<String>,
8295 global_scopes: Vec<GlobalScopeAttr>,
8301 through_relations: Vec<ThroughAttr>,
8310 reverse_has_relations: Vec<ReverseHasAttr>,
8319 generic_has_relations: Vec<GenericHasAttr>,
8324}
8325
8326struct GlobalScopeAttr {
8332 name: String,
8333 apply: syn::Path,
8334}
8335
8336struct ThroughAttr {
8366 name: String,
8368 far: syn::Ident,
8372 far_fk_column: String,
8378 intermediate: syn::Ident,
8382 intermediate_fk_column: String,
8386 intermediate_pk_column: String,
8391}
8392
8393struct ReverseHasAttr {
8432 name: String,
8435 child: syn::Ident,
8439 child_fk_column: String,
8443 self_pk_column: Option<String>,
8446 self_pk_span: Option<proc_macro2::Span>,
8448}
8449
8450impl ReverseHasAttr {
8451 fn self_pk_column(&self) -> &str {
8452 self.self_pk_column.as_deref().unwrap_or("id")
8453 }
8454}
8455
8456struct GenericHasAttr {
8462 name: String,
8464 child: syn::Ident,
8467 ct_column: String,
8470 pk_column: String,
8473 self_pk_column: Option<String>,
8476 self_pk_span: Option<proc_macro2::Span>,
8478}
8479
8480impl GenericHasAttr {
8481 fn self_pk_column(&self) -> &str {
8482 self.self_pk_column.as_deref().unwrap_or("id")
8483 }
8484}
8485
8486struct IndexAttr {
8488 span: proc_macro2::Span,
8490 name: Option<String>,
8492 columns: Vec<String>,
8494 unique: bool,
8496 method: String,
8502 where_clause: Option<String>,
8506 include: Vec<String>,
8509}
8510
8511struct CheckAttr {
8513 name: String,
8514 expr: String,
8515}
8516
8517struct ExcludeAttr {
8522 name: String,
8524 using: String,
8526 elements: Vec<(String, String)>,
8529 where_clause: Option<String>,
8531}
8532
8533struct CompositeFkAttr {
8539 name: String,
8541 to: String,
8543 from: Vec<String>,
8545 on: Vec<String>,
8547}
8548
8549struct GenericFkAttr {
8554 name: String,
8556 ct_column: String,
8558 pk_column: String,
8560}
8561
8562struct M2MAttr {
8564 name: String,
8566 to: String,
8568 through: String,
8570 src: String,
8572 dst: String,
8574 auto_create: bool,
8579}
8580
8581struct GenericM2MAttr {
8586 name: String,
8588 through: String,
8590 pk_column: String,
8592 ct_column: String,
8595 related_column: String,
8597}
8598
8599#[derive(Default)]
8605struct AuditAttrs {
8606 track: Option<(Vec<String>, proc_macro2::Span)>,
8610}
8611
8612#[derive(Default)]
8617struct AdminAttrs {
8618 list_display: Option<(Vec<String>, proc_macro2::Span)>,
8619 search_fields: Option<(Vec<String>, proc_macro2::Span)>,
8620 list_per_page: Option<usize>,
8621 ordering: Option<(Vec<(String, bool)>, proc_macro2::Span)>,
8622 readonly_fields: Option<(Vec<String>, proc_macro2::Span)>,
8623 list_filter: Option<(Vec<String>, proc_macro2::Span)>,
8624 actions: Option<(Vec<String>, proc_macro2::Span)>,
8627 fieldsets: Option<(Vec<(String, Vec<String>)>, proc_macro2::Span)>,
8631 list_display_links: Option<(Vec<String>, proc_macro2::Span)>,
8635 search_help_text: Option<String>,
8639 actions_on_top: Option<bool>,
8642 actions_on_bottom: Option<bool>,
8646 date_hierarchy: Option<String>,
8651 prepopulated_fields: Option<(Vec<(String, Vec<String>)>, proc_macro2::Span)>,
8657 raw_id_fields: Option<(Vec<String>, proc_macro2::Span)>,
8661 autocomplete_fields: Option<(Vec<String>, proc_macro2::Span)>,
8666 list_select_related: Option<String>,
8671 formfield_overrides: Option<(Vec<(String, String)>, proc_macro2::Span)>,
8677}
8678
8679#[derive(Clone, Copy, PartialEq, Eq)]
8682enum ModelAttrName {
8683 Rustango,
8684 Rgo,
8685}
8686
8687impl ModelAttrName {
8688 fn of(attr: &syn::Attribute) -> Option<Self> {
8689 let path = attr.path();
8690 if path.is_ident("rustango") {
8691 Some(Self::Rustango)
8692 } else if path.is_ident("rgo") {
8693 Some(Self::Rgo)
8694 } else {
8695 None
8696 }
8697 }
8698
8699 fn as_str(self) -> &'static str {
8700 match self {
8701 Self::Rustango => "rustango",
8702 Self::Rgo => "rgo",
8703 }
8704 }
8705}
8706
8707fn model_attrs(attrs: &[syn::Attribute]) -> syn::Result<Vec<&syn::Attribute>> {
8710 let mut first: Option<ModelAttrName> = None;
8711 let mut out = Vec::new();
8712 for attr in attrs {
8713 let Some(name) = ModelAttrName::of(attr) else {
8714 continue;
8715 };
8716 match first {
8717 Some(seen) if seen != name => {
8718 return Err(syn::Error::new_spanned(
8719 attr.path(),
8720 format!(
8721 "`#[{}(...)]` and `#[{}(...)]` are the same attribute; \
8722 use one name on an item or field",
8723 seen.as_str(),
8724 name.as_str(),
8725 ),
8726 ));
8727 }
8728 _ => first = Some(name),
8729 }
8730 out.push(attr);
8731 }
8732 Ok(out)
8733}
8734
8735fn parse_container_attrs(input: &DeriveInput) -> syn::Result<ContainerAttrs> {
8736 let mut out = ContainerAttrs {
8737 table: None,
8738 display: None,
8739 app: None,
8740 admin: None,
8741 audit: None,
8742 permissions: true,
8751 m2m: Vec::new(),
8752 generic_m2m: Vec::new(),
8753 indexes: Vec::new(),
8754 checks: Vec::new(),
8755 excludes: Vec::new(),
8756 composite_fks: Vec::new(),
8757 generic_fks: Vec::new(),
8758 scope: None,
8759 manager_fns: Vec::new(),
8760 default_order: Vec::new(),
8761 is_view: false,
8762 managed: true,
8763 verbose_name: None,
8764 verbose_name_plural: None,
8765 base_manager_name: None,
8766 order_with_respect_to: None,
8767 proxy: false,
8768 required_db_features: Vec::new(),
8769 required_db_vendor: None,
8770 default_related_name: None,
8771 db_table_comment: None,
8772 get_latest_by: None,
8773 extra_permissions: Vec::new(),
8774 default_permissions: Vec::new(),
8775 global_scopes: Vec::new(),
8776 through_relations: Vec::new(),
8777 reverse_has_relations: Vec::new(),
8778 generic_has_relations: Vec::new(),
8779 };
8780 for attr in model_attrs(&input.attrs)? {
8781 let written = ModelAttrName::of(attr).map_or("rustango", ModelAttrName::as_str);
8783 attr.parse_nested_meta(|meta| {
8784 if meta.path.is_ident("table") {
8785 let s: LitStr = meta.value()?.parse()?;
8786 let name = s.value();
8787 validate_table_name(&name, s.span())?;
8797 out.table = Some(name);
8798 return Ok(());
8799 }
8800 if meta.path.is_ident("display") {
8801 let s: LitStr = meta.value()?.parse()?;
8802 out.display = Some((s.value(), s.span()));
8803 return Ok(());
8804 }
8805 if meta.path.is_ident("app") {
8806 let s: LitStr = meta.value()?.parse()?;
8807 out.app = Some(s.value());
8808 return Ok(());
8809 }
8810 if meta.path.is_ident("scope") {
8811 let s: LitStr = meta.value()?.parse()?;
8812 let val = s.value();
8813 if !matches!(val.to_ascii_lowercase().as_str(), "registry" | "tenant") {
8814 return Err(meta.error(format!(
8815 "`scope` must be \"registry\" or \"tenant\", got {val:?}"
8816 )));
8817 }
8818 out.scope = Some(val);
8819 return Ok(());
8820 }
8821 if meta.path.is_ident("admin") {
8822 let mut admin = AdminAttrs::default();
8823 meta.parse_nested_meta(|inner| {
8824 if inner.path.is_ident("list_display") {
8825 let s: LitStr = inner.value()?.parse()?;
8826 admin.list_display =
8827 Some((split_field_list(&s.value()), s.span()));
8828 return Ok(());
8829 }
8830 if inner.path.is_ident("search_fields") {
8831 let s: LitStr = inner.value()?.parse()?;
8832 admin.search_fields =
8833 Some((split_field_list(&s.value()), s.span()));
8834 return Ok(());
8835 }
8836 if inner.path.is_ident("readonly_fields") {
8837 let s: LitStr = inner.value()?.parse()?;
8838 admin.readonly_fields =
8839 Some((split_field_list(&s.value()), s.span()));
8840 return Ok(());
8841 }
8842 if inner.path.is_ident("list_per_page") {
8843 let lit: syn::LitInt = inner.value()?.parse()?;
8844 admin.list_per_page = Some(lit.base10_parse::<usize>()?);
8845 return Ok(());
8846 }
8847 if inner.path.is_ident("ordering") {
8848 let s: LitStr = inner.value()?.parse()?;
8849 admin.ordering = Some((
8850 parse_ordering_list(&s.value()),
8851 s.span(),
8852 ));
8853 return Ok(());
8854 }
8855 if inner.path.is_ident("list_filter") {
8856 let s: LitStr = inner.value()?.parse()?;
8857 admin.list_filter =
8858 Some((split_field_list(&s.value()), s.span()));
8859 return Ok(());
8860 }
8861 if inner.path.is_ident("actions") {
8862 let s: LitStr = inner.value()?.parse()?;
8863 admin.actions =
8864 Some((split_field_list(&s.value()), s.span()));
8865 return Ok(());
8866 }
8867 if inner.path.is_ident("fieldsets") {
8868 let s: LitStr = inner.value()?.parse()?;
8869 admin.fieldsets =
8870 Some((parse_fieldset_list(&s.value()), s.span()));
8871 return Ok(());
8872 }
8873 if inner.path.is_ident("list_display_links") {
8874 let s: LitStr = inner.value()?.parse()?;
8875 admin.list_display_links =
8876 Some((split_field_list(&s.value()), s.span()));
8877 return Ok(());
8878 }
8879 if inner.path.is_ident("search_help_text") {
8880 let s: LitStr = inner.value()?.parse()?;
8881 admin.search_help_text = Some(s.value());
8882 return Ok(());
8883 }
8884 if inner.path.is_ident("actions_on_top") {
8885 let lit: syn::LitBool = inner.value()?.parse()?;
8886 admin.actions_on_top = Some(lit.value);
8887 return Ok(());
8888 }
8889 if inner.path.is_ident("actions_on_bottom") {
8890 let lit: syn::LitBool = inner.value()?.parse()?;
8891 admin.actions_on_bottom = Some(lit.value);
8892 return Ok(());
8893 }
8894 if inner.path.is_ident("date_hierarchy") {
8895 let s: LitStr = inner.value()?.parse()?;
8896 admin.date_hierarchy = Some(s.value());
8897 return Ok(());
8898 }
8899 if inner.path.is_ident("prepopulated_fields") {
8900 let s: LitStr = inner.value()?.parse()?;
8901 admin.prepopulated_fields =
8902 Some((parse_prepopulated_list(&s.value()), s.span()));
8903 return Ok(());
8904 }
8905 if inner.path.is_ident("raw_id_fields") {
8906 let s: LitStr = inner.value()?.parse()?;
8907 admin.raw_id_fields =
8908 Some((split_field_list(&s.value()), s.span()));
8909 return Ok(());
8910 }
8911 if inner.path.is_ident("autocomplete_fields") {
8912 let s: LitStr = inner.value()?.parse()?;
8913 admin.autocomplete_fields =
8914 Some((split_field_list(&s.value()), s.span()));
8915 return Ok(());
8916 }
8917 if inner.path.is_ident("list_select_related") {
8918 let s: LitStr = inner.value()?.parse()?;
8919 admin.list_select_related = Some(s.value());
8920 return Ok(());
8921 }
8922 if inner.path.is_ident("formfield_overrides") {
8923 let s: LitStr = inner.value()?.parse()?;
8924 admin.formfield_overrides =
8925 Some((parse_formfield_overrides(&s.value()), s.span()));
8926 return Ok(());
8927 }
8928 Err(inner.error(
8929 "unknown admin attribute (supported: \
8930 `list_display`, `list_display_links`, \
8931 `search_fields`, `search_help_text`, \
8932 `readonly_fields`, \
8933 `list_filter`, `list_per_page`, `ordering`, `actions`, \
8934 `actions_on_top`, `actions_on_bottom`, \
8935 `date_hierarchy`, \
8936 `prepopulated_fields`, \
8937 `raw_id_fields`, \
8938 `autocomplete_fields`, \
8939 `list_select_related`, \
8940 `formfield_overrides`, \
8941 `fieldsets`)",
8942 ))
8943 })?;
8944 out.admin = Some(admin);
8945 return Ok(());
8946 }
8947 if meta.path.is_ident("manager") {
8948 return Err(meta.error(
8950 "`manager(ext = ...)` was removed (#2132): declare your own \
8951 `trait FooManagerExt: Sized { … }` and implement it for `QuerySet<Foo>`",
8952 ));
8953 }
8954 if meta.path.is_ident("manager_fn") {
8955 let s: LitStr = meta.value()?.parse()?;
8960 let name = s.value();
8961 if name.is_empty() {
8962 return Err(meta.error("`manager_fn = \"...\"` cannot be empty"));
8963 }
8964 if name == "objects" {
8965 return Err(meta.error(
8966 "`manager_fn = \"objects\"` collides with the default \
8967 accessor — pick a different name",
8968 ));
8969 }
8970 let ident = syn::Ident::new(&name, s.span());
8971 if out.manager_fns.iter().any(|prev| *prev == ident) {
8972 return Err(meta.error(format!(
8973 "duplicate `manager_fn = \"{name}\"`"
8974 )));
8975 }
8976 out.manager_fns.push(ident);
8977 return Ok(());
8978 }
8979 if meta.path.is_ident("default_order") {
8980 let s: LitStr = meta.value()?.parse()?;
8985 let raw = s.value();
8986 let span = s.span();
8987 let mut parsed: Vec<(String, bool, proc_macro2::Span)> =
8988 Vec::new();
8989 for entry in raw.split(',') {
8990 let trimmed = entry.trim();
8991 if trimmed.is_empty() {
8992 return Err(syn::Error::new(
8993 span,
8994 "`default_order = \"...\"` has an empty entry — \
8995 check for a stray comma",
8996 ));
8997 }
8998 let (desc, name) = if let Some(rest) = trimmed.strip_prefix('-') {
8999 (true, rest.trim().to_owned())
9000 } else if let Some(rest) = trimmed.strip_prefix('+') {
9001 (false, rest.trim().to_owned())
9002 } else {
9003 (false, trimmed.to_owned())
9004 };
9005 if name.is_empty() {
9006 return Err(syn::Error::new(
9007 span,
9008 "`default_order` entry has no column name after the prefix",
9009 ));
9010 }
9011 if parsed.iter().any(|(n, _, _)| *n == name) {
9012 return Err(syn::Error::new(
9013 span,
9014 format!("duplicate column `{name}` in `default_order`"),
9015 ));
9016 }
9017 parsed.push((name, desc, span));
9018 }
9019 if parsed.is_empty() {
9020 return Err(syn::Error::new(
9021 span,
9022 "`default_order = \"...\"` cannot be empty",
9023 ));
9024 }
9025 out.default_order = parsed;
9026 return Ok(());
9027 }
9028 if meta.path.is_ident("global_scope") {
9029 let span = meta.path.span();
9035 let mut scope_name: Option<String> = None;
9036 let mut apply_path: Option<syn::Path> = None;
9037 meta.parse_nested_meta(|inner| {
9038 if inner.path.is_ident("name") {
9039 let s: LitStr = inner.value()?.parse()?;
9040 let raw = s.value();
9041 if raw.trim().is_empty() {
9042 return Err(syn::Error::new(
9043 s.span(),
9044 "`global_scope(name = \"...\")` must not be empty",
9045 ));
9046 }
9047 scope_name = Some(raw);
9048 return Ok(());
9049 }
9050 if inner.path.is_ident("apply") {
9051 let p: syn::Path = inner.value()?.parse()?;
9052 apply_path = Some(p);
9053 return Ok(());
9054 }
9055 Err(inner.error(
9056 "unknown `global_scope` attribute (supported: \
9057 `name`, `apply`)",
9058 ))
9059 })?;
9060 let Some(name) = scope_name else {
9061 return Err(syn::Error::new(
9062 span,
9063 "`global_scope` requires `name = \"...\"`",
9064 ));
9065 };
9066 let Some(apply) = apply_path else {
9067 return Err(syn::Error::new(
9068 span,
9069 "`global_scope` requires `apply = fn_path`",
9070 ));
9071 };
9072 if out.global_scopes.iter().any(|s| s.name == name) {
9073 return Err(syn::Error::new(
9074 span,
9075 format!(
9076 "duplicate global scope name `{name}` — \
9077 pick a unique identifier so \
9078 `QuerySet::without_global_scope(\"{name}\")` \
9079 is unambiguous"
9080 ),
9081 ));
9082 }
9083 out.global_scopes.push(GlobalScopeAttr { name, apply });
9084 return Ok(());
9085 }
9086 if meta.path.is_ident("through") {
9087 let span = meta.path.span();
9092 let mut accessor_name: Option<String> = None;
9093 let mut far_ident: Option<syn::Ident> = None;
9094 let mut far_fk_column: Option<String> = None;
9095 let mut intermediate_ident: Option<syn::Ident> = None;
9096 let mut intermediate_fk_column: Option<String> = None;
9097 let mut intermediate_pk_column: Option<String> = None;
9098 fn parse_nonempty_string(
9099 inner: &syn::meta::ParseNestedMeta<'_>,
9100 field: &str,
9101 ) -> syn::Result<String> {
9102 let s: LitStr = inner.value()?.parse()?;
9103 let raw = s.value();
9104 let trimmed = raw.trim();
9105 if trimmed.is_empty() {
9106 return Err(syn::Error::new(
9107 s.span(),
9108 format!("`through({field} = \"...\")` must not be empty"),
9109 ));
9110 }
9111 Ok(trimmed.to_owned())
9112 }
9113 meta.parse_nested_meta(|inner| {
9114 if inner.path.is_ident("name") {
9115 accessor_name = Some(parse_nonempty_string(&inner, "name")?);
9116 return Ok(());
9117 }
9118 if inner.path.is_ident("far") {
9119 let s: LitStr = inner.value()?.parse()?;
9125 let raw = s.value();
9126 let trimmed = raw.trim();
9127 if trimmed.is_empty() {
9128 return Err(syn::Error::new(
9129 s.span(),
9130 "`through(far = \"...\")` must not be empty",
9131 ));
9132 }
9133 far_ident = Some(syn::Ident::new(trimmed, s.span()));
9134 return Ok(());
9135 }
9136 if inner.path.is_ident("far_fk_column") {
9137 far_fk_column =
9138 Some(parse_nonempty_string(&inner, "far_fk_column")?);
9139 return Ok(());
9140 }
9141 if inner.path.is_ident("intermediate") {
9142 let s: LitStr = inner.value()?.parse()?;
9143 let raw = s.value();
9144 let trimmed = raw.trim();
9145 if trimmed.is_empty() {
9146 return Err(syn::Error::new(
9147 s.span(),
9148 "`through(intermediate = \"...\")` must not be empty",
9149 ));
9150 }
9151 intermediate_ident = Some(syn::Ident::new(trimmed, s.span()));
9152 return Ok(());
9153 }
9154 if inner.path.is_ident("intermediate_fk_column") {
9155 intermediate_fk_column =
9156 Some(parse_nonempty_string(&inner, "intermediate_fk_column")?);
9157 return Ok(());
9158 }
9159 if inner.path.is_ident("intermediate_pk_column") {
9160 intermediate_pk_column =
9161 Some(parse_nonempty_string(&inner, "intermediate_pk_column")?);
9162 return Ok(());
9163 }
9164 Err(inner.error(
9165 "unknown `through` attribute (supported: \
9166 `name`, `far`, `far_fk_column`, \
9167 `intermediate`, `intermediate_fk_column`, \
9168 `intermediate_pk_column`)",
9169 ))
9170 })?;
9171 let Some(name) = accessor_name else {
9172 return Err(syn::Error::new(
9173 span,
9174 "`through` requires `name = \"...\"`",
9175 ));
9176 };
9177 let Some(far) = far_ident else {
9178 return Err(syn::Error::new(
9179 span,
9180 "`through` requires `far = \"FarModelType\"`",
9181 ));
9182 };
9183 let Some(far_fk_column) = far_fk_column else {
9184 return Err(syn::Error::new(
9185 span,
9186 "`through` requires `far_fk_column = \"<column>\"`",
9187 ));
9188 };
9189 let Some(intermediate) = intermediate_ident else {
9190 return Err(syn::Error::new(
9191 span,
9192 "`through` requires `intermediate = \"IntermediateModelType\"`",
9193 ));
9194 };
9195 let Some(intermediate_fk_column) = intermediate_fk_column else {
9196 return Err(syn::Error::new(
9197 span,
9198 "`through` requires `intermediate_fk_column = \"<column>\"`",
9199 ));
9200 };
9201 let intermediate_pk_column =
9202 intermediate_pk_column.unwrap_or_else(|| "id".to_owned());
9203 if out.through_relations.iter().any(|t| t.name == name) {
9204 return Err(syn::Error::new(
9205 span,
9206 format!(
9207 "duplicate `through(name = \"{name}\")` — \
9208 pick a unique accessor name"
9209 ),
9210 ));
9211 }
9212 out.through_relations.push(ThroughAttr {
9213 name,
9214 far,
9215 far_fk_column,
9216 intermediate,
9217 intermediate_fk_column,
9218 intermediate_pk_column,
9219 });
9220 return Ok(());
9221 }
9222 if meta.path.is_ident("reverse_has") {
9223 let span = meta.path.span();
9227 let mut accessor_name: Option<String> = None;
9228 let mut child_ident: Option<syn::Ident> = None;
9229 let mut child_fk_column: Option<String> = None;
9230 let mut self_pk_column: Option<String> = None;
9231 let mut self_pk_span: Option<proc_macro2::Span> = None;
9232 meta.parse_nested_meta(|inner| {
9233 if inner.path.is_ident("name") {
9234 let s: LitStr = inner.value()?.parse()?;
9235 let raw = s.value();
9236 if raw.trim().is_empty() {
9237 return Err(syn::Error::new(
9238 s.span(),
9239 "`reverse_has(name = \"...\")` must not be empty",
9240 ));
9241 }
9242 accessor_name = Some(raw);
9243 return Ok(());
9244 }
9245 if inner.path.is_ident("child") {
9246 let s: LitStr = inner.value()?.parse()?;
9247 let raw = s.value();
9248 let trimmed = raw.trim();
9249 if trimmed.is_empty() {
9250 return Err(syn::Error::new(
9251 s.span(),
9252 "`reverse_has(child = \"...\")` must not be empty",
9253 ));
9254 }
9255 child_ident = Some(syn::Ident::new(trimmed, s.span()));
9256 return Ok(());
9257 }
9258 if inner.path.is_ident("child_fk_column") {
9259 let s: LitStr = inner.value()?.parse()?;
9260 let raw = s.value();
9261 let trimmed = raw.trim();
9262 if trimmed.is_empty() {
9263 return Err(syn::Error::new(
9264 s.span(),
9265 "`reverse_has(child_fk_column = \"...\")` must not be empty",
9266 ));
9267 }
9268 child_fk_column = Some(trimmed.to_owned());
9269 return Ok(());
9270 }
9271 if inner.path.is_ident("self_pk_column") {
9272 let s: LitStr = inner.value()?.parse()?;
9273 let raw = s.value();
9274 let trimmed = raw.trim();
9275 if trimmed.is_empty() {
9276 return Err(syn::Error::new(
9277 s.span(),
9278 "`reverse_has(self_pk_column = \"...\")` must not be empty",
9279 ));
9280 }
9281 self_pk_column = Some(trimmed.to_owned());
9282 self_pk_span = Some(s.span());
9283 return Ok(());
9284 }
9285 Err(inner.error(
9286 "unknown `reverse_has` attribute (supported: \
9287 `name`, `child`, `child_fk_column`, \
9288 `self_pk_column`)",
9289 ))
9290 })?;
9291 let Some(name) = accessor_name else {
9292 return Err(syn::Error::new(
9293 span,
9294 "`reverse_has` requires `name = \"...\"`",
9295 ));
9296 };
9297 let Some(child) = child_ident else {
9298 return Err(syn::Error::new(
9299 span,
9300 "`reverse_has` requires `child = \"ChildModelType\"`",
9301 ));
9302 };
9303 let Some(child_fk_column) = child_fk_column else {
9304 return Err(syn::Error::new(
9305 span,
9306 "`reverse_has` requires `child_fk_column = \"<column>\"`",
9307 ));
9308 };
9309 if out.reverse_has_relations.iter().any(|r| r.name == name) {
9310 return Err(syn::Error::new(
9311 span,
9312 format!(
9313 "duplicate `reverse_has(name = \"{name}\")` — \
9314 pick a unique accessor name"
9315 ),
9316 ));
9317 }
9318 out.reverse_has_relations.push(ReverseHasAttr {
9319 name,
9320 child,
9321 child_fk_column,
9322 self_pk_column,
9323 self_pk_span,
9324 });
9325 return Ok(());
9326 }
9327 if meta.path.is_ident("generic_has") {
9328 let span = meta.path.span();
9334 let mut accessor_name: Option<String> = None;
9335 let mut child_ident: Option<syn::Ident> = None;
9336 let mut ct_column: Option<String> = None;
9337 let mut pk_column: Option<String> = None;
9338 let mut self_pk_column: Option<String> = None;
9339 let mut self_pk_span: Option<proc_macro2::Span> = None;
9340 meta.parse_nested_meta(|inner| {
9341 if inner.path.is_ident("name") {
9342 let s: LitStr = inner.value()?.parse()?;
9343 let raw = s.value();
9344 if raw.trim().is_empty() {
9345 return Err(syn::Error::new(
9346 s.span(),
9347 "`generic_has(name = \"...\")` must not be empty",
9348 ));
9349 }
9350 accessor_name = Some(raw);
9351 return Ok(());
9352 }
9353 if inner.path.is_ident("child") {
9354 let s: LitStr = inner.value()?.parse()?;
9355 let trimmed = s.value().trim().to_owned();
9356 if trimmed.is_empty() {
9357 return Err(syn::Error::new(
9358 s.span(),
9359 "`generic_has(child = \"...\")` must not be empty",
9360 ));
9361 }
9362 child_ident = Some(syn::Ident::new(&trimmed, s.span()));
9363 return Ok(());
9364 }
9365 if inner.path.is_ident("ct_column") {
9366 let s: LitStr = inner.value()?.parse()?;
9367 let trimmed = s.value().trim().to_owned();
9368 if trimmed.is_empty() {
9369 return Err(syn::Error::new(
9370 s.span(),
9371 "`generic_has(ct_column = \"...\")` must not be empty",
9372 ));
9373 }
9374 ct_column = Some(trimmed);
9375 return Ok(());
9376 }
9377 if inner.path.is_ident("pk_column") {
9378 let s: LitStr = inner.value()?.parse()?;
9379 let trimmed = s.value().trim().to_owned();
9380 if trimmed.is_empty() {
9381 return Err(syn::Error::new(
9382 s.span(),
9383 "`generic_has(pk_column = \"...\")` must not be empty",
9384 ));
9385 }
9386 pk_column = Some(trimmed);
9387 return Ok(());
9388 }
9389 if inner.path.is_ident("self_pk_column") {
9390 let s: LitStr = inner.value()?.parse()?;
9391 let trimmed = s.value().trim().to_owned();
9392 if trimmed.is_empty() {
9393 return Err(syn::Error::new(
9394 s.span(),
9395 "`generic_has(self_pk_column = \"...\")` must not be empty",
9396 ));
9397 }
9398 self_pk_column = Some(trimmed);
9399 self_pk_span = Some(s.span());
9400 return Ok(());
9401 }
9402 Err(inner.error(
9403 "unknown `generic_has` attribute (supported: \
9404 `name`, `child`, `ct_column`, `pk_column`, \
9405 `self_pk_column`)",
9406 ))
9407 })?;
9408 let Some(name) = accessor_name else {
9409 return Err(syn::Error::new(
9410 span,
9411 "`generic_has` requires `name = \"...\"`",
9412 ));
9413 };
9414 let Some(child) = child_ident else {
9415 return Err(syn::Error::new(
9416 span,
9417 "`generic_has` requires `child = \"ChildModelType\"`",
9418 ));
9419 };
9420 let ct_column = ct_column.unwrap_or_else(|| "content_type_id".to_owned());
9421 let pk_column = pk_column.unwrap_or_else(|| "object_pk".to_owned());
9422 if out.generic_has_relations.iter().any(|r| r.name == name) {
9423 return Err(syn::Error::new(
9424 span,
9425 format!(
9426 "duplicate `generic_has(name = \"{name}\")` — \
9427 pick a unique accessor name"
9428 ),
9429 ));
9430 }
9431 out.generic_has_relations.push(GenericHasAttr {
9432 name,
9433 child,
9434 ct_column,
9435 pk_column,
9436 self_pk_column,
9437 self_pk_span,
9438 });
9439 return Ok(());
9440 }
9441 if meta.path.is_ident("audit") {
9442 let mut audit = AuditAttrs::default();
9443 meta.parse_nested_meta(|inner| {
9444 if inner.path.is_ident("track") {
9445 let s: LitStr = inner.value()?.parse()?;
9446 audit.track =
9447 Some((split_field_list(&s.value()), s.span()));
9448 return Ok(());
9449 }
9450 Err(inner.error(
9451 "unknown audit attribute (supported: `track`)",
9452 ))
9453 })?;
9454 out.audit = Some(audit);
9455 return Ok(());
9456 }
9457 if meta.path.is_ident("permissions") {
9458 if let Ok(v) = meta.value() {
9463 let lit: syn::LitBool = v.parse()?;
9464 out.permissions = lit.value;
9465 } else {
9466 out.permissions = true;
9467 }
9468 return Ok(());
9469 }
9470 if meta.path.is_ident("view") {
9471 if let Ok(v) = meta.value() {
9477 let lit: syn::LitBool = v.parse()?;
9478 out.is_view = lit.value;
9479 } else {
9480 out.is_view = true;
9481 }
9482 return Ok(());
9483 }
9484 if meta.path.is_ident("managed") {
9485 let v = meta.value()?;
9493 let lit: syn::LitBool = v.parse()?;
9494 out.managed = lit.value;
9495 return Ok(());
9496 }
9497 if meta.path.is_ident("verbose_name") {
9498 let s: LitStr = meta.value()?.parse()?;
9499 out.verbose_name = Some(s.value());
9500 return Ok(());
9501 }
9502 if meta.path.is_ident("verbose_name_plural") {
9503 let s: LitStr = meta.value()?.parse()?;
9504 out.verbose_name_plural = Some(s.value());
9505 return Ok(());
9506 }
9507 if meta.path.is_ident("db_table_comment") {
9508 let s: LitStr = meta.value()?.parse()?;
9511 out.db_table_comment = Some(s.value());
9512 return Ok(());
9513 }
9514 if meta.path.is_ident("proxy") {
9515 let value = if meta.input.peek(syn::Token![=]) {
9524 meta.value()?.parse::<syn::LitBool>()?.value
9525 } else {
9526 true
9527 };
9528 out.proxy = value;
9529 return Ok(());
9530 }
9531 if meta.path.is_ident("order_with_respect_to") {
9532 let s: LitStr = meta.value()?.parse()?;
9546 let raw = s.value();
9547 if raw.is_empty() {
9548 return Err(syn::Error::new(
9549 s.span(),
9550 "`order_with_respect_to` must be a non-empty FK field name",
9551 ));
9552 }
9553 let valid = raw
9554 .chars()
9555 .all(|c| c == '_' || c.is_ascii_alphanumeric())
9556 && !raw.chars().next().is_some_and(|c| c.is_ascii_digit());
9557 if !valid {
9558 return Err(syn::Error::new(
9559 s.span(),
9560 format!(
9561 "`order_with_respect_to` must be a valid Rust \
9562 identifier (letters / digits / underscores, \
9563 not starting with a digit); got `{raw}`"
9564 ),
9565 ));
9566 }
9567 out.order_with_respect_to = Some(raw);
9568 return Ok(());
9569 }
9570 if meta.path.is_ident("required_db_features") {
9571 let s: LitStr = meta.value()?.parse()?;
9586 let raw = s.value();
9587 let features: Vec<String> = raw
9588 .split(',')
9589 .map(str::trim)
9590 .filter(|s| !s.is_empty())
9591 .map(str::to_owned)
9592 .collect();
9593 if features.is_empty() {
9594 return Err(syn::Error::new(
9595 s.span(),
9596 "`required_db_features` must list at least one \
9597 comma-separated capability token",
9598 ));
9599 }
9600 out.required_db_features = features;
9601 return Ok(());
9602 }
9603 if meta.path.is_ident("required_db_vendor") {
9604 let s: LitStr = meta.value()?.parse()?;
9612 let raw = s.value().to_ascii_lowercase();
9613 match raw.as_str() {
9614 "postgresql" | "postgres" | "pg" => {
9615 out.required_db_vendor = Some("postgres".to_owned());
9616 }
9617 "mysql" | "mariadb" => {
9618 out.required_db_vendor = Some("mysql".to_owned());
9619 }
9620 "sqlite" | "sqlite3" => {
9621 out.required_db_vendor = Some("sqlite".to_owned());
9622 }
9623 _ => {
9624 return Err(syn::Error::new(
9625 s.span(),
9626 format!(
9627 "unknown required_db_vendor `{raw}` — \
9628 expected `postgres` (aliases: `postgresql`, `pg`), \
9629 `mysql` (alias: `mariadb`), or `sqlite` \
9630 (alias: `sqlite3`)"
9631 ),
9632 ));
9633 }
9634 }
9635 return Ok(());
9636 }
9637 if meta.path.is_ident("base_manager_name") {
9638 let s: LitStr = meta.value()?.parse()?;
9647 let raw = s.value();
9648 if raw.is_empty() {
9649 return Err(syn::Error::new(
9650 s.span(),
9651 "`base_manager_name` must be a non-empty string",
9652 ));
9653 }
9654 let valid = raw
9655 .chars()
9656 .all(|c| c == '_' || c.is_ascii_alphanumeric())
9657 && !raw.chars().next().is_some_and(|c| c.is_ascii_digit());
9658 if !valid {
9659 return Err(syn::Error::new(
9660 s.span(),
9661 format!(
9662 "`base_manager_name` must be a valid Rust \
9663 identifier (letters / digits / underscores, \
9664 not starting with a digit); got `{raw}`"
9665 ),
9666 ));
9667 }
9668 out.base_manager_name = Some(raw);
9669 return Ok(());
9670 }
9671 if meta.path.is_ident("default_related_name") {
9672 let s: LitStr = meta.value()?.parse()?;
9683 let raw = s.value();
9684 if raw.is_empty() {
9685 return Err(syn::Error::new(
9686 s.span(),
9687 "`default_related_name` must be a non-empty string",
9688 ));
9689 }
9690 let valid = raw
9691 .chars()
9692 .all(|c| c == '_' || c.is_ascii_lowercase() || c.is_ascii_digit())
9693 && !raw.chars().next().is_some_and(|c| c.is_ascii_digit());
9694 if !valid {
9695 return Err(syn::Error::new(
9696 s.span(),
9697 format!(
9698 "`default_related_name` must be snake_case ASCII \
9699 (lowercase letters / digits / underscores, not \
9700 starting with a digit); got `{raw}`"
9701 ),
9702 ));
9703 }
9704 out.default_related_name = Some(raw);
9705 return Ok(());
9706 }
9707 if meta.path.is_ident("extra_permissions") {
9708 let s: LitStr = meta.value()?.parse()?;
9710 let raw = s.value();
9711 let mut pairs = Vec::new();
9712 for entry in raw.split(',') {
9713 let entry = entry.trim();
9714 if entry.is_empty() {
9715 continue;
9716 }
9717 let (codename, label) = match entry.split_once(':') {
9718 Some((c, l)) => (c.trim().to_owned(), l.trim().to_owned()),
9719 None => (entry.to_owned(), entry.to_owned()),
9720 };
9721 if codename.is_empty() {
9722 return Err(meta.error(
9723 "`extra_permissions` entries must be `codename:label` pairs",
9724 ));
9725 }
9726 pairs.push((codename, label));
9727 }
9728 if pairs.is_empty() {
9729 return Err(meta
9730 .error("`extra_permissions = \"…\"` must list at least one pair"));
9731 }
9732 out.extra_permissions = pairs;
9733 return Ok(());
9734 }
9735 if meta.path.is_ident("default_permissions") {
9736 let s: LitStr = meta.value()?.parse()?;
9739 let raw = s.value();
9740 let mut actions: Vec<String> = Vec::new();
9741 for entry in raw.split(',') {
9742 let action = entry.trim().to_ascii_lowercase();
9743 if action.is_empty() {
9744 continue;
9745 }
9746 match action.as_str() {
9747 "add" | "change" | "delete" | "view" => {}
9748 other => {
9749 return Err(syn::Error::new(
9750 s.span(),
9751 format!(
9752 "unknown default_permissions action `{other}` — \
9753 expected one of `add`, `change`, `delete`, `view`"
9754 ),
9755 ));
9756 }
9757 }
9758 if !actions.contains(&action) {
9759 actions.push(action);
9760 }
9761 }
9762 if actions.is_empty() {
9763 return Err(syn::Error::new(
9764 s.span(),
9765 "`default_permissions = \"…\"` must list at least one action; \
9766 use `permissions = false` on the container if you want NO \
9767 permissions seeded for this model.",
9768 ));
9769 }
9770 out.default_permissions = actions;
9771 return Ok(());
9772 }
9773 if meta.path.is_ident("get_latest_by") {
9774 let s: LitStr = meta.value()?.parse()?;
9776 let raw = s.value();
9777 let trimmed = raw.trim();
9778 if trimmed.is_empty() {
9779 return Err(meta.error("`get_latest_by` must name a column"));
9780 }
9781 let (col, desc) = if let Some(stripped) = trimmed.strip_prefix('-') {
9782 (stripped.to_owned(), true)
9783 } else if let Some(stripped) = trimmed.strip_prefix('+') {
9784 (stripped.to_owned(), false)
9785 } else {
9786 (trimmed.to_owned(), false)
9787 };
9788 if col.is_empty() {
9789 return Err(meta.error("`get_latest_by` must name a column"));
9790 }
9791 out.get_latest_by = Some((col, desc, s.span()));
9792 return Ok(());
9793 }
9794 if meta.path.is_ident("unique_together") {
9795 let (columns, name) = parse_together_attr(&meta, "unique_together", written)?;
9804 out.indexes.push(IndexAttr {
9805 span: meta.path.span(),
9806 name,
9807 columns,
9808 unique: true,
9809 method: "btree".to_owned(),
9810 where_clause: None,
9811 include: Vec::new(),
9812 });
9813 return Ok(());
9814 }
9815 if meta.path.is_ident("index_together") {
9816 let (columns, name) = parse_together_attr(&meta, "index_together", written)?;
9822 out.indexes.push(IndexAttr {
9823 span: meta.path.span(),
9824 name,
9825 columns,
9826 unique: false,
9827 method: "btree".to_owned(),
9828 where_clause: None,
9829 include: Vec::new(),
9830 });
9831 return Ok(());
9832 }
9833 if meta.path.is_ident("unique_when") {
9834 let mut columns: Option<Vec<String>> = None;
9848 let mut condition: Option<String> = None;
9849 let mut name: Option<String> = None;
9850 let mut include: Vec<String> = Vec::new();
9851 meta.parse_nested_meta(|inner| {
9852 if inner.path.is_ident("columns") {
9853 let s: LitStr = inner.value()?.parse()?;
9854 columns = Some(split_field_list(&s.value()));
9855 return Ok(());
9856 }
9857 if inner.path.is_ident("condition") {
9858 let s: LitStr = inner.value()?.parse()?;
9859 condition = Some(s.value());
9860 return Ok(());
9861 }
9862 if inner.path.is_ident("name") {
9863 let s: LitStr = inner.value()?.parse()?;
9864 name = Some(s.value());
9865 return Ok(());
9866 }
9867 if inner.path.is_ident("include") {
9868 let s: LitStr = inner.value()?.parse()?;
9872 include = split_field_list(&s.value());
9873 return Ok(());
9874 }
9875 Err(inner.error(
9876 "unknown unique_when attribute (supported: \
9877 `columns = \"...\"`, `condition = \"...\"`, \
9878 `name = \"...\"`, `include = \"...\"`)",
9879 ))
9880 })?;
9881 let columns = columns.ok_or_else(|| {
9882 meta.error("`unique_when(...)` requires `columns = \"...\"`")
9883 })?;
9884 let condition = condition.ok_or_else(|| {
9885 meta.error("`unique_when(...)` requires `condition = \"...\"`")
9886 })?;
9887 if columns.is_empty() {
9888 return Err(meta.error("`unique_when(columns = \"\")` is empty"));
9889 }
9890 out.indexes.push(IndexAttr {
9891 span: meta.path.span(),
9892 name,
9893 columns,
9894 unique: true,
9895 method: "btree".to_owned(),
9896 where_clause: Some(condition),
9897 include,
9898 });
9899 return Ok(());
9900 }
9901 if meta.path.is_ident("index_when") {
9902 let mut columns: Option<Vec<String>> = None;
9919 let mut condition: Option<String> = None;
9920 let mut name: Option<String> = None;
9921 let mut method: String = "btree".to_owned();
9922 let mut include: Vec<String> = Vec::new();
9923 meta.parse_nested_meta(|inner| {
9924 if inner.path.is_ident("columns") {
9925 let s: LitStr = inner.value()?.parse()?;
9926 columns = Some(split_field_list(&s.value()));
9927 return Ok(());
9928 }
9929 if inner.path.is_ident("condition") {
9930 let s: LitStr = inner.value()?.parse()?;
9931 condition = Some(s.value());
9932 return Ok(());
9933 }
9934 if inner.path.is_ident("name") {
9935 let s: LitStr = inner.value()?.parse()?;
9936 name = Some(s.value());
9937 return Ok(());
9938 }
9939 if inner.path.is_ident("method") {
9940 let s: LitStr = inner.value()?.parse()?;
9941 method = s.value();
9942 return Ok(());
9943 }
9944 if inner.path.is_ident("include") {
9945 let s: LitStr = inner.value()?.parse()?;
9949 include = split_field_list(&s.value());
9950 return Ok(());
9951 }
9952 Err(inner.error(
9953 "unknown index_when attribute (supported: \
9954 `columns = \"...\"`, `condition = \"...\"`, \
9955 `name = \"...\"`, `method = \"btree|gin|gist|...\"`, \
9956 `include = \"...\"`)",
9957 ))
9958 })?;
9959 let columns = columns
9960 .ok_or_else(|| meta.error("`index_when(...)` requires `columns = \"...\"`"))?;
9961 let condition = condition.ok_or_else(|| {
9962 meta.error("`index_when(...)` requires `condition = \"...\"`")
9963 })?;
9964 if columns.is_empty() {
9965 return Err(meta.error("`index_when(columns = \"\")` is empty"));
9966 }
9967 out.indexes.push(IndexAttr {
9968 span: meta.path.span(),
9969 name,
9970 columns,
9971 unique: false,
9972 method,
9973 where_clause: Some(condition),
9974 include,
9975 });
9976 return Ok(());
9977 }
9978 if meta.path.is_ident("index") {
9979 let cols_lit: LitStr;
9990 let mut unique = false;
9991 let mut name: Option<String> = None;
9992 let mut method = "btree".to_owned();
9993 if meta.input.peek(syn::token::Paren) {
9994 let content;
9995 syn::parenthesized!(content in meta.input);
9996 cols_lit = content.parse()?;
9997 while content.peek(syn::Token![,]) {
9998 content.parse::<syn::Token![,]>()?;
9999 if content.is_empty() {
10000 break;
10001 }
10002 let flag: syn::Ident = content.parse()?;
10003 if flag == "unique" {
10004 unique = true;
10005 } else if flag == "name" {
10006 content.parse::<syn::Token![=]>()?;
10007 let s: LitStr = content.parse()?;
10008 name = Some(s.value());
10009 } else if flag == "method" {
10010 content.parse::<syn::Token![=]>()?;
10011 let s: LitStr = content.parse()?;
10012 let v = s.value();
10013 match v.as_str() {
10014 "btree" | "gin" | "gist" | "brin" | "spgist" | "hash"
10015 | "bloom" => method = v,
10016 other => {
10017 return Err(syn::Error::new(
10018 s.span(),
10019 format!("unknown index method `{other}` (supported: btree, gin, gist, brin, spgist, hash, bloom)"),
10020 ));
10021 }
10022 }
10023 } else {
10024 return Err(syn::Error::new(
10025 flag.span(),
10026 "unknown index flag (supported: `unique`, `name`, `method`)",
10027 ));
10028 }
10029 }
10030 } else {
10031 cols_lit = meta.value()?.parse()?;
10032 }
10033 let columns = split_field_list(&cols_lit.value());
10034 out.indexes.push(IndexAttr {
10035 span: meta.path.span(),
10036 name,
10037 columns,
10038 unique,
10039 method,
10040 where_clause: None,
10041 include: Vec::new(),
10042 });
10043 return Ok(());
10044 }
10045 if meta.path.is_ident("check") {
10046 let mut name: Option<String> = None;
10048 let mut expr: Option<String> = None;
10049 meta.parse_nested_meta(|inner| {
10050 if inner.path.is_ident("name") {
10051 let s: LitStr = inner.value()?.parse()?;
10052 name = Some(s.value());
10053 return Ok(());
10054 }
10055 if inner.path.is_ident("expr") {
10056 let s: LitStr = inner.value()?.parse()?;
10057 expr = Some(s.value());
10058 return Ok(());
10059 }
10060 Err(inner.error("unknown check attribute (supported: `name`, `expr`)"))
10061 })?;
10062 let name = name.ok_or_else(|| meta.error("check requires `name = \"...\"`"))?;
10063 let expr = expr.ok_or_else(|| meta.error("check requires `expr = \"...\"`"))?;
10064 out.checks.push(CheckAttr { name, expr });
10065 return Ok(());
10066 }
10067 if meta.path.is_ident("exclude") {
10068 let mut name: Option<String> = None;
10072 let mut using: Option<String> = None;
10073 let mut elements_raw: Option<(String, proc_macro2::Span)> = None;
10074 let mut where_clause: Option<String> = None;
10075 meta.parse_nested_meta(|inner| {
10076 if inner.path.is_ident("name") {
10077 let s: LitStr = inner.value()?.parse()?;
10078 name = Some(s.value());
10079 return Ok(());
10080 }
10081 if inner.path.is_ident("using") {
10082 let s: LitStr = inner.value()?.parse()?;
10083 using = Some(s.value());
10084 return Ok(());
10085 }
10086 if inner.path.is_ident("elements") {
10087 let s: LitStr = inner.value()?.parse()?;
10088 elements_raw = Some((s.value(), s.span()));
10089 return Ok(());
10090 }
10091 if inner.path.is_ident("where") || inner.path.is_ident("where_clause") {
10092 let s: LitStr = inner.value()?.parse()?;
10093 where_clause = Some(s.value());
10094 return Ok(());
10095 }
10096 Err(inner.error(
10097 "unknown exclude attribute (supported: `name`, `using`, `elements`, `where`)",
10098 ))
10099 })?;
10100 let name = name.ok_or_else(|| meta.error("exclude requires `name = \"...\"`"))?;
10101 let using = using.unwrap_or_else(|| "gist".to_owned());
10102 let (elements_str, elements_span) = elements_raw.ok_or_else(|| {
10103 meta.error(
10104 "exclude requires `elements = \"col WITH op, col WITH op\"`",
10105 )
10106 })?;
10107 let mut elements: Vec<(String, String)> = Vec::new();
10109 for pair in elements_str.split(',') {
10110 let pair = pair.trim();
10111 if pair.is_empty() {
10112 continue;
10113 }
10114 let mut split = pair.splitn(2, |c: char| c.is_whitespace());
10115 let col = split.next().unwrap_or("").trim();
10116 let rest = split.next().unwrap_or("").trim();
10117 let rest_lc = rest.to_ascii_lowercase();
10119 let op = rest_lc
10120 .strip_prefix("with")
10121 .map(|r| r.trim_start())
10122 .filter(|r| !r.is_empty())
10123 .map(|_| {
10124 rest[4..].trim_start().to_owned()
10127 });
10128 let Some(op) = op else {
10129 return Err(syn::Error::new(
10130 elements_span,
10131 format!(
10132 "exclude elements: `{pair}` must be `<col> WITH <op>` \
10133 (e.g. `room_id WITH =` or `during WITH &&`)"
10134 ),
10135 ));
10136 };
10137 if col.is_empty() || op.is_empty() {
10138 return Err(syn::Error::new(
10139 elements_span,
10140 format!(
10141 "exclude elements: `{pair}` must be `<col> WITH <op>` \
10142 (both sides non-empty)"
10143 ),
10144 ));
10145 }
10146 elements.push((col.to_owned(), op));
10147 }
10148 if elements.is_empty() {
10149 return Err(syn::Error::new(
10150 elements_span,
10151 "exclude requires at least one `col WITH op` element",
10152 ));
10153 }
10154 out.excludes.push(ExcludeAttr {
10155 name,
10156 using,
10157 elements,
10158 where_clause,
10159 });
10160 return Ok(());
10161 }
10162 if meta.path.is_ident("generic_fk") {
10163 let mut gfk = GenericFkAttr {
10164 name: String::new(),
10165 ct_column: String::new(),
10166 pk_column: String::new(),
10167 };
10168 meta.parse_nested_meta(|inner| {
10169 if inner.path.is_ident("name") {
10170 let s: LitStr = inner.value()?.parse()?;
10171 gfk.name = s.value();
10172 return Ok(());
10173 }
10174 if inner.path.is_ident("ct_column") {
10175 let s: LitStr = inner.value()?.parse()?;
10176 gfk.ct_column = s.value();
10177 return Ok(());
10178 }
10179 if inner.path.is_ident("pk_column") {
10180 let s: LitStr = inner.value()?.parse()?;
10181 gfk.pk_column = s.value();
10182 return Ok(());
10183 }
10184 Err(inner.error(
10185 "unknown generic_fk attribute (supported: `name`, `ct_column`, `pk_column`)",
10186 ))
10187 })?;
10188 if gfk.name.is_empty() {
10189 return Err(meta.error("generic_fk requires `name = \"...\"`"));
10190 }
10191 if gfk.ct_column.is_empty() {
10192 return Err(meta.error("generic_fk requires `ct_column = \"...\"`"));
10193 }
10194 if gfk.pk_column.is_empty() {
10195 return Err(meta.error("generic_fk requires `pk_column = \"...\"`"));
10196 }
10197 out.generic_fks.push(gfk);
10198 return Ok(());
10199 }
10200 if meta.path.is_ident("fk_composite") {
10201 let mut fk = CompositeFkAttr {
10202 name: String::new(),
10203 to: String::new(),
10204 from: Vec::new(),
10205 on: Vec::new(),
10206 };
10207 meta.parse_nested_meta(|inner| {
10208 if inner.path.is_ident("name") {
10209 let s: LitStr = inner.value()?.parse()?;
10210 fk.name = s.value();
10211 return Ok(());
10212 }
10213 if inner.path.is_ident("to") {
10214 let s: LitStr = inner.value()?.parse()?;
10215 fk.to = s.value();
10216 return Ok(());
10217 }
10218 if inner.path.is_ident("on") || inner.path.is_ident("from") {
10221 let value = inner.value()?;
10222 let content;
10223 syn::parenthesized!(content in value);
10224 let lits: syn::punctuated::Punctuated<syn::LitStr, syn::Token![,]> =
10225 content.parse_terminated(
10226 |p| p.parse::<syn::LitStr>(),
10227 syn::Token![,],
10228 )?;
10229 let cols: Vec<String> = lits.iter().map(syn::LitStr::value).collect();
10230 if inner.path.is_ident("on") {
10231 fk.on = cols;
10232 } else {
10233 fk.from = cols;
10234 }
10235 return Ok(());
10236 }
10237 Err(inner.error(
10238 "unknown fk_composite attribute (supported: `name`, `to`, `on`, `from`)",
10239 ))
10240 })?;
10241 if fk.name.is_empty() {
10242 return Err(meta.error("fk_composite requires `name = \"...\"`"));
10243 }
10244 if fk.to.is_empty() {
10245 return Err(meta.error("fk_composite requires `to = \"...\"`"));
10246 }
10247 if fk.from.is_empty() || fk.on.is_empty() {
10248 return Err(meta.error(
10249 "fk_composite requires non-empty `from = (...)` and `on = (...)` tuples",
10250 ));
10251 }
10252 if fk.from.len() != fk.on.len() {
10253 return Err(meta.error(format!(
10254 "fk_composite `from` ({} cols) and `on` ({} cols) must be the same length",
10255 fk.from.len(),
10256 fk.on.len(),
10257 )));
10258 }
10259 out.composite_fks.push(fk);
10260 return Ok(());
10261 }
10262 if meta.path.is_ident("m2m") {
10263 let mut m2m = M2MAttr {
10264 name: String::new(),
10265 to: String::new(),
10266 through: String::new(),
10267 src: String::new(),
10268 dst: String::new(),
10269 auto_create: true,
10270 };
10271 meta.parse_nested_meta(|inner| {
10272 if inner.path.is_ident("name") {
10273 let s: LitStr = inner.value()?.parse()?;
10274 m2m.name = s.value();
10275 return Ok(());
10276 }
10277 if inner.path.is_ident("to") {
10278 let s: LitStr = inner.value()?.parse()?;
10279 m2m.to = s.value();
10280 return Ok(());
10281 }
10282 if inner.path.is_ident("through") {
10283 let s: LitStr = inner.value()?.parse()?;
10284 m2m.through = s.value();
10285 return Ok(());
10286 }
10287 if inner.path.is_ident("src") {
10288 let s: LitStr = inner.value()?.parse()?;
10289 m2m.src = s.value();
10290 return Ok(());
10291 }
10292 if inner.path.is_ident("dst") {
10293 let s: LitStr = inner.value()?.parse()?;
10294 m2m.dst = s.value();
10295 return Ok(());
10296 }
10297 if inner.path.is_ident("auto_create") {
10298 let lit: syn::LitBool = inner.value()?.parse()?;
10299 m2m.auto_create = lit.value;
10300 return Ok(());
10301 }
10302 Err(inner.error("unknown m2m attribute (supported: `name`, `to`, `through`, `src`, `dst`, `auto_create`)"))
10303 })?;
10304 if m2m.name.is_empty() {
10305 return Err(meta.error("m2m requires `name = \"...\"`"));
10306 }
10307 if m2m.to.is_empty() {
10308 return Err(meta.error("m2m requires `to = \"...\"`"));
10309 }
10310 if m2m.through.is_empty() {
10311 return Err(meta.error("m2m requires `through = \"...\"`"));
10312 }
10313 if m2m.src.is_empty() {
10314 return Err(meta.error("m2m requires `src = \"...\"`"));
10315 }
10316 if m2m.dst.is_empty() {
10317 return Err(meta.error("m2m requires `dst = \"...\"`"));
10318 }
10319 out.m2m.push(m2m);
10320 return Ok(());
10321 }
10322 if meta.path.is_ident("generic_m2m") {
10323 let mut gm = GenericM2MAttr {
10324 name: String::new(),
10325 through: String::new(),
10326 pk_column: String::new(),
10327 ct_column: String::new(),
10328 related_column: String::new(),
10329 };
10330 meta.parse_nested_meta(|inner| {
10331 let field = |inner: &syn::meta::ParseNestedMeta| -> syn::Result<String> {
10332 let s: LitStr = inner.value()?.parse()?;
10333 Ok(s.value())
10334 };
10335 if inner.path.is_ident("name") {
10336 gm.name = field(&inner)?;
10337 return Ok(());
10338 }
10339 if inner.path.is_ident("through") {
10340 gm.through = field(&inner)?;
10341 return Ok(());
10342 }
10343 if inner.path.is_ident("pk_column") {
10344 gm.pk_column = field(&inner)?;
10345 return Ok(());
10346 }
10347 if inner.path.is_ident("ct_column") {
10348 gm.ct_column = field(&inner)?;
10349 return Ok(());
10350 }
10351 if inner.path.is_ident("related_column") {
10352 gm.related_column = field(&inner)?;
10353 return Ok(());
10354 }
10355 Err(inner.error("unknown generic_m2m attribute (supported: `name`, `through`, `pk_column`, `ct_column`, `related_column`)"))
10356 })?;
10357 for (val, label) in [
10358 (&gm.name, "name"),
10359 (&gm.through, "through"),
10360 (&gm.pk_column, "pk_column"),
10361 (&gm.ct_column, "ct_column"),
10362 (&gm.related_column, "related_column"),
10363 ] {
10364 if val.is_empty() {
10365 return Err(meta.error(format!("generic_m2m requires `{label} = \"...\"`")));
10366 }
10367 }
10368 out.generic_m2m.push(gm);
10369 return Ok(());
10370 }
10371 Err(unknown_key_error(&meta, "container", CONTAINER_ATTR_KEYS))
10372 })?;
10373 }
10374 Ok(out)
10375}
10376
10377fn split_field_list(raw: &str) -> Vec<String> {
10381 raw.split(',')
10382 .map(str::trim)
10383 .filter(|s| !s.is_empty())
10384 .map(str::to_owned)
10385 .collect()
10386}
10387
10388fn parse_together_attr(
10396 meta: &syn::meta::ParseNestedMeta<'_>,
10397 attr: &str,
10398 written: &str,
10399) -> syn::Result<(Vec<String>, Option<String>)> {
10400 if meta.input.peek(syn::Token![=]) {
10403 let cols_lit: LitStr = meta.value()?.parse()?;
10404 let columns = split_field_list(&cols_lit.value());
10405 check_together_columns(meta, attr, written, &columns)?;
10406 return Ok((columns, None));
10407 }
10408 let mut columns: Option<Vec<String>> = None;
10409 let mut name: Option<String> = None;
10410 meta.parse_nested_meta(|inner| {
10411 if inner.path.is_ident("columns") {
10412 let s: LitStr = inner.value()?.parse()?;
10413 columns = Some(split_field_list(&s.value()));
10414 return Ok(());
10415 }
10416 if inner.path.is_ident("name") {
10417 let s: LitStr = inner.value()?.parse()?;
10418 name = Some(s.value());
10419 return Ok(());
10420 }
10421 Err(inner.error("unknown sub-attribute (supported: `columns`, `name`)"))
10422 })?;
10423 let columns = columns.ok_or_else(|| {
10424 meta.error(format!(
10425 "{attr}(...) requires a `columns = \"col1, col2\"` argument",
10426 ))
10427 })?;
10428 check_together_columns(meta, attr, written, &columns)?;
10429 Ok((columns, name))
10430}
10431
10432fn check_together_columns(
10433 meta: &syn::meta::ParseNestedMeta<'_>,
10434 attr: &str,
10435 written: &str,
10436 columns: &[String],
10437) -> syn::Result<()> {
10438 if columns.len() < 2 {
10439 let flag = if attr == "unique_together" {
10440 "unique"
10441 } else {
10442 "index"
10443 };
10444 let single = format!("#[{written}({flag})] on the field");
10445 return Err(meta.error(format!(
10446 "{attr} expects two or more columns; for a single-column equivalent use {single}",
10447 )));
10448 }
10449 Ok(())
10450}
10451
10452fn parse_fieldset_list(raw: &str) -> Vec<(String, Vec<String>)> {
10461 raw.split('|')
10462 .map(str::trim)
10463 .filter(|s| !s.is_empty())
10464 .map(|section| {
10465 let (title, rest) = match section.split_once(':') {
10467 Some((title, rest)) if !title.contains(',') => (title.trim().to_owned(), rest),
10468 _ => (String::new(), section),
10469 };
10470 let fields = split_field_list(rest);
10471 (title, fields)
10472 })
10473 .collect()
10474}
10475
10476fn parse_prepopulated_list(raw: &str) -> Vec<(String, Vec<String>)> {
10481 raw.split(',')
10482 .map(str::trim)
10483 .filter(|s| !s.is_empty())
10484 .filter_map(|entry| {
10485 let (target, sources_raw) = entry.split_once(':')?;
10486 let target = target.trim().to_owned();
10487 if target.is_empty() {
10488 return None;
10489 }
10490 let sources: Vec<String> = sources_raw
10491 .split('+')
10492 .map(|s| s.trim().to_owned())
10493 .filter(|s| !s.is_empty())
10494 .collect();
10495 if sources.is_empty() {
10496 return None;
10497 }
10498 Some((target, sources))
10499 })
10500 .collect()
10501}
10502
10503fn parse_formfield_overrides(raw: &str) -> Vec<(String, String)> {
10509 raw.split(',')
10510 .map(str::trim)
10511 .filter(|s| !s.is_empty())
10512 .filter_map(|entry| {
10513 let (field, widget) = entry.split_once(':')?;
10514 let field = field.trim().to_owned();
10515 let widget = widget.trim().to_owned();
10516 if field.is_empty() || widget.is_empty() {
10517 return None;
10518 }
10519 Some((field, widget))
10520 })
10521 .collect()
10522}
10523
10524fn parse_ordering_list(raw: &str) -> Vec<(String, bool)> {
10527 raw.split(',')
10528 .map(str::trim)
10529 .filter(|s| !s.is_empty())
10530 .map(|spec| {
10531 spec.strip_prefix('-')
10532 .map_or((spec.to_owned(), false), |rest| {
10533 (rest.trim().to_owned(), true)
10534 })
10535 })
10536 .collect()
10537}
10538
10539struct FieldAttrs {
10540 column: Option<String>,
10541 primary_key: bool,
10542 fk: Option<String>,
10543 o2o: Option<String>,
10544 on: Option<String>,
10545 on_delete: Option<String>,
10555 related_name: Option<String>,
10563 max_length: Option<u32>,
10564 vector_dims: Option<u32>,
10568 geometry_srid: Option<u32>,
10572 min: Option<i64>,
10573 max: Option<i64>,
10574 default: Option<String>,
10575 auto_uuid: bool,
10581 default_uuid_v7: bool,
10592 auto_now_add: bool,
10597 auto_now: bool,
10603 soft_delete: bool,
10608 unique: bool,
10611 index: bool,
10615 index_unique: bool,
10616 index_name: Option<String>,
10617 index_method: String,
10620 generated_as: Option<String>,
10626 help_text: Option<String>,
10631 choices: Option<Vec<(String, String)>>,
10639 db_comment: Option<String>,
10645 verbose_name: Option<String>,
10651 editable: bool,
10658 blank: bool,
10662 case_insensitive: bool,
10667 validators: Vec<syn::Ident>,
10673 secret: Option<bool>,
10675}
10676
10677const FIELD_ATTR_KEYS: &[&str] = &[
10679 "column",
10680 "primary_key",
10681 "fk",
10682 "o2o",
10683 "on",
10684 "on_delete",
10685 "related_name",
10686 "max_length",
10687 "vector",
10688 "geometry",
10689 "min",
10690 "max",
10691 "default",
10692 "generated_as",
10693 "help_text",
10694 "choices",
10695 "db_comment",
10696 "verbose_name",
10697 "editable",
10698 "secret",
10699 "blank",
10700 "citext",
10701 "validators",
10702 "auto_uuid",
10703 "default_uuid_v7",
10704 "auto_now_add",
10705 "auto_now",
10706 "soft_delete",
10707 "unique",
10708 "index",
10709];
10710
10711const CONTAINER_ATTR_KEYS: &[&str] = &[
10713 "table",
10714 "display",
10715 "app",
10716 "scope",
10717 "admin",
10718 "manager",
10719 "manager_fn",
10720 "default_order",
10721 "global_scope",
10722 "through",
10723 "reverse_has",
10724 "generic_has",
10725 "audit",
10726 "permissions",
10727 "view",
10728 "managed",
10729 "verbose_name",
10730 "verbose_name_plural",
10731 "db_table_comment",
10732 "proxy",
10733 "order_with_respect_to",
10734 "required_db_features",
10735 "required_db_vendor",
10736 "base_manager_name",
10737 "default_related_name",
10738 "extra_permissions",
10739 "default_permissions",
10740 "get_latest_by",
10741 "unique_together",
10742 "index_together",
10743 "unique_when",
10744 "index_when",
10745 "index",
10746 "check",
10747 "exclude",
10748 "generic_fk",
10749 "fk_composite",
10750 "m2m",
10751 "generic_m2m",
10752];
10753
10754fn parse_flag(meta: &syn::meta::ParseNestedMeta<'_>, key: &str) -> syn::Result<()> {
10757 if meta.input.peek(syn::Token![=]) {
10758 let lit: syn::LitBool = meta.value()?.parse()?;
10759 if !lit.value {
10760 return Err(syn::Error::new(
10761 lit.span(),
10762 format!("`{key} = false` is the default; remove the attribute"),
10763 ));
10764 }
10765 }
10766 Ok(())
10767}
10768
10769fn unknown_key_error(
10771 meta: &syn::meta::ParseNestedMeta<'_>,
10772 what: &str,
10773 keys: &[&str],
10774) -> syn::Error {
10775 let path = &meta.path;
10776 let got = path
10777 .get_ident()
10778 .map_or_else(|| quote!(#path).to_string(), ToString::to_string);
10779 let mut msg = format!("unknown rustango {what} attribute `{got}`");
10780 if let Some(near) = keys
10781 .iter()
10782 .map(|k| (edit_distance(&got, k), *k))
10783 .filter(|(d, k)| *d <= 2.max(k.len() / 4))
10784 .min_by_key(|(d, _)| *d)
10785 .map(|(_, k)| k)
10786 {
10787 msg.push_str(&format!("; did you mean `{near}`?"));
10788 }
10789 msg.push_str(&format!(" Supported: {}", keys.join(", ")));
10790 meta.error(msg)
10791}
10792
10793fn edit_distance(a: &str, b: &str) -> usize {
10795 let b: Vec<char> = b.chars().collect();
10796 let mut prev: Vec<usize> = (0..=b.len()).collect();
10797 for (i, ca) in a.chars().enumerate() {
10798 let mut cur = vec![i + 1];
10799 for (j, cb) in b.iter().enumerate() {
10800 let sub = prev[j] + usize::from(ca != *cb);
10801 cur.push(sub.min(prev[j + 1] + 1).min(cur[j] + 1));
10802 }
10803 prev = cur;
10804 }
10805 prev[b.len()]
10806}
10807
10808fn parse_field_attrs(field: &syn::Field) -> syn::Result<FieldAttrs> {
10809 let mut out = FieldAttrs {
10810 column: None,
10811 primary_key: false,
10812 fk: None,
10813 o2o: None,
10814 on: None,
10815 on_delete: None,
10816 related_name: None,
10817 max_length: None,
10818 vector_dims: None,
10819 geometry_srid: None,
10820 min: None,
10821 max: None,
10822 default: None,
10823 auto_uuid: false,
10824 default_uuid_v7: false,
10825 auto_now_add: false,
10826 auto_now: false,
10827 soft_delete: false,
10828 unique: false,
10829 index: false,
10830 index_unique: false,
10831 index_name: None,
10832 index_method: "btree".to_owned(),
10833 generated_as: None,
10834 help_text: None,
10835 choices: None,
10836 db_comment: None,
10837 verbose_name: None,
10838 editable: true,
10839 blank: false,
10840 case_insensitive: false,
10841 validators: Vec::new(),
10842 secret: None,
10843 };
10844 for attr in model_attrs(&field.attrs)? {
10845 attr.parse_nested_meta(|meta| {
10846 if meta.path.is_ident("column") {
10847 let s: LitStr = meta.value()?.parse()?;
10848 let name = s.value();
10849 validate_sql_identifier(&name, "column", s.span())?;
10850 out.column = Some(name);
10851 return Ok(());
10852 }
10853 if meta.path.is_ident("primary_key") {
10854 parse_flag(&meta, "primary_key")?;
10855 out.primary_key = true;
10856 return Ok(());
10857 }
10858 if meta.path.is_ident("fk") {
10859 let s: LitStr = meta.value()?.parse()?;
10860 out.fk = Some(s.value());
10861 return Ok(());
10862 }
10863 if meta.path.is_ident("o2o") {
10864 let s: LitStr = meta.value()?.parse()?;
10865 out.o2o = Some(s.value());
10866 return Ok(());
10867 }
10868 if meta.path.is_ident("on") {
10869 let s: LitStr = meta.value()?.parse()?;
10870 out.on = Some(s.value());
10871 return Ok(());
10872 }
10873 if meta.path.is_ident("on_delete") {
10874 let s: LitStr = meta.value()?.parse()?;
10875 let raw = s.value();
10876 let normalized = raw.trim().to_ascii_lowercase();
10877 match normalized.as_str() {
10880 "cascade" | "restrict" | "set_null" | "set_default" | "no_action" => {}
10881 _ => {
10882 return Err(syn::Error::new(
10883 s.span(),
10884 format!(
10885 "unknown on_delete action `{raw}`; expected one of \
10886 `cascade`, `restrict`, `set_null`, `set_default`, `no_action`"
10887 ),
10888 ));
10889 }
10890 }
10891 out.on_delete = Some(normalized);
10892 return Ok(());
10893 }
10894 if meta.path.is_ident("related_name") {
10895 let s: LitStr = meta.value()?.parse()?;
10896 let raw = s.value();
10897 if raw.trim().is_empty() {
10898 return Err(syn::Error::new(
10899 s.span(),
10900 "`related_name` must be a non-empty identifier",
10901 ));
10902 }
10903 let snake = raw
10908 .chars()
10909 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
10910 && !raw.starts_with(char::is_numeric);
10911 if !snake && raw != NO_REVERSE_ACCESSOR {
10912 return Err(syn::Error::new(
10913 s.span(),
10914 "`related_name` must be snake_case ASCII (lowercase letters, \
10915 digits, underscores; no leading digit), or \"+\" for none",
10916 ));
10917 }
10918 out.related_name = Some(raw);
10919 return Ok(());
10920 }
10921 if meta.path.is_ident("max_length") {
10922 let lit: syn::LitInt = meta.value()?.parse()?;
10923 out.max_length = Some(lit.base10_parse::<u32>()?);
10924 return Ok(());
10925 }
10926 if meta.path.is_ident("vector") {
10930 meta.parse_nested_meta(|inner| {
10931 if inner.path.is_ident("dims") {
10932 let lit: syn::LitInt = inner.value()?.parse()?;
10933 out.vector_dims = Some(lit.base10_parse::<u32>()?);
10934 return Ok(());
10935 }
10936 Err(inner.error("unknown `vector` attribute (supported: `dims`)"))
10937 })?;
10938 return Ok(());
10939 }
10940 if meta.path.is_ident("geometry") {
10943 meta.parse_nested_meta(|inner| {
10944 if inner.path.is_ident("srid") {
10945 let lit: syn::LitInt = inner.value()?.parse()?;
10946 out.geometry_srid = Some(lit.base10_parse::<u32>()?);
10947 return Ok(());
10948 }
10949 Err(inner.error("unknown `geometry` attribute (supported: `srid`)"))
10950 })?;
10951 return Ok(());
10952 }
10953 if meta.path.is_ident("min") {
10954 out.min = Some(parse_signed_i64(&meta)?);
10955 return Ok(());
10956 }
10957 if meta.path.is_ident("max") {
10958 out.max = Some(parse_signed_i64(&meta)?);
10959 return Ok(());
10960 }
10961 if meta.path.is_ident("default") {
10962 let s: LitStr = meta.value()?.parse()?;
10963 out.default = Some(s.value());
10964 return Ok(());
10965 }
10966 if meta.path.is_ident("generated_as") {
10967 let s: LitStr = meta.value()?.parse()?;
10968 out.generated_as = Some(s.value());
10969 return Ok(());
10970 }
10971 if meta.path.is_ident("help_text") {
10972 let s: LitStr = meta.value()?.parse()?;
10973 out.help_text = Some(s.value());
10974 return Ok(());
10975 }
10976 if meta.path.is_ident("choices") {
10977 let s: LitStr = meta.value()?.parse()?;
10978 let raw = s.value();
10979 let mut pairs: Vec<(String, String)> = Vec::new();
10980 for chunk in raw.split(',') {
10981 let chunk = chunk.trim();
10982 if chunk.is_empty() {
10983 continue;
10984 }
10985 let (value, label) = match chunk.split_once(':') {
10986 Some((v, l)) => (v.trim().to_owned(), l.trim().to_owned()),
10987 None => (chunk.to_owned(), chunk.to_owned()),
10988 };
10989 if value.is_empty() {
10990 return Err(syn::Error::new(
10991 s.span(),
10992 "`choices` entry has empty value before `:`",
10993 ));
10994 }
10995 pairs.push((value, label));
10996 }
10997 if pairs.is_empty() {
10998 return Err(syn::Error::new(
10999 s.span(),
11000 "`choices = \"…\"` must contain at least one value",
11001 ));
11002 }
11003 out.choices = Some(pairs);
11004 return Ok(());
11005 }
11006 if meta.path.is_ident("db_comment") {
11007 let s: LitStr = meta.value()?.parse()?;
11008 out.db_comment = Some(s.value());
11009 return Ok(());
11010 }
11011 if meta.path.is_ident("verbose_name") {
11012 let s: LitStr = meta.value()?.parse()?;
11013 out.verbose_name = Some(s.value());
11014 return Ok(());
11015 }
11016 if meta.path.is_ident("secret") {
11017 out.secret = Some(if let Ok(v) = meta.value() {
11019 v.parse::<syn::LitBool>()?.value
11020 } else {
11021 true
11022 });
11023 return Ok(());
11024 }
11025 if meta.path.is_ident("editable") {
11026 if let Ok(v) = meta.value() {
11031 let lit: syn::LitBool = v.parse()?;
11032 out.editable = lit.value;
11033 } else {
11034 out.editable = true;
11035 }
11036 return Ok(());
11037 }
11038 if meta.path.is_ident("blank") {
11039 if let Ok(v) = meta.value() {
11043 let lit: syn::LitBool = v.parse()?;
11044 out.blank = lit.value;
11045 } else {
11046 out.blank = true;
11047 }
11048 return Ok(());
11049 }
11050 if meta.path.is_ident("citext") {
11051 if let Ok(v) = meta.value() {
11060 let lit: syn::LitBool = v.parse()?;
11061 out.case_insensitive = lit.value;
11062 } else {
11063 out.case_insensitive = true;
11064 }
11065 return Ok(());
11066 }
11067 if meta.path.is_ident("validators") {
11068 let s: LitStr = meta.value()?.parse()?;
11069 let raw = s.value();
11070 out.validators = raw
11071 .split(',')
11072 .map(str::trim)
11073 .filter(|s| !s.is_empty())
11074 .map(|name| {
11075 let mut ident = syn::parse_str::<syn::Ident>(name).map_err(|_| {
11076 syn::Error::new(s.span(), format!("unknown validator `{name}`"))
11077 })?;
11078 ident.set_span(s.span());
11079 Ok(ident)
11080 })
11081 .collect::<syn::Result<_>>()?;
11082 if out.validators.is_empty() {
11083 return Err(syn::Error::new(
11084 s.span(),
11085 "`validators = \"…\"` must list at least one name",
11086 ));
11087 }
11088 return Ok(());
11089 }
11090 if meta.path.is_ident("auto_uuid") {
11091 parse_flag(&meta, "auto_uuid")?;
11092 out.auto_uuid = true;
11093 out.primary_key = true;
11097 if out.default.is_none() {
11098 out.default = Some("gen_random_uuid()".into());
11099 }
11100 return Ok(());
11101 }
11102 if meta.path.is_ident("default_uuid_v7") {
11103 parse_flag(&meta, "default_uuid_v7")?;
11104 out.default_uuid_v7 = true;
11110 out.primary_key = true;
11111 return Ok(());
11112 }
11113 if meta.path.is_ident("auto_now_add") {
11114 parse_flag(&meta, "auto_now_add")?;
11115 out.auto_now_add = true;
11116 if out.default.is_none() {
11117 out.default = Some("now()".into());
11118 }
11119 return Ok(());
11120 }
11121 if meta.path.is_ident("auto_now") {
11122 parse_flag(&meta, "auto_now")?;
11123 out.auto_now = true;
11124 if out.default.is_none() {
11125 out.default = Some("now()".into());
11126 }
11127 return Ok(());
11128 }
11129 if meta.path.is_ident("soft_delete") {
11130 parse_flag(&meta, "soft_delete")?;
11131 out.soft_delete = true;
11132 return Ok(());
11133 }
11134 if meta.path.is_ident("unique") {
11135 parse_flag(&meta, "unique")?;
11136 out.unique = true;
11137 return Ok(());
11138 }
11139 if meta.path.is_ident("index") {
11140 parse_flag(&meta, "index")?;
11141 out.index = true;
11142 if meta.input.peek(syn::token::Paren) {
11144 meta.parse_nested_meta(|inner| {
11145 if inner.path.is_ident("unique") {
11146 out.index_unique = true;
11147 return Ok(());
11148 }
11149 if inner.path.is_ident("name") {
11150 let s: LitStr = inner.value()?.parse()?;
11151 out.index_name = Some(s.value());
11152 return Ok(());
11153 }
11154 if inner.path.is_ident("method") {
11155 let s: LitStr = inner.value()?.parse()?;
11156 let v = s.value();
11157 match v.as_str() {
11158 "btree" | "gin" | "gist" | "brin" | "spgist" | "hash" | "bloom" => {
11159 out.index_method = v;
11160 }
11161 other => {
11162 return Err(inner.error(format!(
11163 "unknown index method `{other}` (supported: btree, gin, gist, brin, spgist, hash, bloom)",
11164 )));
11165 }
11166 }
11167 return Ok(());
11168 }
11169 Err(inner.error(
11170 "unknown index sub-attribute (supported: `unique`, `name`, `method`)",
11171 ))
11172 })?;
11173 }
11174 return Ok(());
11175 }
11176 Err(unknown_key_error(&meta, "field", FIELD_ATTR_KEYS))
11177 })?;
11178 }
11179 Ok(out)
11180}
11181
11182fn parse_signed_i64(meta: &syn::meta::ParseNestedMeta<'_>) -> syn::Result<i64> {
11184 let expr: syn::Expr = meta.value()?.parse()?;
11185 match expr {
11186 syn::Expr::Lit(syn::ExprLit {
11187 lit: syn::Lit::Int(lit),
11188 ..
11189 }) => lit.base10_parse::<i64>(),
11190 syn::Expr::Unary(syn::ExprUnary {
11191 op: syn::UnOp::Neg(_),
11192 expr,
11193 ..
11194 }) => {
11195 if let syn::Expr::Lit(syn::ExprLit {
11196 lit: syn::Lit::Int(lit),
11197 ..
11198 }) = *expr
11199 {
11200 let v: i64 = lit.base10_parse()?;
11201 Ok(-v)
11202 } else {
11203 Err(syn::Error::new_spanned(expr, "expected integer literal"))
11204 }
11205 }
11206 other => Err(syn::Error::new_spanned(
11207 other,
11208 "expected integer literal (signed)",
11209 )),
11210 }
11211}
11212
11213struct FieldInfo<'a> {
11214 ident: &'a syn::Ident,
11215 column: String,
11216 primary_key: bool,
11217 auto: bool,
11221 value_ty: &'a Type,
11224 field_type_tokens: TokenStream2,
11226 schema: TokenStream2,
11227 from_row_init: TokenStream2,
11228 from_aliased_row_init: TokenStream2,
11234 from_row_init_my: TokenStream2,
11237 from_aliased_row_init_my: TokenStream2,
11238 uuid_column: bool,
11240 fk_inner: Option<Type>,
11244 fk_pk_kind: DetectedKind,
11250 nullable: bool,
11258 auto_now: bool,
11264 auto_now_add: bool,
11270 soft_delete: bool,
11275 generated_as: Option<String>,
11280 default_uuid_v7: bool,
11287 related_name: Option<String>,
11292}
11293
11294fn validate_table_name(name: &str, span: proc_macro2::Span) -> syn::Result<()> {
11308 validate_sql_identifier(name, "table", span)
11309}
11310
11311fn validate_sql_identifier(name: &str, kind: &str, span: proc_macro2::Span) -> syn::Result<()> {
11316 if name.is_empty() {
11317 return Err(syn::Error::new(
11318 span,
11319 format!("`{kind} = \"\"` is not a valid SQL identifier"),
11320 ));
11321 }
11322 let mut chars = name.chars();
11323 let first = chars.next().unwrap();
11324 if !(first.is_ascii_alphabetic() || first == '_') {
11325 return Err(syn::Error::new(
11326 span,
11327 format!("{kind} name `{name}` must start with a letter or underscore (got {first:?})"),
11328 ));
11329 }
11330 for c in chars {
11331 if !(c.is_ascii_alphanumeric() || c == '_') {
11332 return Err(syn::Error::new(
11333 span,
11334 format!(
11335 "{kind} name `{name}` contains invalid character {c:?} — \
11336 SQL identifiers must match `[a-zA-Z_][a-zA-Z0-9_]*`. \
11337 Hyphens in particular break FK / index name derivation \
11338 downstream; use underscores instead (e.g. `{}`)",
11339 name.replace(|x: char| !x.is_ascii_alphanumeric() && x != '_', "_"),
11340 ),
11341 ));
11342 }
11343 }
11344 Ok(())
11345}
11346
11347fn process_field<'a>(field: &'a syn::Field, table: &str) -> syn::Result<FieldInfo<'a>> {
11348 let root = rustango_root();
11349 let attrs = parse_field_attrs(field)?;
11350 let ident = field
11351 .ident
11352 .as_ref()
11353 .ok_or_else(|| syn::Error::new(field.span(), "tuple structs are not supported"))?;
11354 let name = ident_name(ident);
11355 let column = attrs.column.clone().unwrap_or_else(|| name.clone());
11356 let primary_key = attrs.primary_key;
11357 let DetectedType {
11358 kind,
11359 nullable,
11360 auto: detected_auto,
11361 fk_inner,
11362 } = detect_type(&field.ty)?;
11363 check_bound_compatibility(field, &attrs, kind)?;
11364 let auto = detected_auto;
11365 if attrs.auto_uuid {
11371 if kind != DetectedKind::Uuid {
11372 return Err(syn::Error::new_spanned(
11373 field,
11374 "`#[rustango(auto_uuid)]` requires the field type to be \
11375 `Auto<uuid::Uuid>`",
11376 ));
11377 }
11378 if !detected_auto {
11379 return Err(syn::Error::new_spanned(
11380 field,
11381 "`#[rustango(auto_uuid)]` requires the field type to be \
11382 wrapped in `Auto<...>` so the macro skips the column on \
11383 INSERT and the DB DEFAULT (`gen_random_uuid()`) fires",
11384 ));
11385 }
11386 }
11387 if attrs.default_uuid_v7 {
11388 if kind != DetectedKind::Uuid {
11389 return Err(syn::Error::new_spanned(
11390 field,
11391 "`#[rustango(default_uuid_v7)]` requires the field type to be \
11392 `Auto<uuid::Uuid>`",
11393 ));
11394 }
11395 if !detected_auto {
11396 return Err(syn::Error::new_spanned(
11397 field,
11398 "`#[rustango(default_uuid_v7)]` requires the field type to be \
11399 wrapped in `Auto<...>` so the macro can detect the \
11400 unset-vs-set state and fill a fresh UUIDv7 before INSERT",
11401 ));
11402 }
11403 if attrs.auto_uuid {
11404 return Err(syn::Error::new_spanned(
11405 field,
11406 "`#[rustango(default_uuid_v7)]` is mutually exclusive with \
11407 `#[rustango(auto_uuid)]` — the former generates the UUID \
11408 Rust-side, the latter relies on the DB's `gen_random_uuid()`. \
11409 Pick one.",
11410 ));
11411 }
11412 }
11413 if attrs.auto_now_add || attrs.auto_now {
11414 if kind != DetectedKind::DateTime {
11415 return Err(syn::Error::new_spanned(
11416 field,
11417 "`#[rustango(auto_now_add)]` / `#[rustango(auto_now)]` require \
11418 the field type to be `Auto<chrono::DateTime<chrono::Utc>>`",
11419 ));
11420 }
11421 if !detected_auto {
11422 return Err(syn::Error::new_spanned(
11423 field,
11424 "`#[rustango(auto_now_add)]` / `#[rustango(auto_now)]` require \
11425 the field type to be wrapped in `Auto<...>` so the macro skips \
11426 the column on INSERT and the DB DEFAULT (`now()`) fires",
11427 ));
11428 }
11429 }
11430 if attrs.soft_delete && !(kind == DetectedKind::DateTime && nullable) {
11431 return Err(syn::Error::new_spanned(
11432 field,
11433 "`#[rustango(soft_delete)]` requires the field type to be \
11434 `Option<chrono::DateTime<chrono::Utc>>`",
11435 ));
11436 }
11437 let is_mixin_auto =
11438 attrs.auto_uuid || attrs.default_uuid_v7 || attrs.auto_now_add || attrs.auto_now;
11439 if detected_auto && !primary_key && !is_mixin_auto {
11440 return Err(syn::Error::new_spanned(
11441 field,
11442 "`Auto<T>` is only valid on a `#[rustango(primary_key)]` field, \
11443 or on a field carrying one of `auto_uuid`, `auto_now_add`, or \
11444 `auto_now`",
11445 ));
11446 }
11447 if detected_auto && attrs.default.is_some() && !is_mixin_auto {
11448 return Err(syn::Error::new_spanned(
11449 field,
11450 "`#[rustango(default = \"…\")]` is redundant on an `Auto<T>` field — \
11451 SERIAL / BIGSERIAL already supplies a default sequence.",
11452 ));
11453 }
11454 if fk_inner.is_some() && primary_key {
11455 return Err(syn::Error::new_spanned(
11456 field,
11457 "`ForeignKey<T>` is not allowed on a primary-key field — \
11458 a row's PK is its own identity, not a reference to a parent.",
11459 ));
11460 }
11461 if attrs.generated_as.is_some() {
11462 if primary_key {
11463 return Err(syn::Error::new_spanned(
11464 field,
11465 "`#[rustango(generated_as = \"…\")]` is not allowed on a \
11466 primary-key field — a PK must be writable so the row \
11467 has an identity at INSERT time.",
11468 ));
11469 }
11470 if attrs.default.is_some() {
11471 return Err(syn::Error::new_spanned(
11472 field,
11473 "`#[rustango(generated_as = \"…\")]` cannot combine with \
11474 `default = \"…\"` — Postgres rejects DEFAULT on \
11475 generated columns. The expression IS the default.",
11476 ));
11477 }
11478 if detected_auto {
11479 return Err(syn::Error::new_spanned(
11480 field,
11481 "`#[rustango(generated_as = \"…\")]` is not allowed on \
11482 an `Auto<T>` field — generated columns are computed \
11483 by the DB, not server-assigned via a sequence. Use a \
11484 plain Rust type (e.g. `f64`).",
11485 ));
11486 }
11487 if fk_inner.is_some() {
11488 return Err(syn::Error::new_spanned(
11489 field,
11490 "`#[rustango(generated_as = \"…\")]` is not allowed on a \
11491 ForeignKey field.",
11492 ));
11493 }
11494 }
11495 let relation = relation_tokens(field, &attrs, fk_inner, table)?;
11496 let column_lit = column.as_str();
11497 let field_type_tokens = if kind == DetectedKind::Vector {
11502 let root = rustango_root();
11503 let dims = attrs.vector_dims.unwrap_or(0);
11504 quote!(#root::core::FieldType::Vector(#dims))
11505 } else if kind == DetectedKind::Geometry {
11506 let root = rustango_root();
11510 let srid = attrs.geometry_srid.unwrap_or(0);
11511 quote!(#root::core::FieldType::Geometry(#srid))
11512 } else {
11513 kind.variant_tokens()
11514 };
11515 let max_length = optional_u32(attrs.max_length);
11516 let min = optional_i64(attrs.min);
11517 let max = optional_i64(attrs.max);
11518 let default = optional_str(attrs.default.as_deref());
11519
11520 let unique = attrs.unique;
11521 let generated_as = optional_str(attrs.generated_as.as_deref());
11522 let help_text = optional_str(attrs.help_text.as_deref());
11523 let choices = optional_choices(attrs.choices.as_deref());
11524 let db_comment = optional_str(attrs.db_comment.as_deref());
11525 let verbose_name = optional_str(attrs.verbose_name.as_deref());
11526 let editable = attrs.editable;
11527 let secret = match attrs.secret {
11528 Some(v) => quote!(::core::option::Option::Some(#v)),
11529 None => quote!(::core::option::Option::None),
11530 };
11531 let blank = attrs.blank;
11532 let case_insensitive = attrs.case_insensitive;
11533 let validators = &attrs.validators;
11535 if attrs.on_delete.is_some() && attrs.fk.is_none() && attrs.o2o.is_none() && fk_inner.is_none()
11536 {
11537 return Err(syn::Error::new_spanned(
11538 field,
11539 "`#[rustango(on_delete = \"…\")]` requires a `ForeignKey<T>` field \
11540 or `fk = \"<table>\"` / `o2o = \"<table>\"` on the same field — it \
11541 has no meaning on a non-FK column.",
11542 ));
11543 }
11544 if attrs.on_delete.as_deref() == Some("set_null") && !nullable {
11545 return Err(syn::Error::new_spanned(
11546 field,
11547 "`on_delete = \"set_null\"` needs a nullable column: use \
11548 `Option<ForeignKey<T>>` or `Option<…>` (spelled out; a type \
11549 alias for `Option` is not seen).",
11550 ));
11551 }
11552 let fk_on_delete = match attrs.on_delete.as_deref() {
11553 None => quote!(::core::option::Option::None),
11554 Some(action) => {
11555 let variant = match action {
11556 "cascade" => quote!(Cascade),
11557 "restrict" => quote!(Restrict),
11558 "set_null" => quote!(SetNull),
11559 "set_default" => quote!(SetDefault),
11560 "no_action" => quote!(NoAction),
11561 other => unreachable!("on_delete `{other}` should have been rejected at parse"),
11563 };
11564 quote!(::core::option::Option::Some(
11565 #root::core::OnDeleteAction::#variant
11566 ))
11567 }
11568 };
11569 let auto_now_flag = attrs.auto_now;
11570 let cast = match cast_type(&field.ty) {
11571 Some(ty) => quote!(::core::option::Option::Some(<#ty>::FIELD_CAST)),
11572 None => quote!(::core::option::Option::None),
11573 };
11574 let sealed_guard = match (attrs.secret, cast_type(&field.ty)) {
11576 (Some(false), Some(ty)) => quote! {
11577 const _: () = ::core::assert!(
11578 !::core::matches!(<#ty>::FIELD_CAST, #root::core::FieldCast::Sealed),
11579 "`#[rustango(secret = false)]` on a sealed cast: it is always a credential",
11580 );
11581 },
11582 _ => quote!(),
11583 };
11584 let schema = quote! {{
11585 #sealed_guard
11586 #root::core::FieldSchema::from_parts(#root::core::FieldSchemaParts {
11587 name: #name,
11588 column: #column_lit,
11589 ty: #field_type_tokens,
11590 nullable: #nullable,
11591 primary_key: #primary_key,
11592 relation: #relation,
11593 max_length: #max_length,
11594 min: #min,
11595 max: #max,
11596 default: #default,
11597 auto: #auto,
11598 auto_now: #auto_now_flag,
11599 unique: #unique,
11600 generated_as: #generated_as,
11601 help_text: #help_text,
11602 choices: #choices,
11603 db_comment: #db_comment,
11604 verbose_name: #verbose_name,
11605 editable: #editable,
11606 blank: #blank,
11607 case_insensitive: #case_insensitive,
11608 fk_on_delete: #fk_on_delete,
11609 validators: &[ #( #root::core::__validator_names::#validators ),* ],
11610 cast: #cast,
11611 secret: #secret,
11612 })
11613 }};
11614
11615 let decimal_column = kind == DetectedKind::Decimal && !detected_auto && fk_inner.is_none();
11617 let (from_row_init, from_aliased_row_init) = if decimal_column {
11618 (
11619 quote! { #ident: #root::sql::try_get_flat(row, #column_lit)? },
11620 quote! {
11621 #ident: #root::sql::try_get_flat(
11622 row,
11623 #root::core::joins::joined_label(prefix, #column_lit).as_str(),
11624 )?
11625 },
11626 )
11627 } else {
11628 (
11629 quote! { #ident: #root::sql::sqlx::Row::try_get(row, #column_lit)? },
11630 quote! {
11631 #ident: #root::sql::sqlx::Row::try_get(
11632 row,
11633 #root::core::joins::joined_label(prefix, #column_lit).as_str(),
11634 )?
11635 },
11636 )
11637 };
11638 let uuid_column = kind == DetectedKind::Uuid && !detected_auto && fk_inner.is_none();
11641 let (from_row_init_my, from_aliased_row_init_my) = if uuid_column {
11642 (
11643 quote! { #ident: #root::sql::try_get_flat_my(row, #column_lit)? },
11644 quote! {
11645 #ident: #root::sql::try_get_flat_my(
11646 row,
11647 #root::core::joins::joined_label(prefix, #column_lit).as_str(),
11648 )?
11649 },
11650 )
11651 } else {
11652 (from_row_init.clone(), from_aliased_row_init.clone())
11653 };
11654
11655 Ok(FieldInfo {
11656 ident,
11657 column,
11658 primary_key,
11659 auto,
11660 value_ty: &field.ty,
11661 field_type_tokens,
11662 schema,
11663 from_row_init,
11664 from_aliased_row_init,
11665 from_row_init_my,
11666 from_aliased_row_init_my,
11667 uuid_column,
11668 fk_inner: fk_inner.cloned(),
11669 fk_pk_kind: kind,
11670 nullable,
11671 auto_now: attrs.auto_now,
11672 auto_now_add: attrs.auto_now_add,
11673 soft_delete: attrs.soft_delete,
11674 generated_as: attrs.generated_as.clone(),
11675 default_uuid_v7: attrs.default_uuid_v7,
11676 related_name: attrs.related_name.clone(),
11677 })
11678}
11679
11680fn check_bound_compatibility(
11681 field: &syn::Field,
11682 attrs: &FieldAttrs,
11683 kind: DetectedKind,
11684) -> syn::Result<()> {
11685 if attrs.max_length.is_some() && kind != DetectedKind::String {
11686 return Err(syn::Error::new_spanned(
11687 field,
11688 "`max_length` is only valid on `String` fields (or `Option<String>`)",
11689 ));
11690 }
11691 if attrs.case_insensitive && kind != DetectedKind::String {
11693 return Err(syn::Error::new_spanned(
11694 field,
11695 "`citext` is only valid on `String` fields (or `Option<String>`)",
11696 ));
11697 }
11698 if attrs.vector_dims.is_some() && kind != DetectedKind::Vector {
11699 return Err(syn::Error::new_spanned(
11700 field,
11701 "`vector(dims = …)` is only valid on a pgvector `Vector` field",
11702 ));
11703 }
11704 if attrs.geometry_srid.is_some() && kind != DetectedKind::Geometry {
11705 return Err(syn::Error::new_spanned(
11706 field,
11707 "`geometry(srid = …)` is only valid on a PostGIS `Point` field",
11708 ));
11709 }
11710 if attrs.choices.is_some() && kind != DetectedKind::String {
11711 return Err(syn::Error::new_spanned(
11712 field,
11713 "`choices` is only valid on `String` fields (or `Option<String>`) — \
11714 integer-valued enumerations should be modeled with a Rust enum and \
11715 custom (de)serializer for now",
11716 ));
11717 }
11718 if !attrs.validators.is_empty() && kind != DetectedKind::String {
11720 return Err(syn::Error::new_spanned(
11721 field,
11722 "`validators` is only valid on `String` fields (or `Option<String>`)",
11723 ));
11724 }
11725 if (attrs.min.is_some() || attrs.max.is_some()) && !kind.is_integer() {
11726 return Err(syn::Error::new_spanned(
11727 field,
11728 "`min` / `max` are only valid on integer fields (`i32`, `i64`, optionally Option-wrapped)",
11729 ));
11730 }
11731 if let (Some(min), Some(max)) = (attrs.min, attrs.max) {
11732 if min > max {
11733 return Err(syn::Error::new_spanned(
11734 field,
11735 format!("`min` ({min}) is greater than `max` ({max})"),
11736 ));
11737 }
11738 }
11739 if let Some(default) = &attrs.default {
11740 check_default_expr(default, kind).map_err(|msg| syn::Error::new_spanned(field, msg))?;
11741 }
11742 Ok(())
11743}
11744
11745const DEFAULT_KEYWORDS: &[&str] = &[
11748 "NULL",
11749 "TRUE",
11750 "FALSE",
11751 "CURRENT_TIMESTAMP",
11752 "CURRENT_DATE",
11753 "CURRENT_TIME",
11754 "LOCALTIMESTAMP",
11755 "LOCALTIME",
11756];
11757
11758fn check_default_expr(default: &str, kind: DetectedKind) -> Result<(), String> {
11762 let expr = default.trim();
11763 let is_word = expr.starts_with(|c: char| c.is_ascii_alphabetic() || c == '_')
11764 && expr
11765 .chars()
11766 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == ' ');
11767 let upper = expr.to_ascii_uppercase();
11768 if is_word && ["CURRENT_USER", "SESSION_USER", "USER"].contains(&upper.as_str()) {
11769 return Err(format!(
11770 "`default = \"{expr}\"` works on Postgres only: MySQL refuses it as a default \
11771 and SQLite stores the text `{expr}`; set the value in code instead"
11772 ));
11773 }
11774 if is_word && !DEFAULT_KEYWORDS.contains(&upper.as_str()) {
11775 return Err(format!(
11776 "`default = \"{expr}\"` is raw SQL, so `{expr}` is read as a column name; \
11777 for the text '{expr}' write `default = \"'{expr}'\"`"
11778 ));
11779 }
11780 let numeric = kind.is_integer()
11781 || matches!(
11782 kind,
11783 DetectedKind::F32 | DetectedKind::F64 | DetectedKind::Decimal
11784 );
11785 if numeric {
11786 let unquoted = expr
11787 .strip_prefix('\'')
11788 .and_then(|e| e.strip_suffix('\''))
11789 .unwrap_or(expr);
11790 let is_number = if kind.is_integer() {
11791 unquoted.parse::<i64>().is_ok()
11792 } else {
11793 unquoted.parse::<f64>().is_ok_and(f64::is_finite)
11794 };
11795 let is_literal = is_word || expr.is_empty() || expr.starts_with('\'');
11798 if is_literal && !is_number && upper != "NULL" {
11799 return Err(format!(
11800 "`default = \"{expr}\"` is not a number, but the field is numeric"
11801 ));
11802 }
11803 }
11804 Ok(())
11805}
11806
11807fn const_slice(ty: &TokenStream2, items: impl Iterator<Item = TokenStream2>) -> TokenStream2 {
11810 quote!({
11811 const ITEMS: &[#ty] = &[ #(#items),* ];
11812 ITEMS
11813 })
11814}
11815
11816fn optional_u32(value: Option<u32>) -> TokenStream2 {
11817 if let Some(v) = value {
11818 quote!(::core::option::Option::Some(#v))
11819 } else {
11820 quote!(::core::option::Option::None)
11821 }
11822}
11823
11824fn optional_i64(value: Option<i64>) -> TokenStream2 {
11825 if let Some(v) = value {
11826 quote!(::core::option::Option::Some(#v))
11827 } else {
11828 quote!(::core::option::Option::None)
11829 }
11830}
11831
11832fn optional_str(value: Option<&str>) -> TokenStream2 {
11833 if let Some(v) = value {
11834 quote!(::core::option::Option::Some(#v))
11835 } else {
11836 quote!(::core::option::Option::None)
11837 }
11838}
11839
11840fn optional_choices(pairs: Option<&[(String, String)]>) -> TokenStream2 {
11841 let Some(pairs) = pairs else {
11842 return quote!(::core::option::Option::None);
11843 };
11844 let entries = pairs.iter().map(|(v, l)| quote!((#v, #l)));
11845 quote!(::core::option::Option::Some(&[#(#entries),*]))
11846}
11847
11848fn relation_tokens(
11849 field: &syn::Field,
11850 attrs: &FieldAttrs,
11851 fk_inner: Option<&syn::Type>,
11852 table: &str,
11853) -> syn::Result<TokenStream2> {
11854 let root = rustango_root();
11855 if let Some(inner) = fk_inner {
11856 if attrs.fk.is_some() || attrs.o2o.is_some() {
11857 return Err(syn::Error::new_spanned(
11858 field,
11859 "`ForeignKey<T>` already declares the FK target via the type parameter — \
11860 remove the `fk = \"…\"` / `o2o = \"…\"` attribute.",
11861 ));
11862 }
11863 let on = match attrs.on.as_deref() {
11865 Some(on) => quote!(#on),
11866 None => quote! {
11867 match <#inner as #root::core::Model>::SCHEMA.pk_column() {
11868 ::core::option::Option::Some(c) => c,
11869 ::core::option::Option::None => "id",
11870 }
11871 },
11872 };
11873 return Ok(quote! {
11874 ::core::option::Option::Some(#root::core::Relation::fk(
11875 <#inner as #root::core::Model>::SCHEMA.table,
11876 #on,
11877 ))
11878 });
11879 }
11880 match (&attrs.fk, &attrs.o2o) {
11881 (Some(_), Some(_)) => Err(syn::Error::new_spanned(
11882 field,
11883 "`fk` and `o2o` are mutually exclusive",
11884 )),
11885 (Some(to), None) => {
11886 let on = attrs.on.as_deref().unwrap_or("id");
11887 let resolved = if to == "self" { table } else { to };
11893 Ok(quote! {
11894 ::core::option::Option::Some(#root::core::Relation::fk(#resolved, #on))
11895 })
11896 }
11897 (None, Some(to)) => {
11898 let on = attrs.on.as_deref().unwrap_or("id");
11899 let resolved = if to == "self" { table } else { to };
11900 Ok(quote! {
11901 ::core::option::Option::Some(#root::core::Relation::o2o(#resolved, #on))
11902 })
11903 }
11904 (None, None) => {
11905 if attrs.on.is_some() {
11906 return Err(syn::Error::new_spanned(
11907 field,
11908 "`on` requires `fk` or `o2o`",
11909 ));
11910 }
11911 Ok(quote!(::core::option::Option::None))
11912 }
11913 }
11914}
11915
11916#[derive(Clone, Copy, PartialEq, Eq)]
11920enum DetectedKind {
11921 I16,
11922 I32,
11923 I64,
11924 F32,
11925 F64,
11926 Bool,
11927 String,
11928 DateTime,
11929 Date,
11930 Time,
11931 Uuid,
11932 Json,
11933 Decimal,
11934 Binary,
11935 ArrayText,
11937 ArrayInt,
11939 ArrayBigInt,
11941 RangeInt,
11943 RangeBigInt,
11945 RangeNumeric,
11947 RangeDate,
11949 RangeDateTime,
11951 HStore,
11953 Vector,
11957 Geometry,
11961}
11962
11963impl DetectedKind {
11964 fn variant_tokens(self) -> TokenStream2 {
11965 let root = rustango_root();
11966 match self {
11967 Self::I16 => quote!(#root::core::FieldType::I16),
11968 Self::I32 => quote!(#root::core::FieldType::I32),
11969 Self::I64 => quote!(#root::core::FieldType::I64),
11970 Self::F32 => quote!(#root::core::FieldType::F32),
11971 Self::F64 => quote!(#root::core::FieldType::F64),
11972 Self::Bool => quote!(#root::core::FieldType::Bool),
11973 Self::String => quote!(#root::core::FieldType::String),
11974 Self::DateTime => quote!(#root::core::FieldType::DateTime),
11975 Self::Date => quote!(#root::core::FieldType::Date),
11976 Self::Time => quote!(#root::core::FieldType::Time),
11977 Self::Uuid => quote!(#root::core::FieldType::Uuid),
11978 Self::Json => quote!(#root::core::FieldType::Json),
11979 Self::Decimal => quote!(#root::core::FieldType::Decimal),
11980 Self::Binary => quote!(#root::core::FieldType::Binary),
11981 Self::ArrayText => {
11982 quote!(#root::core::FieldType::Array(#root::core::ArrayElem::Text))
11983 }
11984 Self::ArrayInt => {
11985 quote!(#root::core::FieldType::Array(#root::core::ArrayElem::Int))
11986 }
11987 Self::ArrayBigInt => {
11988 quote!(#root::core::FieldType::Array(#root::core::ArrayElem::BigInt))
11989 }
11990 Self::RangeInt => {
11991 quote!(#root::core::FieldType::Range(#root::core::RangeElem::Int))
11992 }
11993 Self::RangeBigInt => {
11994 quote!(#root::core::FieldType::Range(#root::core::RangeElem::BigInt))
11995 }
11996 Self::RangeNumeric => {
11997 quote!(#root::core::FieldType::Range(#root::core::RangeElem::Numeric))
11998 }
11999 Self::RangeDate => {
12000 quote!(#root::core::FieldType::Range(#root::core::RangeElem::Date))
12001 }
12002 Self::RangeDateTime => {
12003 quote!(#root::core::FieldType::Range(#root::core::RangeElem::DateTime))
12004 }
12005 Self::HStore => quote!(#root::core::FieldType::HStore),
12006 Self::Vector => quote!(#root::core::FieldType::Vector(0)),
12009 Self::Geometry => quote!(#root::core::FieldType::Geometry(0)),
12012 }
12013 }
12014
12015 fn is_integer(self) -> bool {
12016 matches!(self, Self::I16 | Self::I32 | Self::I64)
12017 }
12018}
12019
12020#[derive(Clone, Copy)]
12026struct DetectedType<'a> {
12027 kind: DetectedKind,
12028 nullable: bool,
12029 auto: bool,
12030 fk_inner: Option<&'a syn::Type>,
12031}
12032
12033fn auto_inner_type(ty: &syn::Type) -> Option<&syn::Type> {
12038 let Type::Path(TypePath { path, qself: None }) = ty else {
12039 return None;
12040 };
12041 let last = path.segments.last()?;
12042 if last.ident != "Auto" {
12043 return None;
12044 }
12045 let syn::PathArguments::AngleBracketed(args) = &last.arguments else {
12046 return None;
12047 };
12048 args.args.iter().find_map(|a| match a {
12049 syn::GenericArgument::Type(t) => Some(t),
12050 _ => None,
12051 })
12052}
12053
12054fn cast_type(ty: &syn::Type) -> Option<&syn::Type> {
12056 let Type::Path(TypePath { path, qself: None }) = ty else {
12057 return None;
12058 };
12059 let last = path.segments.last()?;
12060 if last.ident == "Cast" {
12061 return Some(ty);
12062 }
12063 if last.ident != "Option" {
12064 return None;
12065 }
12066 let syn::PathArguments::AngleBracketed(args) = &last.arguments else {
12067 return None;
12068 };
12069 match args.args.first()? {
12070 syn::GenericArgument::Type(inner) => cast_type(inner).filter(|_| args.args.len() == 1),
12071 _ => None,
12072 }
12073}
12074
12075fn detect_type(ty: &syn::Type) -> syn::Result<DetectedType<'_>> {
12076 let Type::Path(TypePath { path, qself: None }) = ty else {
12077 return Err(syn::Error::new_spanned(ty, "unsupported field type"));
12078 };
12079 let last = path
12080 .segments
12081 .last()
12082 .ok_or_else(|| syn::Error::new_spanned(ty, "empty type path"))?;
12083
12084 if last.ident == "Option" {
12085 let inner = generic_inner(ty, &last.arguments, "Option")?;
12086 let inner_det = detect_type(inner)?;
12087 if inner_det.nullable {
12088 return Err(syn::Error::new_spanned(
12089 ty,
12090 "nested Option is not supported",
12091 ));
12092 }
12093 if inner_det.auto {
12094 return Err(syn::Error::new_spanned(
12095 ty,
12096 "`Option<Auto<T>>` is not supported — Auto fields are server-assigned and cannot be NULL",
12097 ));
12098 }
12099 return Ok(DetectedType {
12100 nullable: true,
12101 ..inner_det
12102 });
12103 }
12104
12105 if last.ident == "Auto" {
12106 let inner = generic_inner(ty, &last.arguments, "Auto")?;
12107 let inner_det = detect_type(inner)?;
12108 if inner_det.auto {
12109 return Err(syn::Error::new_spanned(ty, "nested Auto is not supported"));
12110 }
12111 if inner_det.nullable {
12112 return Err(syn::Error::new_spanned(
12113 ty,
12114 "`Auto<Option<T>>` is not supported — Auto fields are server-assigned and cannot be NULL",
12115 ));
12116 }
12117 if inner_det.fk_inner.is_some() {
12118 return Err(syn::Error::new_spanned(
12119 ty,
12120 "`Auto<ForeignKey<T>>` is not supported — Auto is for server-assigned PKs, ForeignKey is for parent references",
12121 ));
12122 }
12123 if !matches!(
12124 inner_det.kind,
12125 DetectedKind::I32 | DetectedKind::I64 | DetectedKind::Uuid | DetectedKind::DateTime
12126 ) {
12127 return Err(syn::Error::new_spanned(
12128 ty,
12129 "`Auto<T>` only supports integers (`i32` → SERIAL, `i64` → BIGSERIAL), \
12130 `uuid::Uuid` (DEFAULT gen_random_uuid()), or `chrono::DateTime<chrono::Utc>` \
12131 (DEFAULT now())",
12132 ));
12133 }
12134 return Ok(DetectedType {
12135 auto: true,
12136 ..inner_det
12137 });
12138 }
12139
12140 if last.ident == "ForeignKey" {
12141 let (inner, key_ty) = generic_pair(ty, &last.arguments, "ForeignKey")?;
12142 let kind = match key_ty {
12150 Some(k) => detect_type(k)?.kind,
12151 None => DetectedKind::I64,
12152 };
12153 return Ok(DetectedType {
12154 kind,
12155 nullable: false,
12156 auto: false,
12157 fk_inner: Some(inner),
12158 });
12159 }
12160
12161 let kind = match last.ident.to_string().as_str() {
12162 "i16" => DetectedKind::I16,
12163 "i32" => DetectedKind::I32,
12164 "i64" => DetectedKind::I64,
12165 "f32" => DetectedKind::F32,
12166 "f64" => DetectedKind::F64,
12167 "bool" => DetectedKind::Bool,
12168 "String" => DetectedKind::String,
12169 "DateTime" => DetectedKind::DateTime,
12170 "NaiveDate" => DetectedKind::Date,
12171 "NaiveTime" => DetectedKind::Time,
12172 "Uuid" => DetectedKind::Uuid,
12173 "Value" => DetectedKind::Json,
12174 "Decimal" => DetectedKind::Decimal,
12175 "Vec" => {
12179 let (inner, _) = generic_pair(ty, &last.arguments, "Vec")?;
12180 if let Type::Path(TypePath { path, qself: None }) = inner {
12181 if let Some(seg) = path.segments.last() {
12182 if seg.ident == "u8" && seg.arguments.is_empty() {
12183 return Ok(DetectedType {
12184 kind: DetectedKind::Binary,
12185 nullable: false,
12186 auto: false,
12187 fk_inner: None,
12188 });
12189 }
12190 }
12191 }
12192 return Err(syn::Error::new_spanned(
12193 ty,
12194 "unsupported `Vec<T>` field — only `Vec<u8>` (→ Binary) is supported; \
12195 for a PostgreSQL array column use `Array<String>` / `Array<i32>` / `Array<i64>`",
12196 ));
12197 }
12198 "Array" => {
12201 let (inner, _) = generic_pair(ty, &last.arguments, "Array")?;
12202 let elem = match inner {
12203 Type::Path(TypePath { path, qself: None }) => {
12204 path.segments.last().map(|s| s.ident.to_string())
12205 }
12206 _ => None,
12207 };
12208 let kind = match elem.as_deref() {
12209 Some("String") => DetectedKind::ArrayText,
12210 Some("i32") => DetectedKind::ArrayInt,
12211 Some("i64") => DetectedKind::ArrayBigInt,
12212 _ => {
12213 return Err(syn::Error::new_spanned(
12214 ty,
12215 "unsupported `Array<T>` element — only `Array<String>` (→ text[]), \
12216 `Array<i32>` (→ integer[]), and `Array<i64>` (→ bigint[]) are supported (#341)",
12217 ));
12218 }
12219 };
12220 return Ok(DetectedType {
12221 kind,
12222 nullable: false,
12223 auto: false,
12224 fk_inner: None,
12225 });
12226 }
12227 "Range" => {
12231 let (inner, _) = generic_pair(ty, &last.arguments, "Range")?;
12232 let elem = match inner {
12233 Type::Path(TypePath { path, qself: None }) => {
12234 path.segments.last().map(|s| s.ident.to_string())
12235 }
12236 _ => None,
12237 };
12238 let kind = match elem.as_deref() {
12239 Some("i32") => DetectedKind::RangeInt,
12240 Some("i64") => DetectedKind::RangeBigInt,
12241 Some("Decimal") => DetectedKind::RangeNumeric,
12242 Some("NaiveDate") => DetectedKind::RangeDate,
12243 Some("DateTime") => DetectedKind::RangeDateTime,
12244 _ => {
12245 return Err(syn::Error::new_spanned(
12246 ty,
12247 "unsupported `Range<T>` element — only `Range<i32>` (→ int4range), \
12248 `Range<i64>` (→ int8range), `Range<Decimal>` (→ numrange), \
12249 `Range<NaiveDate>` (→ daterange), and `Range<DateTime<Utc>>` \
12250 (→ tstzrange) are supported (#343)",
12251 ));
12252 }
12253 };
12254 return Ok(DetectedType {
12255 kind,
12256 nullable: false,
12257 auto: false,
12258 fk_inner: None,
12259 });
12260 }
12261 "Cast" => {
12266 return Ok(DetectedType {
12267 kind: DetectedKind::String,
12268 nullable: false,
12269 auto: false,
12270 fk_inner: None,
12271 });
12272 }
12273 "HStore" => {
12276 return Ok(DetectedType {
12277 kind: DetectedKind::HStore,
12278 nullable: false,
12279 auto: false,
12280 fk_inner: None,
12281 });
12282 }
12283 "Vector" => {
12286 return Ok(DetectedType {
12287 kind: DetectedKind::Vector,
12288 nullable: false,
12289 auto: false,
12290 fk_inner: None,
12291 });
12292 }
12293 "Point" => {
12296 return Ok(DetectedType {
12297 kind: DetectedKind::Geometry,
12298 nullable: false,
12299 auto: false,
12300 fk_inner: None,
12301 });
12302 }
12303 other => {
12304 return Err(syn::Error::new_spanned(
12305 ty,
12306 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)"),
12307 ));
12308 }
12309 };
12310 Ok(DetectedType {
12311 kind,
12312 nullable: false,
12313 auto: false,
12314 fk_inner: None,
12315 })
12316}
12317
12318fn generic_inner<'a>(
12319 ty: &'a Type,
12320 arguments: &'a PathArguments,
12321 wrapper: &str,
12322) -> syn::Result<&'a Type> {
12323 let PathArguments::AngleBracketed(args) = arguments else {
12324 return Err(syn::Error::new_spanned(
12325 ty,
12326 format!("{wrapper} requires a generic argument"),
12327 ));
12328 };
12329 args.args
12330 .iter()
12331 .find_map(|a| match a {
12332 GenericArgument::Type(t) => Some(t),
12333 _ => None,
12334 })
12335 .ok_or_else(|| {
12336 syn::Error::new_spanned(ty, format!("{wrapper}<T> requires a type argument"))
12337 })
12338}
12339
12340fn generic_pair<'a>(
12344 ty: &'a Type,
12345 arguments: &'a PathArguments,
12346 wrapper: &str,
12347) -> syn::Result<(&'a Type, Option<&'a Type>)> {
12348 let PathArguments::AngleBracketed(args) = arguments else {
12349 return Err(syn::Error::new_spanned(
12350 ty,
12351 format!("{wrapper} requires a generic argument"),
12352 ));
12353 };
12354 let mut types = args.args.iter().filter_map(|a| match a {
12355 GenericArgument::Type(t) => Some(t),
12356 _ => None,
12357 });
12358 let first = types.next().ok_or_else(|| {
12359 syn::Error::new_spanned(ty, format!("{wrapper}<T> requires a type argument"))
12360 })?;
12361 let second = types.next();
12362 Ok((first, second))
12363}
12364
12365fn to_snake_case(s: &str) -> String {
12366 let mut out = String::with_capacity(s.len() + 4);
12367 for (i, ch) in s.chars().enumerate() {
12368 if ch.is_ascii_uppercase() {
12369 if i > 0 {
12370 out.push('_');
12371 }
12372 out.push(ch.to_ascii_lowercase());
12373 } else {
12374 out.push(ch);
12375 }
12376 }
12377 out
12378}
12379
12380#[derive(Default)]
12386struct FormFieldAttrs {
12387 min: Option<i64>,
12388 max: Option<i64>,
12389 min_length: Option<u32>,
12390 max_length: Option<u32>,
12391 clean: Option<syn::Ident>,
12398}
12399
12400#[derive(Default)]
12403struct FormContainerAttrs {
12404 validate: Option<syn::Ident>,
12410}
12411
12412#[derive(Clone, Copy)]
12414enum FormFieldKind {
12415 String,
12416 I16,
12417 I32,
12418 I64,
12419 F32,
12420 F64,
12421 Bool,
12422}
12423
12424impl FormFieldKind {
12425 fn parse_method(self) -> &'static str {
12426 match self {
12427 Self::I16 => "i16",
12428 Self::I32 => "i32",
12429 Self::I64 => "i64",
12430 Self::F32 => "f32",
12431 Self::F64 => "f64",
12432 Self::String | Self::Bool => "",
12435 }
12436 }
12437}
12438
12439fn form_local(name: &str) -> syn::Ident {
12441 syn::Ident::new(name, proc_macro2::Span::mixed_site())
12442}
12443
12444fn expand_form(input: &DeriveInput) -> syn::Result<TokenStream2> {
12445 let root = rustango_root();
12446 let (errs, cleaned, data_param) = (
12447 form_local("__errors"),
12448 form_local("__cleaned"),
12449 form_local("data"),
12450 );
12451 let struct_name = &input.ident;
12452
12453 let Data::Struct(data) = &input.data else {
12454 return Err(syn::Error::new_spanned(
12455 struct_name,
12456 "Form can only be derived on structs",
12457 ));
12458 };
12459 let Fields::Named(named) = &data.fields else {
12460 return Err(syn::Error::new_spanned(
12461 struct_name,
12462 "Form requires a struct with named fields",
12463 ));
12464 };
12465
12466 let container = parse_form_container_attrs(input)?;
12468 let post_field_clean: Vec<TokenStream2> = Vec::new();
12469 let _ = post_field_clean;
12470
12471 let mut field_blocks: Vec<TokenStream2> = Vec::with_capacity(named.named.len());
12472 let mut field_idents: Vec<&syn::Ident> = Vec::with_capacity(named.named.len());
12473
12474 for field in &named.named {
12475 let ident = field
12476 .ident
12477 .as_ref()
12478 .ok_or_else(|| syn::Error::new(field.span(), "tuple structs are not supported"))?;
12479 let attrs = parse_form_field_attrs(field)?;
12480 let (kind, nullable) = detect_form_field(&field.ty, field.span())?;
12481 let is_string = matches!(kind, FormFieldKind::String);
12482 let is_number = !is_string && !matches!(kind, FormFieldKind::Bool);
12483 if !is_string && (attrs.min_length.is_some() || attrs.max_length.is_some()) {
12484 return Err(syn::Error::new(
12485 field.span(),
12486 "`min_length` / `max_length` need a String field",
12487 ));
12488 }
12489 if !is_number && (attrs.min.is_some() || attrs.max.is_some()) {
12490 return Err(syn::Error::new(
12491 field.span(),
12492 "`min` / `max` need a numeric field",
12493 ));
12494 }
12495
12496 let name_lit = ident_name(ident);
12497 let parse_block = render_form_field_parse(ident, &name_lit, kind, nullable, &attrs);
12498 let clean_block = if let Some(clean_fn) = &attrs.clean {
12504 quote! {
12505 if #errs.fields().get(#name_lit).is_none() {
12506 match Self::#clean_fn(&#ident) {
12507 ::core::result::Result::Ok(#cleaned) => { #ident = #cleaned; }
12508 ::core::result::Result::Err(__msg) => {
12509 #errs.add(#name_lit, __msg);
12510 }
12511 }
12512 }
12513 }
12514 } else {
12515 quote! {}
12516 };
12517 field_blocks.push(quote! {
12518 #parse_block
12519 #clean_block
12520 });
12521 field_idents.push(ident);
12522 }
12523
12524 let cross_field_call = if let Some(validate_fn) = &container.validate {
12529 quote! {
12530 if #errs.is_empty() {
12531 let __candidate = Self { #( #field_idents ),* };
12532 if let ::core::result::Result::Err(__other) = Self::#validate_fn(&__candidate) {
12533 #errs.merge(__other);
12534 }
12535 if !#errs.is_empty() {
12536 return ::core::result::Result::Err(#errs);
12537 }
12538 return ::core::result::Result::Ok(__candidate);
12539 }
12540 }
12541 } else {
12542 quote! {}
12543 };
12544
12545 Ok(quote! {
12546 impl #root::forms::Form for #struct_name {
12547 fn parse(
12548 #data_param: &::std::collections::HashMap<::std::string::String, ::std::string::String>,
12549 ) -> ::core::result::Result<Self, #root::forms::FormErrors> {
12550 let mut #errs = #root::forms::FormErrors::default();
12551 #( #field_blocks )*
12552 #cross_field_call
12553 if !#errs.is_empty() {
12554 return ::core::result::Result::Err(#errs);
12555 }
12556 ::core::result::Result::Ok(Self {
12557 #( #field_idents ),*
12558 })
12559 }
12560 }
12561 })
12562}
12563
12564fn parse_form_container_attrs(input: &DeriveInput) -> syn::Result<FormContainerAttrs> {
12565 let mut out = FormContainerAttrs::default();
12566 for attr in &input.attrs {
12567 if !attr.path().is_ident("form") {
12568 continue;
12569 }
12570 attr.parse_nested_meta(|meta| {
12571 if meta.path.is_ident("validate") {
12572 let s: LitStr = meta.value()?.parse()?;
12573 out.validate = Some(syn::Ident::new(&s.value(), s.span()));
12574 return Ok(());
12575 }
12576 Err(meta.error("unknown form container attribute (supported: `validate`)"))
12577 })?;
12578 }
12579 Ok(out)
12580}
12581
12582fn parse_form_field_attrs(field: &syn::Field) -> syn::Result<FormFieldAttrs> {
12583 let mut out = FormFieldAttrs::default();
12584 for attr in &field.attrs {
12585 if !attr.path().is_ident("form") {
12586 continue;
12587 }
12588 attr.parse_nested_meta(|meta| {
12589 if meta.path.is_ident("min") {
12590 let lit: syn::LitInt = meta.value()?.parse()?;
12591 out.min = Some(lit.base10_parse::<i64>()?);
12592 return Ok(());
12593 }
12594 if meta.path.is_ident("max") {
12595 let lit: syn::LitInt = meta.value()?.parse()?;
12596 out.max = Some(lit.base10_parse::<i64>()?);
12597 return Ok(());
12598 }
12599 if meta.path.is_ident("min_length") {
12600 let lit: syn::LitInt = meta.value()?.parse()?;
12601 out.min_length = Some(lit.base10_parse::<u32>()?);
12602 return Ok(());
12603 }
12604 if meta.path.is_ident("max_length") {
12605 let lit: syn::LitInt = meta.value()?.parse()?;
12606 out.max_length = Some(lit.base10_parse::<u32>()?);
12607 return Ok(());
12608 }
12609 if meta.path.is_ident("clean") {
12610 let s: LitStr = meta.value()?.parse()?;
12611 out.clean = Some(syn::Ident::new(&s.value(), s.span()));
12612 return Ok(());
12613 }
12614 Err(meta.error(
12615 "unknown form field attribute (supported: `min`, `max`, `min_length`, `max_length`, `clean`)",
12616 ))
12617 })?;
12618 }
12619 Ok(out)
12620}
12621
12622fn detect_form_field(ty: &Type, span: proc_macro2::Span) -> syn::Result<(FormFieldKind, bool)> {
12623 let Type::Path(TypePath { path, qself: None }) = ty else {
12624 return Err(syn::Error::new(
12625 span,
12626 "Form field must be a simple typed path (e.g. `String`, `i32`, `Option<String>`)",
12627 ));
12628 };
12629 let last = path
12630 .segments
12631 .last()
12632 .ok_or_else(|| syn::Error::new(span, "empty type path"))?;
12633
12634 if last.ident == "Option" {
12635 let inner = generic_inner(ty, &last.arguments, "Option")?;
12636 let (kind, nested) = detect_form_field(inner, span)?;
12637 if nested {
12638 return Err(syn::Error::new(
12639 span,
12640 "nested Option in Form fields is not supported",
12641 ));
12642 }
12643 return Ok((kind, true));
12644 }
12645
12646 let kind = match last.ident.to_string().as_str() {
12647 "String" => FormFieldKind::String,
12648 "i16" => FormFieldKind::I16,
12649 "i32" => FormFieldKind::I32,
12650 "i64" => FormFieldKind::I64,
12651 "f32" => FormFieldKind::F32,
12652 "f64" => FormFieldKind::F64,
12653 "bool" => FormFieldKind::Bool,
12654 other => {
12655 return Err(syn::Error::new(
12656 span,
12657 format!(
12658 "Form field type `{other}` is not supported in v0.8 — use String / \
12659 i16 / i32 / i64 / f32 / f64 / bool, optionally wrapped in Option<…>"
12660 ),
12661 ));
12662 }
12663 };
12664 Ok((kind, false))
12665}
12666
12667#[allow(clippy::too_many_lines)]
12668fn render_form_field_parse(
12669 ident: &syn::Ident,
12670 name_lit: &str,
12671 kind: FormFieldKind,
12672 nullable: bool,
12673 attrs: &FormFieldAttrs,
12674) -> TokenStream2 {
12675 let (errs, data_param) = (form_local("__errors"), form_local("data"));
12676 let lookup = quote! {
12678 let __raw: ::core::option::Option<&::std::string::String> = #data_param.get(#name_lit);
12679 };
12680
12681 let root = rustango_root();
12682 let checkbox = quote! {
12684 match #root::forms::checkbox(__s) {
12685 ::core::option::Option::Some(__b) => __b,
12686 ::core::option::Option::None => {
12687 #errs.add(#name_lit, "Enter true or false.");
12688 false
12689 }
12690 }
12691 };
12692 let parsed_value = match kind {
12693 FormFieldKind::Bool if nullable => quote! {
12695 let __v: ::core::option::Option<bool> = match __raw {
12696 ::core::option::Option::None => ::core::option::Option::None,
12697 ::core::option::Option::Some(__s) if __s.is_empty() => {
12698 ::core::option::Option::None
12699 }
12700 ::core::option::Option::Some(__s) => ::core::option::Option::Some(#checkbox),
12701 };
12702 },
12703 FormFieldKind::Bool => quote! {
12704 let __v: bool = match __raw {
12705 ::core::option::Option::None => false,
12706 ::core::option::Option::Some(__s) => #checkbox,
12707 };
12708 },
12709 FormFieldKind::String => {
12710 if nullable {
12711 quote! {
12712 let __v: ::core::option::Option<::std::string::String> = match __raw {
12713 ::core::option::Option::None => ::core::option::Option::None,
12714 ::core::option::Option::Some(__s) if __s.is_empty() => {
12715 ::core::option::Option::None
12716 }
12717 ::core::option::Option::Some(__s) => {
12718 ::core::option::Option::Some(::core::clone::Clone::clone(__s))
12719 }
12720 };
12721 }
12722 } else {
12723 quote! {
12724 let __v: ::std::string::String = match __raw {
12725 ::core::option::Option::Some(__s) if !__s.is_empty() => {
12726 ::core::clone::Clone::clone(__s)
12727 }
12728 _ => {
12729 #errs.add(#name_lit, "This field is required.");
12730 ::std::string::String::new()
12731 }
12732 };
12733 }
12734 }
12735 }
12736 FormFieldKind::I16
12737 | FormFieldKind::I32
12738 | FormFieldKind::I64
12739 | FormFieldKind::F32
12740 | FormFieldKind::F64 => {
12741 let parse_ty = syn::Ident::new(kind.parse_method(), proc_macro2::Span::call_site());
12742 let ty_lit = kind.parse_method();
12743 let default_val = match kind {
12744 FormFieldKind::I16 => quote! { 0i16 },
12745 FormFieldKind::I32 => quote! { 0i32 },
12746 FormFieldKind::I64 => quote! { 0i64 },
12747 FormFieldKind::F32 => quote! { 0f32 },
12748 FormFieldKind::F64 => quote! { 0f64 },
12749 _ => quote! { Default::default() },
12750 };
12751 let finite = if matches!(kind, FormFieldKind::F32 | FormFieldKind::F64) {
12753 quote! {
12754 .and_then(|__n| if __n.is_finite() {
12755 ::core::result::Result::Ok(__n)
12756 } else {
12757 ::core::result::Result::Err(::std::string::String::from("not a finite number"))
12758 })
12759 }
12760 } else {
12761 quote! {}
12762 };
12763 if nullable {
12764 quote! {
12765 let __v: ::core::option::Option<#parse_ty> = match __raw {
12766 ::core::option::Option::None => ::core::option::Option::None,
12767 ::core::option::Option::Some(__s) if __s.is_empty() => {
12768 ::core::option::Option::None
12769 }
12770 ::core::option::Option::Some(__s) => {
12771 match __s.parse::<#parse_ty>().map_err(|__e| ::std::string::ToString::to_string(&__e)) #finite {
12772 ::core::result::Result::Ok(__n) => {
12773 ::core::option::Option::Some(__n)
12774 }
12775 ::core::result::Result::Err(__e) => {
12776 #errs.add(
12777 #name_lit,
12778 ::std::format!("Enter a valid {} value: {}", #ty_lit, __e),
12779 );
12780 ::core::option::Option::None
12781 }
12782 }
12783 }
12784 };
12785 }
12786 } else {
12787 quote! {
12788 let __v: #parse_ty = match __raw {
12789 ::core::option::Option::Some(__s) if !__s.is_empty() => {
12790 match __s.parse::<#parse_ty>().map_err(|__e| ::std::string::ToString::to_string(&__e)) #finite {
12791 ::core::result::Result::Ok(__n) => __n,
12792 ::core::result::Result::Err(__e) => {
12793 #errs.add(
12794 #name_lit,
12795 ::std::format!("Enter a valid {} value: {}", #ty_lit, __e),
12796 );
12797 #default_val
12798 }
12799 }
12800 }
12801 _ => {
12802 #errs.add(#name_lit, "This field is required.");
12803 #default_val
12804 }
12805 };
12806 }
12807 }
12808 }
12809 };
12810
12811 let validators = render_form_validators(name_lit, kind, nullable, attrs);
12812
12813 quote! {
12814 let mut #ident = {
12818 #lookup
12819 #parsed_value
12820 #validators
12821 __v
12822 };
12823 }
12824}
12825
12826fn render_form_validators(
12827 name_lit: &str,
12828 kind: FormFieldKind,
12829 nullable: bool,
12830 attrs: &FormFieldAttrs,
12831) -> TokenStream2 {
12832 let errs = form_local("__errors");
12833 let mut checks: Vec<TokenStream2> = Vec::new();
12834
12835 let val_ref = if nullable {
12836 quote! { __v.as_ref() }
12837 } else {
12838 quote! { ::core::option::Option::Some(&__v) }
12839 };
12840
12841 let is_string = matches!(kind, FormFieldKind::String);
12842 let is_numeric = matches!(
12843 kind,
12844 FormFieldKind::I16
12845 | FormFieldKind::I32
12846 | FormFieldKind::I64
12847 | FormFieldKind::F32
12848 | FormFieldKind::F64
12849 );
12850
12851 if is_string {
12852 if let Some(min_len) = attrs.min_length {
12853 let min_len_usize = min_len as usize;
12854 checks.push(quote! {
12855 if let ::core::option::Option::Some(__s) = #val_ref {
12856 if __s.chars().count() < #min_len_usize {
12857 #errs.add(
12858 #name_lit,
12859 ::std::format!("Ensure this value has at least {} characters.", #min_len_usize),
12860 );
12861 }
12862 }
12863 });
12864 }
12865 if let Some(max_len) = attrs.max_length {
12866 let max_len_usize = max_len as usize;
12867 checks.push(quote! {
12868 if let ::core::option::Option::Some(__s) = #val_ref {
12869 if __s.chars().count() > #max_len_usize {
12870 #errs.add(
12871 #name_lit,
12872 ::std::format!("Ensure this value has at most {} characters.", #max_len_usize),
12873 );
12874 }
12875 }
12876 });
12877 }
12878 }
12879
12880 if is_numeric {
12881 if let Some(min) = attrs.min {
12882 checks.push(quote! {
12883 if let ::core::option::Option::Some(__n) = #val_ref {
12884 if (*__n as f64) < (#min as f64) {
12885 #errs.add(
12886 #name_lit,
12887 ::std::format!("Ensure this value is greater than or equal to {}.", #min),
12888 );
12889 }
12890 }
12891 });
12892 }
12893 if let Some(max) = attrs.max {
12894 checks.push(quote! {
12895 if let ::core::option::Option::Some(__n) = #val_ref {
12896 if (*__n as f64) > (#max as f64) {
12897 #errs.add(
12898 #name_lit,
12899 ::std::format!("Ensure this value is less than or equal to {}.", #max),
12900 );
12901 }
12902 }
12903 });
12904 }
12905 }
12906
12907 quote! { #( #checks )* }
12908}
12909
12910struct ViewSetAttrs {
12915 model: syn::Path,
12916 fields: Vec<String>,
12917 filter_fields: Vec<String>,
12918 search_fields: Vec<String>,
12919 ordering: Vec<(String, bool)>,
12921 page_size: Option<usize>,
12922 read_only: bool,
12923 allow_anonymous: bool,
12924 perms: ViewSetPermsAttrs,
12925 serializer: Option<syn::Path>,
12930}
12931
12932#[derive(Default)]
12933struct ViewSetPermsAttrs {
12934 list: Vec<String>,
12935 retrieve: Vec<String>,
12936 create: Vec<String>,
12937 update: Vec<String>,
12938 destroy: Vec<String>,
12939}
12940
12941fn expand_viewset(input: &DeriveInput) -> syn::Result<TokenStream2> {
12942 let root = rustango_root();
12943 let struct_name = &input.ident;
12944
12945 match &input.data {
12947 Data::Struct(s) => match &s.fields {
12948 Fields::Unit | Fields::Named(_) => {}
12949 Fields::Unnamed(_) => {
12950 return Err(syn::Error::new_spanned(
12951 struct_name,
12952 "ViewSet can only be derived on a unit struct or an empty named struct",
12953 ));
12954 }
12955 },
12956 _ => {
12957 return Err(syn::Error::new_spanned(
12958 struct_name,
12959 "ViewSet can only be derived on a struct",
12960 ));
12961 }
12962 }
12963
12964 let attrs = parse_viewset_attrs(input)?;
12965 let model_path = &attrs.model;
12966
12967 let field_lits = attrs.fields.iter().map(|f| f.as_str());
12968
12969 let filter_fields_call = if attrs.filter_fields.is_empty() {
12970 quote!()
12971 } else {
12972 let lits = attrs.filter_fields.iter().map(|f| f.as_str());
12973 quote!(.filter_fields(&[ #(#lits),* ]))
12974 };
12975
12976 let search_fields_call = if attrs.search_fields.is_empty() {
12977 quote!()
12978 } else {
12979 let lits = attrs.search_fields.iter().map(|f| f.as_str());
12980 quote!(.search_fields(&[ #(#lits),* ]))
12981 };
12982
12983 let ordering_call = if attrs.ordering.is_empty() {
12984 quote!()
12985 } else {
12986 let pairs = attrs.ordering.iter().map(|(f, desc)| {
12987 let f = f.as_str();
12988 quote!((#f, #desc))
12989 });
12990 quote!(.ordering(&[ #(#pairs),* ]))
12991 };
12992
12993 let page_size_call = if let Some(n) = attrs.page_size {
12994 quote!(.page_size(#n))
12995 } else {
12996 quote!()
12997 };
12998
12999 let read_only_call = if attrs.read_only {
13000 quote!(.read_only())
13001 } else {
13002 quote!()
13003 };
13004 let allow_anonymous_call = if attrs.allow_anonymous {
13005 quote!(.allow_anonymous())
13006 } else {
13007 quote!()
13008 };
13009
13010 let serializer_call = if let Some(ref ser) = attrs.serializer {
13014 quote!(.serializer::<#ser>())
13015 } else {
13016 quote!()
13017 };
13018
13019 let perms = &attrs.perms;
13020 let perms_call = if perms.list.is_empty()
13021 && perms.retrieve.is_empty()
13022 && perms.create.is_empty()
13023 && perms.update.is_empty()
13024 && perms.destroy.is_empty()
13025 {
13026 quote!()
13027 } else {
13028 let list_lits = perms.list.iter().map(|s| s.as_str());
13029 let retrieve_lits = perms.retrieve.iter().map(|s| s.as_str());
13030 let create_lits = perms.create.iter().map(|s| s.as_str());
13031 let update_lits = perms.update.iter().map(|s| s.as_str());
13032 let destroy_lits = perms.destroy.iter().map(|s| s.as_str());
13033 quote! {
13034 .permissions(#root::viewset::ViewSetPerms {
13035 list: ::std::vec![ #(#list_lits.to_owned()),* ],
13036 retrieve: ::std::vec![ #(#retrieve_lits.to_owned()),* ],
13037 create: ::std::vec![ #(#create_lits.to_owned()),* ],
13038 update: ::std::vec![ #(#update_lits.to_owned()),* ],
13039 destroy: ::std::vec![ #(#destroy_lits.to_owned()),* ],
13040 })
13041 }
13042 };
13043
13044 Ok(quote! {
13045 impl #struct_name {
13046 pub fn router(
13055 prefix: &str,
13056 pool: impl ::core::convert::Into<#root::sql::Pool>,
13057 ) -> #root::__axum::Router {
13058 #root::viewset::ViewSet::for_model(
13059 <#model_path as #root::core::Model>::SCHEMA,
13060 &[ #(#field_lits),* ],
13061 )
13062 #filter_fields_call
13063 #search_fields_call
13064 #ordering_call
13065 #page_size_call
13066 #perms_call
13067 #read_only_call
13068 #allow_anonymous_call
13069 #serializer_call
13070 .router(prefix, pool.into())
13071 }
13072 }
13073 })
13074}
13075
13076fn parse_viewset_attrs(input: &DeriveInput) -> syn::Result<ViewSetAttrs> {
13077 let mut model: Option<syn::Path> = None;
13078 let mut fields: Option<Vec<String>> = None;
13079 let mut filter_fields: Vec<String> = Vec::new();
13080 let mut search_fields: Vec<String> = Vec::new();
13081 let mut ordering: Vec<(String, bool)> = Vec::new();
13082 let mut page_size: Option<usize> = None;
13083 let mut read_only = false;
13084 let mut allow_anonymous = false;
13085 let mut perms = ViewSetPermsAttrs::default();
13086 let mut serializer: Option<syn::Path> = None;
13087
13088 for attr in &input.attrs {
13089 if !attr.path().is_ident("viewset") {
13090 continue;
13091 }
13092 attr.parse_nested_meta(|meta| {
13093 if meta.path.is_ident("model") {
13094 let path: syn::Path = meta.value()?.parse()?;
13095 model = Some(path);
13096 return Ok(());
13097 }
13098 if meta.path.is_ident("serializer") {
13099 let path: syn::Path = meta.value()?.parse()?;
13100 serializer = Some(path);
13101 return Ok(());
13102 }
13103 if meta.path.is_ident("fields") {
13104 let s: LitStr = meta.value()?.parse()?;
13105 fields = Some(split_field_list(&s.value()));
13106 return Ok(());
13107 }
13108 if meta.path.is_ident("filter_fields") {
13109 let s: LitStr = meta.value()?.parse()?;
13110 filter_fields = split_field_list(&s.value());
13111 return Ok(());
13112 }
13113 if meta.path.is_ident("search_fields") {
13114 let s: LitStr = meta.value()?.parse()?;
13115 search_fields = split_field_list(&s.value());
13116 return Ok(());
13117 }
13118 if meta.path.is_ident("ordering") {
13119 let s: LitStr = meta.value()?.parse()?;
13120 ordering = parse_ordering_list(&s.value());
13121 return Ok(());
13122 }
13123 if meta.path.is_ident("page_size") {
13124 let lit: syn::LitInt = meta.value()?.parse()?;
13125 page_size = Some(lit.base10_parse::<usize>()?);
13126 return Ok(());
13127 }
13128 if meta.path.is_ident("read_only") {
13129 read_only = true;
13130 return Ok(());
13131 }
13132 if meta.path.is_ident("allow_anonymous") {
13133 allow_anonymous = true;
13134 return Ok(());
13135 }
13136 if meta.path.is_ident("permissions") {
13137 meta.parse_nested_meta(|inner| {
13138 let parse_codenames = |inner: &syn::meta::ParseNestedMeta| -> syn::Result<Vec<String>> {
13139 let s: LitStr = inner.value()?.parse()?;
13140 Ok(split_field_list(&s.value()))
13141 };
13142 if inner.path.is_ident("list") {
13143 perms.list = parse_codenames(&inner)?;
13144 } else if inner.path.is_ident("retrieve") {
13145 perms.retrieve = parse_codenames(&inner)?;
13146 } else if inner.path.is_ident("create") {
13147 perms.create = parse_codenames(&inner)?;
13148 } else if inner.path.is_ident("update") {
13149 perms.update = parse_codenames(&inner)?;
13150 } else if inner.path.is_ident("destroy") {
13151 perms.destroy = parse_codenames(&inner)?;
13152 } else {
13153 return Err(inner.error(
13154 "unknown permissions key (supported: list, retrieve, create, update, destroy)",
13155 ));
13156 }
13157 Ok(())
13158 })?;
13159 return Ok(());
13160 }
13161 Err(meta.error(
13162 "unknown viewset attribute (supported: model, fields, filter_fields, \
13163 search_fields, ordering, page_size, read_only, allow_anonymous, serializer, \
13164 permissions(...))",
13165 ))
13166 })?;
13167 }
13168
13169 let model = model.ok_or_else(|| {
13170 syn::Error::new_spanned(&input.ident, "`#[viewset(model = SomeModel)]` is required")
13171 })?;
13172 let fields = fields.ok_or_else(|| {
13174 syn::Error::new_spanned(
13175 &input.ident,
13176 "`#[viewset(fields = \"id, title\")]` is required: the fields the API renders and writes",
13177 )
13178 })?;
13179
13180 Ok(ViewSetAttrs {
13181 model,
13182 fields,
13183 filter_fields,
13184 search_fields,
13185 ordering,
13186 page_size,
13187 read_only,
13188 allow_anonymous,
13189 perms,
13190 serializer,
13191 })
13192}
13193
13194struct SerializerContainerAttrs {
13197 model: syn::Path,
13198 cross_validate: Option<syn::Ident>,
13206}
13207
13208#[derive(Default)]
13209struct SerializerFieldAttrs {
13210 read_only: bool,
13211 write_only: bool,
13212 source: Option<String>,
13213 skip: bool,
13214 method: Option<String>,
13218 validate: Option<String>,
13223 nested: bool,
13233 nested_strict: bool,
13238 many: Option<syn::Type>,
13247 slug: Option<String>,
13258 max_length: Option<u64>,
13262 min_length: Option<u64>,
13265 min: Option<i64>,
13268 max: Option<i64>,
13271}
13272
13273fn parse_serializer_container_attrs(input: &DeriveInput) -> syn::Result<SerializerContainerAttrs> {
13274 let mut model: Option<syn::Path> = None;
13275 let mut cross_validate: Option<syn::Ident> = None;
13276 for attr in &input.attrs {
13277 if !attr.path().is_ident("serializer") {
13278 continue;
13279 }
13280 attr.parse_nested_meta(|meta| {
13281 if meta.path.is_ident("model") {
13282 let _eq: syn::Token![=] = meta.input.parse()?;
13283 model = Some(meta.input.parse()?);
13284 return Ok(());
13285 }
13286 if meta.path.is_ident("validate") {
13287 let s: LitStr = meta.value()?.parse()?;
13292 cross_validate = Some(syn::Ident::new(&s.value(), s.span()));
13293 return Ok(());
13294 }
13295 Err(meta.error(
13296 "unknown serializer container attribute \
13297 (supported: `model`, `validate`)",
13298 ))
13299 })?;
13300 }
13301 let model = model.ok_or_else(|| {
13302 syn::Error::new_spanned(
13303 &input.ident,
13304 "`#[serializer(model = SomeModel)]` is required",
13305 )
13306 })?;
13307 Ok(SerializerContainerAttrs {
13308 model,
13309 cross_validate,
13310 })
13311}
13312
13313#[derive(Default)]
13316struct SerdeWireAttrs {
13317 rename_ser: Option<String>,
13318 rename_de: Option<String>,
13319 skip_ser: bool,
13320 skip_de: bool,
13321 skip_ser_if: Option<syn::ExprPath>,
13322}
13323
13324#[derive(Clone, Copy)]
13326enum SerdeRenameRule {
13327 Lower,
13328 Upper,
13329 Pascal,
13330 Camel,
13331 ScreamingSnake,
13332 Kebab,
13333 ScreamingKebab,
13334}
13335
13336impl SerdeRenameRule {
13337 fn parse(s: &LitStr) -> syn::Result<Self> {
13338 Ok(match s.value().as_str() {
13339 "lowercase" | "snake_case" => Self::Lower,
13340 "UPPERCASE" => Self::Upper,
13341 "PascalCase" => Self::Pascal,
13342 "camelCase" => Self::Camel,
13343 "SCREAMING_SNAKE_CASE" => Self::ScreamingSnake,
13344 "kebab-case" => Self::Kebab,
13345 "SCREAMING-KEBAB-CASE" => Self::ScreamingKebab,
13346 _ => {
13347 return Err(syn::Error::new_spanned(
13348 s,
13349 "unknown serde `rename_all` rule",
13350 ))
13351 }
13352 })
13353 }
13354
13355 fn apply(self, field: &str) -> String {
13357 match self {
13358 Self::Lower => field.to_owned(),
13359 Self::Upper | Self::ScreamingSnake => field.to_ascii_uppercase(),
13360 Self::Pascal | Self::Camel => {
13361 let mut out = String::new();
13362 let mut cap = matches!(self, Self::Pascal);
13363 for ch in field.chars() {
13364 if ch == '_' {
13365 cap = !out.is_empty() || matches!(self, Self::Pascal);
13366 } else if cap {
13367 out.push(ch.to_ascii_uppercase());
13368 cap = false;
13369 } else {
13370 out.push(ch);
13371 }
13372 }
13373 out
13374 }
13375 Self::Kebab => field.replace('_', "-"),
13376 Self::ScreamingKebab => field.to_ascii_uppercase().replace('_', "-"),
13377 }
13378 }
13379}
13380
13381fn skip_serde_meta_value(meta: &syn::meta::ParseNestedMeta) -> syn::Result<()> {
13383 if meta.input.peek(syn::Token![=]) {
13384 let _: syn::Expr = meta.value()?.parse()?;
13385 } else if meta.input.peek(syn::token::Paren) {
13386 let _: proc_macro2::Group = meta.input.parse()?;
13387 }
13388 Ok(())
13389}
13390
13391fn parse_serde_rename(
13393 meta: &syn::meta::ParseNestedMeta,
13394 mut set: impl FnMut(bool, LitStr) -> syn::Result<()>,
13395) -> syn::Result<()> {
13396 if meta.input.peek(syn::Token![=]) {
13397 let s: LitStr = meta.value()?.parse()?;
13398 set(true, s.clone())?;
13399 return set(false, s);
13400 }
13401 meta.parse_nested_meta(|inner| {
13402 let is_ser = inner.path.is_ident("serialize");
13403 if !is_ser && !inner.path.is_ident("deserialize") {
13404 return Err(inner.error("expected `serialize` or `deserialize`"));
13405 }
13406 set(is_ser, inner.value()?.parse()?)
13407 })
13408}
13409
13410fn parse_serde_container_rules(
13412 input: &DeriveInput,
13413) -> syn::Result<(Option<SerdeRenameRule>, Option<SerdeRenameRule>)> {
13414 let (mut ser, mut de) = (None, None);
13415 for attr in input.attrs.iter().filter(|a| a.path().is_ident("serde")) {
13416 attr.parse_nested_meta(|meta| {
13417 if meta.path.is_ident("rename_all") {
13418 return parse_serde_rename(&meta, |is_ser, s| {
13419 let rule = Some(SerdeRenameRule::parse(&s)?);
13420 if is_ser {
13421 ser = rule;
13422 } else {
13423 de = rule;
13424 }
13425 Ok(())
13426 });
13427 }
13428 skip_serde_meta_value(&meta)
13429 })?;
13430 }
13431 Ok((ser, de))
13432}
13433
13434fn parse_serde_field_attrs(field: &syn::Field) -> syn::Result<SerdeWireAttrs> {
13435 let mut out = SerdeWireAttrs::default();
13436 for attr in field.attrs.iter().filter(|a| a.path().is_ident("serde")) {
13437 attr.parse_nested_meta(|meta| {
13438 if meta.path.is_ident("rename") {
13439 return parse_serde_rename(&meta, |is_ser, s| {
13440 if is_ser {
13441 out.rename_ser = Some(s.value());
13442 } else {
13443 out.rename_de = Some(s.value());
13444 }
13445 Ok(())
13446 });
13447 }
13448 if meta.path.is_ident("skip") {
13449 out.skip_ser = true;
13450 out.skip_de = true;
13451 return Ok(());
13452 }
13453 if meta.path.is_ident("skip_serializing") {
13454 out.skip_ser = true;
13455 return Ok(());
13456 }
13457 if meta.path.is_ident("skip_deserializing") {
13458 out.skip_de = true;
13459 return Ok(());
13460 }
13461 if meta.path.is_ident("skip_serializing_if") {
13462 let s: LitStr = meta.value()?.parse()?;
13463 out.skip_ser_if = Some(s.parse()?);
13464 return Ok(());
13465 }
13466 skip_serde_meta_value(&meta)
13467 })?;
13468 }
13469 Ok(out)
13470}
13471
13472fn parse_serializer_field_attrs(field: &syn::Field) -> syn::Result<SerializerFieldAttrs> {
13473 let mut out = SerializerFieldAttrs::default();
13474 for attr in &field.attrs {
13475 if !attr.path().is_ident("serializer") {
13476 continue;
13477 }
13478 attr.parse_nested_meta(|meta| {
13479 if meta.path.is_ident("read_only") {
13480 out.read_only = true;
13481 return Ok(());
13482 }
13483 if meta.path.is_ident("write_only") {
13484 out.write_only = true;
13485 return Ok(());
13486 }
13487 if meta.path.is_ident("skip") {
13488 out.skip = true;
13489 return Ok(());
13490 }
13491 if meta.path.is_ident("source") {
13492 let s: LitStr = meta.value()?.parse()?;
13493 out.source = Some(s.value());
13494 return Ok(());
13495 }
13496 if meta.path.is_ident("method") {
13497 let s: LitStr = meta.value()?.parse()?;
13498 out.method = Some(s.value());
13499 return Ok(());
13500 }
13501 if meta.path.is_ident("validate") {
13502 let s: LitStr = meta.value()?.parse()?;
13503 out.validate = Some(s.value());
13504 return Ok(());
13505 }
13506 if meta.path.is_ident("many") {
13507 let _eq: syn::Token![=] = meta.input.parse()?;
13508 out.many = Some(meta.input.parse()?);
13509 return Ok(());
13510 }
13511 if meta.path.is_ident("nested") {
13512 out.nested = true;
13513 if meta.input.peek(syn::token::Paren) {
13516 meta.parse_nested_meta(|inner| {
13517 if inner.path.is_ident("strict") {
13518 out.nested_strict = true;
13519 return Ok(());
13520 }
13521 Err(inner.error("unknown nested sub-attribute (supported: `strict`)"))
13522 })?;
13523 }
13524 return Ok(());
13525 }
13526 if meta.path.is_ident("slug") {
13527 let s: LitStr = meta.value()?.parse()?;
13528 out.slug = Some(s.value());
13529 return Ok(());
13530 }
13531 if meta.path.is_ident("max_length") {
13532 let lit: syn::LitInt = meta.value()?.parse()?;
13533 out.max_length = Some(lit.base10_parse::<u64>()?);
13534 return Ok(());
13535 }
13536 if meta.path.is_ident("min_length") {
13537 let lit: syn::LitInt = meta.value()?.parse()?;
13538 out.min_length = Some(lit.base10_parse::<u64>()?);
13539 return Ok(());
13540 }
13541 if meta.path.is_ident("min") {
13542 let lit: syn::LitInt = meta.value()?.parse()?;
13543 out.min = Some(lit.base10_parse::<i64>()?);
13544 return Ok(());
13545 }
13546 if meta.path.is_ident("max") {
13547 let lit: syn::LitInt = meta.value()?.parse()?;
13548 out.max = Some(lit.base10_parse::<i64>()?);
13549 return Ok(());
13550 }
13551 Err(meta.error(
13552 "unknown serializer field attribute (supported: \
13553 `read_only`, `write_only`, `source`, `skip`, `method`, \
13554 `validate`, `nested`, `many`, `slug`, `max_length`, \
13555 `min_length`, `min`, `max`)",
13556 ))
13557 })?;
13558 }
13559 if out.read_only && out.write_only {
13561 return Err(syn::Error::new_spanned(
13562 field,
13563 "a field cannot be both `read_only` and `write_only`",
13564 ));
13565 }
13566 if out.method.is_some() && out.source.is_some() {
13567 return Err(syn::Error::new_spanned(
13568 field,
13569 "`method` and `source` are mutually exclusive — `method` computes \
13570 the value from a method, `source` reads it from a different model field",
13571 ));
13572 }
13573 if out.slug.is_some() && (out.method.is_some() || out.nested || out.many.is_some()) {
13574 return Err(syn::Error::new_spanned(
13575 field,
13576 "`slug` is mutually exclusive with `method`, `nested`, and `many` \
13577 — pick one strategy for populating the field",
13578 ));
13579 }
13580 Ok(out)
13581}
13582
13583fn expand_serializer(input: &DeriveInput) -> syn::Result<TokenStream2> {
13584 let root = rustango_root();
13585 let struct_name = &input.ident;
13586 let struct_name_lit = struct_name.to_string();
13587
13588 let Data::Struct(data) = &input.data else {
13589 return Err(syn::Error::new_spanned(
13590 struct_name,
13591 "Serializer can only be derived on structs",
13592 ));
13593 };
13594 let Fields::Named(named) = &data.fields else {
13595 return Err(syn::Error::new_spanned(
13596 struct_name,
13597 "Serializer requires a struct with named fields",
13598 ));
13599 };
13600
13601 let container = parse_serializer_container_attrs(input)?;
13602 let model_path = &container.model;
13603
13604 #[allow(dead_code)]
13608 struct FieldInfo {
13609 ident: syn::Ident,
13610 ty: syn::Type,
13611 attrs: SerializerFieldAttrs,
13612 serde: SerdeWireAttrs,
13613 ser_name: String,
13615 de_name: String,
13616 }
13617 let (ser_rule, de_rule) = parse_serde_container_rules(input)?;
13618 let mut fields_info: Vec<FieldInfo> = Vec::new();
13619 for field in &named.named {
13620 let ident = field.ident.clone().expect("named field has ident");
13621 let attrs = parse_serializer_field_attrs(field)?;
13622 let serde = parse_serde_field_attrs(field)?;
13623 let name = ident_name(&ident);
13624 let wire = |rename: &Option<String>, rule: Option<SerdeRenameRule>| {
13625 rename
13626 .clone()
13627 .unwrap_or_else(|| rule.map_or_else(|| name.clone(), |r| r.apply(&name)))
13628 };
13629 fields_info.push(FieldInfo {
13630 ser_name: wire(&serde.rename_ser, ser_rule),
13631 de_name: wire(&serde.rename_de, de_rule),
13632 ident,
13633 ty: field.ty.clone(),
13634 attrs,
13635 serde,
13636 });
13637 }
13638
13639 let from_model_fields = fields_info.iter().map(|fi| {
13641 let ident = &fi.ident;
13642 if let Some(_inner) = &fi.attrs.many {
13643 quote! { #ident: ::std::vec::Vec::new() }
13647 } else if let Some(method) = &fi.attrs.method {
13648 let method_ident = syn::Ident::new(method, ident.span());
13652 quote! { #ident: Self::#method_ident(model) }
13653 } else if let Some(slug_field) = &fi.attrs.slug {
13654 let src_name = fi
13663 .attrs
13664 .source
13665 .as_deref()
13666 .unwrap_or(&ident_name(&fi.ident))
13667 .to_owned();
13668 let src_ident = ident_from_name(&src_name, ident.span());
13669 let slug_ident = syn::Ident::new(slug_field, ident.span());
13670 quote! {
13671 #ident: #root::serializer::LoadedRelation::render(
13672 &model.#src_ident,
13673 |__loaded| ::core::clone::Clone::clone(&__loaded.#slug_ident),
13674 ::core::default::Default::default,
13675 )
13676 }
13677 } else if fi.attrs.nested {
13678 let src_name = fi.attrs.source.as_deref().unwrap_or(&ident_name(&fi.ident)).to_owned();
13696 let src_ident = ident_from_name(&src_name, ident.span());
13697 let unloaded = if fi.attrs.nested_strict {
13698 let panic_msg = format!(
13699 "nested(strict) serializer for `{ident}` requires `model.{src_name}` to be loaded — \
13700 call .get(&pool).await? or .select_related(\"{src_name}\") on the model first",
13701 );
13702 quote! { || ::core::panic!("{}", #panic_msg) }
13703 } else {
13704 quote! { ::core::default::Default::default }
13705 };
13706 quote! {
13707 #ident: #root::serializer::LoadedRelation::render(
13708 &model.#src_ident,
13709 |__loaded| #root::serializer::ModelSerializer::from_model(__loaded),
13710 #unloaded,
13711 )
13712 }
13713 } else if fi.attrs.write_only || fi.attrs.skip {
13714 quote! { #ident: ::core::default::Default::default() }
13716 } else if let Some(src) = &fi.attrs.source {
13717 let src_ident = ident_from_name(src, ident.span());
13718 quote! { #ident: ::core::clone::Clone::clone(&model.#src_ident) }
13719 } else {
13720 quote! { #ident: ::core::clone::Clone::clone(&model.#ident) }
13721 }
13722 });
13723
13724 let is_writable = |fi: &&FieldInfo| {
13727 !fi.attrs.read_only
13728 && !fi.attrs.skip
13729 && !fi.serde.skip_de
13730 && fi.attrs.method.is_none()
13731 && !fi.attrs.nested
13732 && fi.attrs.many.is_none()
13733 && fi.attrs.slug.is_none()
13734 };
13735
13736 let opt_usize = |v: Option<u64>| match v {
13744 Some(n) => {
13745 let n = n as usize;
13746 quote!(::core::option::Option::Some(#n))
13747 }
13748 None => quote!(::core::option::Option::None),
13749 };
13750 #[cfg(feature = "openapi")]
13751 let opt_u64 = |v: Option<u64>| match v {
13752 Some(n) => quote!(::core::option::Option::Some(#n)),
13753 None => quote!(::core::option::Option::None),
13754 };
13755 let opt_i64 = |v: Option<i64>| match v {
13756 Some(n) => quote!(::core::option::Option::Some(#n)),
13757 None => quote!(::core::option::Option::None),
13758 };
13759 let constraint_blocks: Vec<_> = fields_info
13760 .iter()
13761 .filter(is_writable)
13762 .map(|fi| {
13763 let ident = &fi.ident;
13764 let fname = &fi.de_name;
13765 let mname = fi
13766 .attrs
13767 .source
13768 .clone()
13769 .unwrap_or_else(|| ident_name(&fi.ident));
13770 let attr_max_len = opt_usize(fi.attrs.max_length);
13771 let attr_min_len = opt_usize(fi.attrs.min_length);
13772 let attr_min = opt_i64(fi.attrs.min);
13773 let attr_max = opt_i64(fi.attrs.max);
13774 let ty = &fi.ty;
13776 let len_check = (fi.attrs.max_length.is_some() || fi.attrs.min_length.is_some())
13777 .then(|| quote! { #root::forms::validators::assert_length_bounded::<#ty>(); });
13778 let num_check = (fi.attrs.min.is_some() || fi.attrs.max.is_some())
13779 .then(|| quote! { #root::forms::validators::assert_number_bounded::<#ty>(); });
13780 quote! {
13781 #len_check
13782 #num_check
13783 if __sent(#fname) {
13784 let __sf = <#model_path as #root::core::Model>::SCHEMA.field(#mname);
13785 let __max_length: ::core::option::Option<usize> = #attr_max_len
13786 .or_else(|| __sf.and_then(|__f| __f.max_length).map(|__n| __n as usize));
13787 let __min_length: ::core::option::Option<usize> = #attr_min_len;
13788 let __min: ::core::option::Option<i64> =
13789 #attr_min.or_else(|| __sf.and_then(|__f| __f.min));
13790 let __max: ::core::option::Option<i64> =
13791 #attr_max.or_else(|| __sf.and_then(|__f| __f.max));
13792 let __choices = __sf.and_then(|__f| __f.choices);
13793 let __v = #root::__serde_json::to_value(&self.#ident)
13794 .unwrap_or(#root::__serde_json::Value::Null);
13795 #root::forms::validators::check_value(
13796 #fname, &__v, __max_length, __min_length, __min, __max, __choices,
13797 &mut __errors,
13798 );
13799 }
13800 }
13801 })
13802 .collect();
13803 let has_constraints = !constraint_blocks.is_empty();
13804
13805 let validator_calls: Vec<_> = fields_info
13809 .iter()
13810 .filter_map(|fi| {
13811 let ident = &fi.ident;
13812 let name_lit = &fi.de_name;
13813 let method = fi.attrs.validate.as_ref()?;
13814 let method_ident = syn::Ident::new(method, ident.span());
13815 Some(quote! {
13816 if __sent(#name_lit) {
13817 if let ::core::result::Result::Err(__e) = Self::#method_ident(&self.#ident) {
13818 __errors.add(#name_lit.to_owned(), __e);
13819 }
13820 }
13821 })
13822 })
13823 .collect();
13824 let cross_validate_call = container.cross_validate.as_ref().map(|method_ident| {
13831 quote! {
13832 if let ::core::result::Result::Err(__cross) = self.#method_ident() {
13835 __errors.merge(__cross);
13836 }
13837 }
13838 });
13839 let has_validators = !validator_calls.is_empty() || container.cross_validate.is_some();
13840 let has_run_validations = has_validators || has_constraints;
13843 let checks = quote! {
13847 let mut __errors = #root::forms::FormErrors::default();
13848 #( #constraint_blocks )*
13849 #( #validator_calls )*
13850 #cross_validate_call
13851 if __errors.is_empty() {
13852 ::core::result::Result::Ok(())
13853 } else {
13854 ::core::result::Result::Err(__errors)
13855 }
13856 };
13857 let validate_body = quote! {
13858 let __sent = |_: &str| true;
13859 #checks
13860 };
13861 let validate_patch_body = quote! {
13863 let __sent = |__n: &str| __obj.is_some_and(|__o| __o.contains_key(__n));
13864 #checks
13865 };
13866 let validate_method = if has_validators {
13872 quote! {
13873 impl #struct_name {
13874 pub fn validate(&self) -> ::core::result::Result<(), #root::forms::FormErrors> {
13880 #validate_body
13881 }
13882 }
13883 }
13884 } else {
13885 quote! {}
13886 };
13887 let trait_validate_override = if has_run_validations {
13892 quote! {
13893 fn validate(&self) -> ::core::result::Result<(), #root::forms::FormErrors> {
13894 #validate_body
13895 }
13896 }
13897 } else {
13898 quote! {}
13899 };
13900
13901 let many_setters: Vec<_> = fields_info
13905 .iter()
13906 .filter_map(|fi| {
13907 let many_ty = fi.attrs.many.as_ref()?;
13908 let ident = &fi.ident;
13909 let setter = syn::Ident::new(&format!("set_{}", ident_name(ident)), ident.span());
13910 Some(quote! {
13911 pub fn #setter(
13916 &mut self,
13917 models: &[<#many_ty as #root::serializer::ModelSerializer>::Model],
13918 ) -> &mut Self {
13919 self.#ident = models.iter()
13920 .map(<#many_ty as #root::serializer::ModelSerializer>::from_model)
13921 .collect();
13922 self
13923 }
13924 })
13925 })
13926 .collect();
13927 let many_setters_impl = if many_setters.is_empty() {
13928 quote! {}
13929 } else {
13930 quote! {
13931 impl #struct_name {
13932 #( #many_setters )*
13933 }
13934 }
13935 };
13936
13937 let output_fields: Vec<_> = fields_info
13939 .iter()
13940 .filter(|fi| !fi.attrs.write_only && !fi.serde.skip_ser)
13941 .collect();
13942 let output_field_count = output_fields.iter().map(|fi| {
13944 let ident = &fi.ident;
13945 if let Some(pred) = &fi.serde.skip_ser_if {
13946 quote! { + if #pred(&self.#ident) { 0 } else { 1 } }
13947 } else {
13948 quote! { + 1 }
13949 }
13950 });
13951 let output_field_count = quote! { 0usize #( #output_field_count )* };
13952 let serialize_fields = output_fields.iter().map(|fi| {
13953 let ident = &fi.ident;
13954 let name_lit = &fi.ser_name;
13955 if let Some(pred) = &fi.serde.skip_ser_if {
13956 quote! {
13957 if #pred(&self.#ident) {
13958 __state.skip_field(#name_lit)?;
13959 } else {
13960 __state.serialize_field(#name_lit, &self.#ident)?;
13961 }
13962 }
13963 } else {
13964 quote! { __state.serialize_field(#name_lit, &self.#ident)?; }
13965 }
13966 });
13967
13968 let writable_lits: Vec<_> = fields_info
13983 .iter()
13984 .filter(is_writable)
13985 .map(|fi| fi.de_name.clone())
13986 .collect();
13987
13988 let writable_source_lits: Vec<String> = fields_info
13993 .iter()
13994 .filter(is_writable)
13995 .map(|fi| {
13996 fi.attrs
13997 .source
13998 .clone()
13999 .unwrap_or_else(|| ident_name(&fi.ident))
14000 })
14001 .collect();
14002
14003 let readable: Vec<&FieldInfo> = fields_info
14007 .iter()
14008 .filter(|fi| {
14009 !fi.attrs.write_only
14010 && !fi.serde.skip_ser
14011 && !fi.attrs.skip
14012 && fi.attrs.method.is_none()
14013 && !fi.attrs.nested
14014 && fi.attrs.many.is_none()
14015 && fi.attrs.slug.is_none()
14016 })
14017 .collect();
14018 let readable_source_lits: Vec<String> = readable
14019 .iter()
14020 .map(|fi| {
14021 fi.attrs
14022 .source
14023 .clone()
14024 .unwrap_or_else(|| ident_name(&fi.ident))
14025 })
14026 .collect();
14027 let readable_lits: Vec<String> = readable.iter().map(|fi| fi.ser_name.clone()).collect();
14029
14030 let from_writable_json_inits: Vec<_> = fields_info
14037 .iter()
14038 .map(|fi| {
14039 let ident = &fi.ident;
14040 let fname = &fi.de_name;
14041 let ty = &fi.ty;
14042 if is_writable(&fi) {
14043 quote! {
14044 #ident: match __obj.and_then(|__o| __o.get(#fname)) {
14045 ::core::option::Option::Some(__v) => {
14046 match #root::__serde_json::from_value::<#ty>(
14047 ::core::clone::Clone::clone(__v),
14048 ) {
14049 ::core::result::Result::Ok(__x) => __x,
14050 ::core::result::Result::Err(__e) => {
14051 __errors.add(#fname.to_owned(), __e.to_string());
14052 ::core::default::Default::default()
14053 }
14054 }
14055 }
14056 ::core::option::Option::None => ::core::default::Default::default(),
14057 }
14058 }
14059 } else {
14060 quote! { #ident: ::core::default::Default::default() }
14061 }
14062 })
14063 .collect();
14064 let patch_overlays: Vec<_> = fields_info
14066 .iter()
14067 .filter(is_writable)
14068 .map(|fi| {
14069 let ident = &fi.ident;
14070 let fname = &fi.de_name;
14071 let ty = &fi.ty;
14072 quote! {
14073 if let ::core::option::Option::Some(__v) = __obj.and_then(|__o| __o.get(#fname)) {
14074 match #root::__serde_json::from_value::<#ty>(::core::clone::Clone::clone(__v)) {
14075 ::core::result::Result::Ok(__x) => __merged.#ident = __x,
14076 ::core::result::Result::Err(__e) => {
14077 __errors.add(#fname.to_owned(), __e.to_string());
14078 }
14079 }
14080 }
14081 }
14082 })
14083 .collect();
14084 let patch_validate_call = if has_run_validations {
14085 quote! { __merged.__rustango_validate_patch(__obj) }
14086 } else {
14087 quote! { let _ = &__merged; ::core::result::Result::Ok(()) }
14088 };
14089 let patch_validate_helper = if has_run_validations {
14090 quote! {
14091 impl #struct_name {
14092 #[doc(hidden)]
14093 fn __rustango_validate_patch(
14094 &self,
14095 __obj: ::core::option::Option<&#root::__serde_json::Map<::std::string::String, #root::__serde_json::Value>>,
14096 ) -> ::core::result::Result<(), #root::forms::FormErrors> {
14097 #validate_patch_body
14098 }
14099 }
14100 }
14101 } else {
14102 quote! {}
14103 };
14104
14105 let openapi_impl = {
14109 #[cfg(feature = "openapi")]
14110 {
14111 let property_calls = output_fields.iter().map(|fi| {
14112 let name_lit = &fi.ser_name;
14113 let ty = &fi.ty;
14114 let nullable_call = if is_option(ty) {
14115 quote! { .nullable() }
14116 } else {
14117 quote! {}
14118 };
14119 let limits_call = is_writable(fi).then(|| {
14121 let mname = fi
14122 .attrs
14123 .source
14124 .clone()
14125 .unwrap_or_else(|| ident_name(&fi.ident));
14126 let (a, b, c, d) = (
14127 opt_u64(fi.attrs.max_length),
14128 opt_u64(fi.attrs.min_length),
14129 opt_i64(fi.attrs.min),
14130 opt_i64(fi.attrs.max),
14131 );
14132 quote! {
14133 .field_limits(
14134 <#model_path as #root::core::Model>::SCHEMA.field(#mname),
14135 #a, #b, #c, #d,
14136 )
14137 }
14138 });
14139 quote! {
14140 .property(
14141 #name_lit,
14142 <#ty as #root::openapi::OpenApiSchema>::openapi_schema()
14143 #limits_call
14144 #nullable_call,
14145 )
14146 }
14147 });
14148 let required_lits: Vec<_> = output_fields
14149 .iter()
14150 .filter(|fi| !is_option(&fi.ty) && fi.serde.skip_ser_if.is_none())
14151 .map(|fi| fi.ser_name.clone())
14152 .collect();
14153 quote! {
14154 impl #root::openapi::OpenApiSchema for #struct_name {
14155 fn openapi_schema() -> #root::openapi::Schema {
14156 #root::openapi::Schema::object()
14157 #( #property_calls )*
14158 .required([ #( #required_lits ),* ])
14159 }
14160 }
14161 }
14162 }
14163 #[cfg(not(feature = "openapi"))]
14164 {
14165 quote! {}
14166 }
14167 };
14168
14169 let related_tables = fields_info.iter().filter_map(|fi| {
14171 let name = &fi.ser_name;
14172 if let Some(inner) = &fi.attrs.many {
14173 return Some(quote! {
14174 (#name, <<#inner as #root::serializer::ModelSerializer>::Model
14175 as #root::core::Model>::SCHEMA.table)
14176 });
14177 }
14178 if !(fi.attrs.nested || fi.attrs.slug.is_some()) {
14179 return None;
14180 }
14181 let src_name = fi
14182 .attrs
14183 .source
14184 .as_deref()
14185 .unwrap_or(&ident_name(&fi.ident))
14186 .to_owned();
14187 let src_ident = ident_from_name(&src_name, fi.ident.span());
14188 Some(quote! {
14189 (#name, #root::serializer::relation_table(|__m: &#model_path| &__m.#src_ident))
14190 })
14191 });
14192
14193 Ok(quote! {
14194 impl #root::serializer::ModelSerializer for #struct_name {
14195 type Model = #model_path;
14196
14197 fn related_tables() -> ::std::vec::Vec<(&'static str, &'static str)> {
14198 ::std::vec![ #( #related_tables ),* ]
14199 }
14200
14201 fn from_model(model: &Self::Model) -> Self {
14202 Self {
14203 #( #from_model_fields ),*
14204 }
14205 }
14206
14207 fn writable_fields() -> &'static [&'static str] {
14208 &[ #( #writable_lits ),* ]
14209 }
14210
14211 fn writable_source_fields() -> &'static [&'static str] {
14212 &[ #( #writable_source_lits ),* ]
14213 }
14214
14215 fn readable_source_fields() -> &'static [&'static str] {
14216 &[ #( #readable_source_lits ),* ]
14217 }
14218
14219 fn readable_fields() -> &'static [&'static str] {
14220 &[ #( #readable_lits ),* ]
14221 }
14222
14223 fn from_writable_json(
14224 __body: &#root::__serde_json::Value,
14225 ) -> ::core::result::Result<Self, #root::forms::FormErrors> {
14226 let mut __errors = #root::forms::FormErrors::default();
14227 let __obj = __body.as_object();
14228 let __out = Self {
14229 #( #from_writable_json_inits ),*
14230 };
14231 if __errors.is_empty() {
14232 ::core::result::Result::Ok(__out)
14233 } else {
14234 ::core::result::Result::Err(__errors)
14235 }
14236 }
14237
14238 fn validate_patch(
14239 __stored: &Self::Model,
14240 __body: &#root::__serde_json::Value,
14241 ) -> ::core::result::Result<(), #root::forms::FormErrors> {
14242 let mut __errors = #root::forms::FormErrors::default();
14243 let __obj = __body.as_object();
14244 #[allow(unused_mut)]
14245 let mut __merged = <Self as #root::serializer::ModelSerializer>::from_model(__stored);
14246 #( #patch_overlays )*
14247 if !__errors.is_empty() {
14248 return ::core::result::Result::Err(__errors);
14249 }
14250 #patch_validate_call
14251 }
14252
14253 #trait_validate_override
14254 }
14255
14256 #patch_validate_helper
14257
14258 impl #root::__serde::Serialize for #struct_name {
14259 fn serialize<S>(&self, serializer: S)
14260 -> ::core::result::Result<S::Ok, S::Error>
14261 where
14262 S: #root::__serde::Serializer,
14263 {
14264 use #root::__serde::ser::SerializeStruct;
14265 let mut __state = serializer.serialize_struct(
14266 #struct_name_lit,
14267 #output_field_count,
14268 )?;
14269 #( #serialize_fields )*
14270 __state.end()
14271 }
14272 }
14273
14274 #openapi_impl
14275
14276 #validate_method
14277
14278 #many_setters_impl
14279 })
14280}
14281
14282#[cfg_attr(not(feature = "openapi"), allow(dead_code))]
14286fn is_option(ty: &syn::Type) -> bool {
14287 if let syn::Type::Path(p) = ty {
14288 if let Some(last) = p.path.segments.last() {
14289 return last.ident == "Option";
14290 }
14291 }
14292 false
14293}
14294
14295#[cfg(test)]
14296mod main_attr_tests {
14297 use super::*;
14298
14299 fn expand(args: &str) -> String {
14300 let body = quote! { async fn main() { run().await } };
14301 expand_main(args.parse().unwrap(), body)
14302 .expect("expansion failed")
14303 .to_string()
14304 }
14305
14306 #[test]
14310 fn logging_false_emits_no_subscriber() {
14311 let out = expand("logging = false");
14312 assert!(
14313 !out.contains("logging :: Setup"),
14314 "`logging = false` must not install a subscriber:\n{out}",
14315 );
14316 assert!(
14317 out.contains("block_on"),
14318 "the runtime must still be built:\n{out}",
14319 );
14320 }
14321
14322 #[test]
14323 fn default_still_installs_a_subscriber() {
14324 let out = expand("");
14328 assert!(
14329 out.contains("logging :: Setup"),
14330 "the default entrypoint must still install one:\n{out}",
14331 );
14332 assert!(
14335 !out.contains("args ()"),
14336 "the entrypoint must not route logs by argv:\n{out}",
14337 );
14338 }
14339
14340 #[test]
14341 fn logging_false_composes_with_flavor() {
14342 let out = expand("flavor = \"current_thread\", logging = false");
14343 assert!(!out.contains("logging :: Setup"), "{out}");
14344 assert!(out.contains("new_current_thread"), "{out}");
14345 }
14346
14347 #[test]
14349 fn body_runs_on_a_big_stack_thread() {
14350 let out = expand("");
14351 assert!(out.contains("run_on_main_thread"), "{out}");
14352 assert!(out.contains("MAIN_STACK_SIZE"), "{out}");
14353 let out = expand("stack_size = 16777216, logging = false");
14354 assert!(out.contains("16777216usize"), "{out}");
14355 assert!(!out.contains("MAIN_STACK_SIZE"), "{out}");
14356 assert!(
14357 out.contains("thread_stack_size"),
14358 "workers get it too:\n{out}"
14359 );
14360 }
14361
14362 #[test]
14364 fn a_tiny_stack_size_is_refused() {
14365 let body = quote! { async fn main() {} };
14366 let err = expand_main("stack_size = 4096".parse().unwrap(), body)
14367 .expect_err("4 KiB")
14368 .to_string();
14369 assert!(err.contains("at least"), "{err}");
14370 }
14371
14372 #[test]
14374 fn lint_attributes_reach_the_body() {
14375 let body = quote! {
14376 #[allow(unused_variables)]
14377 #[doc = "entry"]
14378 async fn main() { let x = 1; }
14379 };
14380 let out = expand_main(quote!(), body).unwrap().to_string();
14381 assert_eq!(out.matches("allow (unused_variables)").count(), 2, "{out}");
14382 assert_eq!(out.matches("doc = \"entry\"").count(), 1, "{out}");
14383 }
14384
14385 #[test]
14386 fn logging_true_is_the_default_shape() {
14387 assert!(
14388 parse_main_args(&"logging = true".parse().unwrap())
14389 .unwrap()
14390 .logging
14391 );
14392 assert!(
14393 !parse_main_args(&"logging=false".parse().unwrap())
14394 .unwrap()
14395 .logging
14396 );
14397 }
14398}
14399
14400#[cfg(test)]
14401mod main_arg_tests {
14402 use super::*;
14403
14404 fn expand(args: &str) -> syn::Result<TokenStream2> {
14405 let body = quote! { async fn main() { run().await } };
14406 expand_main(args.parse().unwrap(), body)
14407 }
14408
14409 #[test]
14411 fn unknown_or_misspelt_args_are_errors() {
14412 for args in [
14413 "logging = flase",
14414 "multi_thread",
14415 "workers = 4",
14416 r#"flavor = "curent_thread""#,
14417 "worker_threads = 4",
14418 ] {
14419 let err = expand(args).expect_err(args).to_string();
14420 assert!(err.contains("#[rustango::main]"), "{args}: {err}");
14421 }
14422 }
14423
14424 #[test]
14425 fn supported_args_expand() {
14426 for args in [
14427 "",
14428 r#"flavor = "current_thread""#,
14429 r#"flavor = "multi_thread", logging = false"#,
14430 "logging = true",
14431 ] {
14432 assert!(expand(args).is_ok(), "{args}");
14433 }
14434 }
14435}
14436
14437#[cfg(test)]
14438mod embed_migrations_tests {
14439 use super::*;
14440
14441 fn expand(path: &str) -> String {
14442 expand_embed_migrations(quote! { #path })
14443 .expect("expansion failed")
14444 .to_string()
14445 }
14446
14447 #[test]
14448 fn missing_dir_warns() {
14449 let out = expand("./no_such_migrations_dir");
14450 assert!(out.contains("deprecated"), "missing dir must warn:\n{out}");
14451 assert!(out.contains("no_such_migrations_dir"), "{out}");
14452 }
14453
14454 #[test]
14455 fn existing_dir_does_not_warn() {
14456 let out = expand("./src");
14457 assert!(!out.contains("deprecated"), "{out}");
14458 }
14459}
14460
14461#[cfg(test)]
14462mod field_attr_tests {
14463 use super::*;
14464
14465 fn error_for(field: syn::Field) -> Option<String> {
14466 let attrs = parse_field_attrs(&field).unwrap();
14467 let kind = detect_type(&field.ty).unwrap().kind;
14468 check_bound_compatibility(&field, &attrs, kind)
14469 .err()
14470 .map(|e| e.to_string())
14471 }
14472
14473 #[test]
14475 fn type_specific_attrs_reject_other_types() {
14476 let f: syn::FieldsNamed = syn::parse_quote!({
14477 #[rustango(citext)] a: i64,
14478 #[rustango(vector(dims = 3))] b: String,
14479 #[rustango(geometry(srid = 4326))] c: String,
14480 #[rustango(citext)] d: String,
14481 });
14482 let errs: Vec<_> = f.named.into_iter().map(error_for).collect();
14483 assert!(errs[0].as_deref().unwrap().contains("citext"), "{errs:?}");
14484 assert!(errs[1].as_deref().unwrap().contains("vector"), "{errs:?}");
14485 assert!(errs[2].as_deref().unwrap().contains("geometry"), "{errs:?}");
14486 assert_eq!(errs[3], None);
14487 }
14488
14489 #[test]
14491 fn manager_ext_is_refused() {
14492 let input: DeriveInput = syn::parse_quote! {
14493 #[rustango(table = "p", manager(ext = "PostManagerExt"))]
14494 struct Post { id: i64 }
14495 };
14496 let err = parse_container_attrs(&input).err().expect("refused");
14497 assert!(err.to_string().contains("#2132"), "{err}");
14498 }
14499
14500 #[test]
14502 fn set_null_needs_an_optional_foreign_key() {
14503 let input: DeriveInput = syn::parse_quote! {
14504 #[rustango(table = "c")]
14505 struct Child {
14506 #[rustango(primary_key)] id: i64,
14507 #[rustango(on_delete = "set_null")] parent: ForeignKey<Parent>,
14508 }
14509 };
14510 let err = expand(&input).err().expect("refused");
14511 assert!(err.to_string().contains("set_null"), "{err}");
14512 let input: DeriveInput = syn::parse_quote! {
14513 #[rustango(table = "c")]
14514 struct Child {
14515 #[rustango(primary_key)] id: i64,
14516 #[rustango(on_delete = "set_null")] parent: Option<ForeignKey<Parent>>,
14517 }
14518 };
14519 assert!(expand(&input).is_ok());
14520 }
14521
14522 #[test]
14524 fn set_null_needs_a_nullable_fk_column() {
14525 let input: DeriveInput = syn::parse_quote! {
14526 #[rustango(table = "c")]
14527 struct Child {
14528 #[rustango(primary_key)] id: i64,
14529 #[rustango(fk = "p", on_delete = "set_null")] parent_id: i64,
14530 #[rustango(o2o = "q", on_delete = "set_null")] other_id: Option<i64>,
14531 }
14532 };
14533 let err = expand(&input).err().expect("refused");
14534 assert!(err.to_string().contains("set_null"), "{err}");
14535 let input: DeriveInput = syn::parse_quote! {
14536 #[rustango(table = "c")]
14537 struct Child {
14538 #[rustango(primary_key)] id: i64,
14539 #[rustango(fk = "p", on_delete = "set_null")] parent_id: std::option::Option<i64>,
14540 #[rustango(o2o = "q", on_delete = "set_null")] other_id: ::core::option::Option<i64>,
14541 #[rustango(on_delete = "set_null")] parent: core::option::Option<ForeignKey<Parent>>,
14542 }
14543 };
14544 assert!(expand(&input).is_ok());
14545 }
14546
14547 #[test]
14550 fn unknown_index_columns_are_errors() {
14551 let input: DeriveInput = syn::parse_quote! {
14552 #[rustango(
14553 table = "w",
14554 unique_together = "a, nope",
14555 index_together = "a, b_typo",
14556 index("missing"),
14557 get_latest_by = "-when_typo"
14558 )]
14559 struct W { #[rustango(primary_key)] id: i64, a: i64, b: i64 }
14560 };
14561 let err = expand(&input).expect_err("must not compile");
14562 let all: Vec<String> = err.into_iter().map(|e| e.to_string()).collect();
14563 for name in ["nope", "b_typo", "missing", "when_typo"] {
14564 assert!(
14565 all.iter().any(|m| m.contains(&format!("`{name}`"))),
14566 "{name}: {all:?}"
14567 );
14568 }
14569 }
14570
14571 #[test]
14574 fn known_index_columns_compile() {
14575 let input: DeriveInput = syn::parse_quote! {
14576 #[rustango(
14577 table = "w",
14578 unique_together = "a, b_col",
14579 index("lower(a)"),
14580 get_latest_by = "-b"
14581 )]
14582 struct W {
14583 #[rustango(primary_key)] id: i64,
14584 a: i64,
14585 #[rustango(column = "b_col")] b: i64,
14586 }
14587 };
14588 expand(&input).expect("compiles");
14589 }
14590
14591 #[test]
14593 fn reverse_accessor_clash_names_related_name() {
14594 let input: DeriveInput = syn::parse_quote! {
14595 #[rustango(table = "m")]
14596 struct Message {
14597 #[rustango(primary_key)] id: i64,
14598 sender: ForeignKey<User>,
14599 recipient: ForeignKey<User>,
14600 }
14601 };
14602 let err = expand(&input).err().expect("refused");
14603 assert!(err.to_string().contains("related_name"), "{err}");
14604 }
14605
14606 #[test]
14609 fn related_name_plus_skips_the_reverse_accessor() {
14610 let input: DeriveInput = syn::parse_quote! {
14611 #[rustango(table = "m")]
14612 struct Message {
14613 #[rustango(primary_key)] id: i64,
14614 sender: ForeignKey<User>,
14615 #[rustango(related_name = "+")] recipient: ForeignKey<User>,
14616 #[rustango(related_name = "+")] owner: ForeignKey<other::Account>,
14617 }
14618 };
14619 let out = expand(&input).expect("derives").to_string();
14620 assert_eq!(out.matches("message_set_pool").count(), 1, "{out}");
14621 assert!(!out.contains("impl other :: Account {"), "{out}");
14622 }
14623
14624 fn field_error(src: &str) -> Option<String> {
14625 let f: syn::FieldsNamed = syn::parse_str(&format!("{{ {src} }}")).unwrap();
14626 let field = f.named.into_iter().next().unwrap();
14627 match parse_field_attrs(&field) {
14628 Err(e) => Some(e.to_string()),
14629 Ok(_) => error_for(field),
14630 }
14631 }
14632
14633 #[test]
14635 fn flag_attrs_accept_eq_true() {
14636 for key in [
14637 "primary_key",
14638 "unique",
14639 "soft_delete",
14640 "index",
14641 "auto_now",
14642 "auto_now_add",
14643 ] {
14644 let f: syn::FieldsNamed =
14645 syn::parse_str(&format!("{{ #[rustango({key} = true)] a: i64 }}")).unwrap();
14646 let field = f.named.into_iter().next().unwrap();
14647 assert!(parse_field_attrs(&field).is_ok(), "{key} = true");
14648 let err = field_error(&format!("#[rustango({key} = false)] a: i64")).unwrap();
14649 assert!(err.contains(&format!("`{key} = false`")), "{err}");
14650 }
14651 let f: syn::Field =
14652 syn::parse_quote!(#[rustango(primary_key = true, unique = true)] a: i64);
14653 let attrs = parse_field_attrs(&f).unwrap();
14654 assert!(attrs.primary_key && attrs.unique);
14655 }
14656
14657 #[test]
14659 fn unknown_key_is_named_with_a_suggestion() {
14660 let err = field_error("#[rustango(max_lenght = 5)] a: String").unwrap();
14661 assert!(err.contains("`max_lenght`"), "{err}");
14662 assert!(err.contains("did you mean `max_length`"), "{err}");
14663 assert!(err.contains("Supported: column"), "{err}");
14664 let input: DeriveInput = syn::parse_quote! {
14665 #[rustango(tabel = "p")]
14666 struct P { id: i64 }
14667 };
14668 let err = parse_container_attrs(&input).err().unwrap().to_string();
14669 assert!(
14670 err.contains("`tabel`") && err.contains("did you mean `table`"),
14671 "{err}"
14672 );
14673 let err = field_error("#[rustango(zzzzzz)] a: i64").unwrap();
14674 assert!(!err.contains("did you mean"), "{err}");
14675 }
14676
14677 #[test]
14679 fn listed_keys_are_all_known() {
14680 for key in FIELD_ATTR_KEYS {
14681 let err = field_error(&format!("#[rustango({key})] a: i64")).unwrap_or_default();
14682 assert!(!err.contains("unknown rustango field"), "{key}: {err}");
14683 }
14684 for key in CONTAINER_ATTR_KEYS {
14685 let input: DeriveInput =
14686 syn::parse_str(&format!("#[rustango({key})] struct P {{ id: i64 }}")).unwrap();
14687 let err = parse_container_attrs(&input)
14688 .err()
14689 .map(|e| e.to_string())
14690 .unwrap_or_default();
14691 assert!(!err.contains("unknown rustango container"), "{key}: {err}");
14692 }
14693 }
14694
14695 #[test]
14697 fn default_shapes_only_sqlite_takes_are_refused() {
14698 for bad in [
14699 r#"#[rustango(default = "draft")] s: String"#,
14700 r#"#[rustango(default = "draft")] s: Option<String>"#,
14701 r#"#[rustango(default = "abc")] n: i32"#,
14702 r#"#[rustango(default = "'abc'")] n: i64"#,
14703 r#"#[rustango(default = "true")] n: i32"#,
14704 r#"#[rustango(default = "")] n: i32"#,
14705 r#"#[rustango(default = "'x'")] n: f64"#,
14706 ] {
14707 assert!(field_error(bad).is_some(), "{bad} must be refused");
14708 }
14709 let err = field_error(r#"#[rustango(default = "draft")] s: String"#).unwrap();
14710 assert!(err.contains(r#"default = "'draft'""#), "{err}");
14711 let err = field_error(r#"#[rustango(default = "current_user")] s: String"#).unwrap();
14713 assert!(
14714 err.contains("Postgres only") && !err.contains("column"),
14715 "{err}"
14716 );
14717 for good in [
14718 r#"#[rustango(default = "'draft'")] s: String"#,
14719 r#"#[rustango(default = "")] s: String"#,
14720 r#"#[rustango(default = "now()")] s: String"#,
14721 r#"#[rustango(default = "CURRENT_TIMESTAMP")] s: String"#,
14722 r#"#[rustango(default = "NULL")] s: Option<String>"#,
14723 r#"#[rustango(default = "0")] n: i32"#,
14724 r#"#[rustango(default = "-1")] n: i64"#,
14725 r#"#[rustango(default = "'5'")] n: i64"#,
14726 r#"#[rustango(default = "(1 + 2)")] n: i64"#,
14727 r#"#[rustango(default = "1.5")] n: f64"#,
14728 r#"#[rustango(default = "true")] b: bool"#,
14729 ] {
14730 assert_eq!(field_error(good), None, "{good}");
14731 }
14732 }
14733
14734 #[test]
14736 fn raw_idents_derive_plain_names() {
14737 let ident: syn::Ident = syn::parse_quote!(r#type);
14738 assert_eq!(column_type_ident(&ident).to_string(), "type_col");
14739 assert_eq!(ident_from_name("type", ident.span()).to_string(), "r#type");
14740 }
14741}
14742
14743#[cfg(test)]
14744mod model_attr_name_tests {
14745 use super::*;
14746
14747 fn container_error(item: proc_macro2::TokenStream) -> Option<String> {
14748 let input: DeriveInput = syn::parse2(item).unwrap();
14749 parse_container_attrs(&input).err().map(|e| e.to_string())
14750 }
14751
14752 fn field_error(field: proc_macro2::TokenStream) -> Option<String> {
14753 let f: syn::FieldsNamed = syn::parse2(quote!({ #field })).unwrap();
14754 let field = f.named.into_iter().next().unwrap();
14755 parse_field_attrs(&field).err().map(|e| e.to_string())
14756 }
14757
14758 #[test]
14760 fn rgo_is_read_like_rustango() {
14761 let long: DeriveInput = syn::parse_quote! {
14762 #[rustango(table = "t", display = "name")] struct S;
14763 };
14764 let short: DeriveInput = syn::parse_quote! {
14765 #[rgo(table = "t", display = "name")] struct S;
14766 };
14767 let (long, short) = (
14768 parse_container_attrs(&long).unwrap(),
14769 parse_container_attrs(&short).unwrap(),
14770 );
14771 assert_eq!(long.table, short.table);
14772 assert_eq!(short.table.as_deref(), Some("t"));
14773 assert_eq!(long.display.map(|d| d.0), short.display.map(|d| d.0));
14774 }
14775
14776 #[test]
14777 fn mixing_names_on_one_item_or_field_is_refused() {
14778 let err = container_error(quote! {
14779 #[rustango(table = "t")] #[rgo(display = "name")] struct S;
14780 })
14781 .expect("a mix on an item must fail");
14782 assert!(
14783 err.contains("`#[rustango(...)]` and `#[rgo(...)]`"),
14784 "{err}"
14785 );
14786 let err = field_error(quote! { #[rgo(primary_key)] #[rustango(column = "c")] a: i64 })
14787 .expect("a mix on a field must fail");
14788 assert!(
14789 err.contains("`#[rgo(...)]` and `#[rustango(...)]`"),
14790 "{err}"
14791 );
14792 assert_eq!(
14794 container_error(quote! { #[rgo(table = "t")] #[rgo(display = "name")] struct S; }),
14795 None
14796 );
14797 assert_eq!(
14798 field_error(quote! { #[rustango(primary_key)] #[rustango(column = "c")] a: i64 }),
14799 None
14800 );
14801 }
14802
14803 #[test]
14805 fn hints_use_the_written_name() {
14806 let err = container_error(quote! { #[rgo(table = "t", unique_together = "a")] struct S; })
14807 .expect("one column must fail");
14808 assert!(err.contains("#[rgo(unique)]"), "{err}");
14809 let err =
14810 container_error(quote! { #[rustango(table = "t", index_together = "a")] struct S; })
14811 .expect("one column must fail");
14812 assert!(err.contains("#[rustango(index)]"), "{err}");
14813 }
14814}
14815
14816fn decode_text(bytes: Vec<u8>) -> Result<String, String> {
14820 fn utf16(bytes: &[u8], unit: fn([u8; 2]) -> u16) -> Result<String, String> {
14821 if bytes.len() % 2 != 0 {
14822 return Err("odd-length UTF-16".into());
14823 }
14824 let units = bytes.chunks_exact(2).map(|c| unit([c[0], c[1]]));
14825 char::decode_utf16(units)
14826 .collect::<Result<String, _>>()
14827 .map_err(|e| e.to_string())
14828 }
14829 match bytes.as_slice() {
14830 [0xFF, 0xFE, rest @ ..] => utf16(rest, u16::from_le_bytes),
14831 [0xFE, 0xFF, rest @ ..] => utf16(rest, u16::from_be_bytes),
14832 _ => {
14833 let mut bytes = bytes;
14834 if bytes.starts_with(&[0xEF, 0xBB, 0xBF]) {
14835 bytes.drain(..3);
14836 }
14837 String::from_utf8(bytes).map_err(|e| e.to_string())
14838 }
14839 }
14840}
14841
14842#[cfg(test)]
14843mod decode_text_tests {
14844 use super::decode_text;
14845
14846 #[test]
14847 fn a_bom_or_utf16_migration_decodes() {
14848 let text = r#"{"name": "0001_é"}"#;
14849 let mut bom = vec![0xEF, 0xBB, 0xBF];
14850 bom.extend(text.bytes());
14851 let mut le = vec![0xFF, 0xFE];
14852 le.extend(text.encode_utf16().flat_map(u16::to_le_bytes));
14853 let mut be = vec![0xFE, 0xFF];
14854 be.extend(text.encode_utf16().flat_map(u16::to_be_bytes));
14855 for bytes in [text.as_bytes().to_vec(), bom, le, be] {
14856 assert_eq!(decode_text(bytes).unwrap(), text);
14857 }
14858 assert!(decode_text(vec![0xFF, 0xFE, 0x41]).is_err());
14859 }
14860}