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(
3781 &mut self,
3782 pool: &#root::sql::Pool,
3783 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
3784 self.__rustango_save_exec(pool).await
3785 }
3786
3787 #partial
3788 }
3789 }
3790 } else {
3791 quote!()
3792 };
3793
3794 let pool_insert_method = if audited_fields.is_some() {
3801 if let Some(_) = primary_key {
3802 let returning_cols: Vec<proc_macro2::TokenStream> = if !fields.returning_cols.is_empty()
3804 {
3805 fields.returning_cols.clone()
3806 } else {
3807 primary_key
3814 .map(|(_, col)| {
3815 let lit = col.as_str();
3816 vec![quote!(#lit)]
3817 })
3818 .unwrap_or_default()
3819 };
3820 let assigned: Vec<syn::Ident> = fields
3822 .auto_field_idents
3823 .iter()
3824 .map(|(ident, _, _)| ident.clone())
3825 .collect();
3826 let body = restore_auto_now_unless(
3827 &assigned,
3828 "e!(::core::result::Result<(), #root::sql::ExecError>),
3829 "e!(_saved.is_ok()),
3830 "e! {
3831 let mut _query = self.__rustango_insert_query();
3832 if _query.returning.is_empty() {
3834 _query.returning = ::std::vec![ #( #returning_cols ),* ];
3835 }
3836 #root::audit::insert_one_with_audit(pool, &_query, self, |_m: &Self, _op| {
3837 _m.__rustango_audit_entry(_op)
3838 })
3839 .await
3840 },
3841 );
3842 quote! {
3843 pub async fn insert(
3851 &mut self,
3852 pool: &#root::sql::Pool,
3853 ) -> ::core::result::Result<(), #root::sql::ExecError> {
3854 #body
3855 }
3856 }
3857 } else {
3858 quote!()
3859 }
3860 } else {
3861 pool_insert_method
3863 };
3864
3865 let pool_save_method = if let Some(tracked) = audited_fields {
3886 if let Some((pk_ident, pk_col)) = primary_key {
3887 let pk_column_lit = pk_col.as_str();
3888 let partial = save_partial_methods(
3889 pk_ident,
3890 pk_column_lit,
3891 quote! {
3892 self.__rustango_save_with_diff(
3893 pool,
3894 &_query,
3895 ::core::option::Option::Some(&_wanted_cols),
3896 )
3897 .await
3898 },
3899 );
3900 let mk_before_pairs =
3905 |getter: &dyn Fn(&ColumnEntry) -> TokenStream2| -> Vec<TokenStream2> {
3906 tracked
3907 .iter()
3908 .map(|c| {
3909 let getter = getter(c);
3910 let column_lit = c.column.as_str();
3911 let value_ty = &c.value_ty;
3912 quote! {
3913 (
3914 #column_lit,
3915 match #getter::<#value_ty>(
3916 _audit_before_row, #column_lit,
3917 ) {
3918 ::core::result::Result::Ok(v) => {
3919 #root::core::__json_value(&v)
3920 }
3921 ::core::result::Result::Err(_) => #root::__serde_json::Value::Null,
3922 },
3923 )
3924 }
3925 })
3926 .collect()
3927 };
3928 let before_pairs_pg = mk_before_pairs(&|_| quote!(#root::sql::try_get_returning));
3929 let before_pairs_my = mk_before_pairs(&|c| {
3930 if c.uuid_column {
3931 quote!(#root::sql::try_get_flat_my)
3932 } else {
3933 quote!(#root::sql::try_get_returning_my)
3934 }
3935 });
3936 let before_pairs_sqlite =
3937 mk_before_pairs(&|_| quote!(#root::sql::try_get_returning_sqlite));
3938 let tracked_cols: Vec<&str> = tracked.iter().map(|c| c.column.as_str()).collect();
3939 let pk_value_for_bind = if fields.pk_is_auto {
3940 quote!(self.#pk_ident.get().copied().unwrap_or_default())
3941 } else {
3942 quote!(::core::clone::Clone::clone(&self.#pk_ident))
3943 };
3944 let assignments = &fields.update_assignments;
3945 let unset_dispatch = if fields.has_auto {
3946 quote! {
3947 if matches!(self.#pk_ident, #root::sql::Auto::Unset) {
3948 return self.insert(pool).await.map(|()| 1u64);
3949 }
3950 }
3951 } else {
3952 quote!()
3953 };
3954 let write = restore_auto_now_unless(
3955 &fields.auto_now_idents,
3956 &save_ret,
3957 &save_keep,
3958 "e! {
3959 let _query = #root::core::UpdateQuery::new(
3960 <Self as #root::core::Model>::SCHEMA,
3961 ::std::vec![ #( #assignments ),* ],
3962 #root::core::WhereExpr::Predicate(#root::core::Filter::new(
3963 #pk_column_lit,
3964 #root::core::Op::Eq,
3965 ::core::convert::Into::<#root::core::SqlValue>::into(
3966 ::core::clone::Clone::clone(&self.#pk_ident)
3967 ),
3968 )),
3969 );
3970 self.__rustango_save_with_diff(pool, &_query, ::core::option::Option::None)
3971 .await
3972 .and_then(#root::sql::__macro_internals::row_saved)
3973 },
3974 );
3975 quote! {
3976 pub async fn save(
3988 &mut self,
3989 pool: &#root::sql::Pool,
3990 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
3991 #unset_dispatch
3992 #write
3993 }
3994
3995 #[doc(hidden)]
3997 async fn __rustango_save_with_diff(
3998 &self,
3999 pool: &#root::sql::Pool,
4000 query: &#root::core::UpdateQuery,
4001 only: ::core::option::Option<&::std::collections::HashSet<&'static str>>,
4002 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4003 let mut _after_pairs = self.__rustango_audit_pairs();
4004 if let ::core::option::Option::Some(only) = only {
4005 _after_pairs.retain(|(col, _)| only.contains(col));
4006 }
4007 let _before_query = #root::audit::before_image_query(
4008 <Self as #root::core::Model>::SCHEMA,
4009 #pk_column_lit,
4010 ::core::convert::Into::<#root::core::SqlValue>::into(
4011 #pk_value_for_bind,
4012 ),
4013 &[ #( #tracked_cols ),* ],
4014 );
4015 #root::audit::save_one_with_diff(
4016 pool,
4017 query,
4018 &_before_query,
4019 <Self as #root::core::Model>::SCHEMA.table,
4020 self.__rustango_audit_pk(),
4021 _after_pairs,
4022 |_audit_before_row| ::std::vec![ #( #before_pairs_pg ),* ],
4023 |_audit_before_row| ::std::vec![ #( #before_pairs_my ),* ],
4024 |_audit_before_row| ::std::vec![ #( #before_pairs_sqlite ),* ],
4025 ).await
4026 }
4027
4028 #partial
4029 }
4030 } else {
4031 quote!()
4032 }
4033 } else {
4034 pool_save_method
4035 };
4036
4037 let pool_delete_method = {
4044 let pk_column_lit = primary_key.map(|(_, col)| col.as_str()).unwrap_or("id");
4045 let pk_ident_for_pool = primary_key.map(|(ident, _)| ident);
4046 if let Some(pk_ident) = pk_ident_for_pool {
4047 if audited_fields.is_some() {
4048 quote! {
4049 pub async fn delete(
4056 &self,
4057 pool: &#root::sql::Pool,
4058 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4059 let _query = #root::core::DeleteQuery::by_pk(
4060 <Self as #root::core::Model>::SCHEMA,
4061 #pk_column_lit,
4062 ::core::convert::Into::<#root::core::SqlValue>::into(
4063 ::core::clone::Clone::clone(&self.#pk_ident)
4064 ),
4065 );
4066 let _audit_entry =
4067 self.__rustango_audit_entry(#root::audit::AuditOp::Delete);
4068 #root::audit::delete_one_with_audit(
4069 pool, &_query, &_audit_entry,
4070 ).await
4071 }
4072 }
4073 } else {
4074 quote! {
4075 pub async fn delete(
4083 &self,
4084 pool: &#root::sql::Pool,
4085 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4086 self.__rustango_delete_exec(pool).await
4087 }
4088 }
4089 }
4090 } else {
4091 quote!()
4092 }
4093 };
4094
4095 let (bulk_delete_run, destroy_run, bulk_update_run, truncate_run, truncate_cascade_run) =
4098 if audited_fields.is_some() {
4099 (
4100 quote!(#root::audit::delete_many_with_audit::<Self>(pool, &_query, |_r: &Self| {
4101 _r.__rustango_audit_entry(#root::audit::AuditOp::Delete)
4102 })
4103 .await),
4104 quote!(#root::audit::delete_each_with_audit::<Self>(pool, &_queries, |_r: &Self| {
4105 _r.__rustango_audit_entry(#root::audit::AuditOp::Delete)
4106 })
4107 .await),
4108 quote!(#root::audit::update_many_with_audit::<Self>(pool, &_query, |_r: &Self| {
4109 _r.__rustango_audit_entry(#root::audit::AuditOp::Update)
4110 })
4111 .await),
4112 quote!(#root::audit::truncate_with_audit(
4113 pool,
4114 <Self as #root::core::Model>::SCHEMA.table,
4115 #root::sql::TruncateMode::Plain,
4116 )
4117 .await),
4118 quote!(#root::audit::truncate_with_audit(
4119 pool,
4120 <Self as #root::core::Model>::SCHEMA.table,
4121 #root::sql::TruncateMode::Cascade,
4122 )
4123 .await
4124 .map(drop)),
4125 )
4126 } else {
4127 (
4128 quote!(#root::sql::delete_pool(pool, &_query).await),
4129 quote!(#root::sql::__rustango_delete_all_or_none(pool, _queries).await),
4130 quote!(#root::sql::update_pool(pool, &_query).await),
4131 quote!(#root::sql::truncate(pool, <Self as #root::core::Model>::SCHEMA).await),
4132 quote!(#root::sql::truncate_cascade(pool, <Self as #root::core::Model>::SCHEMA).await),
4133 )
4134 };
4135
4136 let refresh_replicate_methods = if let Some((pk_ident, _)) = primary_key {
4149 let other_field_clones: Vec<TokenStream2> = fields
4150 .column_entries
4151 .iter()
4152 .filter(|c| &c.ident != pk_ident)
4153 .map(|c| {
4154 let ident = &c.ident;
4155 quote! {
4156 #ident: ::core::clone::Clone::clone(&self.#ident)
4157 }
4158 })
4159 .collect();
4160 let pk_clone_token = if fields.pk_is_auto {
4161 quote! { #pk_ident: #root::sql::Auto::Unset }
4162 } else {
4163 quote! { #pk_ident: ::core::clone::Clone::clone(&self.#pk_ident) }
4164 };
4165 let replicate_doc = if fields.pk_is_auto {
4166 quote! {
4167 }
4179 } else {
4180 quote! {
4181 }
4187 };
4188 let column_names: ::std::collections::HashSet<String> = fields
4202 .column_entries
4203 .iter()
4204 .map(|c| ident_name(&c.ident))
4205 .collect();
4206 let emit_if_no_field_collision = |name: &str, tokens: TokenStream2| -> TokenStream2 {
4207 if column_names.contains(name) {
4208 quote! {}
4209 } else {
4210 tokens
4211 }
4212 };
4213 let count_method = emit_if_no_field_collision(
4214 "count",
4215 quote! {
4216 pub async fn count(
4228 pool: &#root::sql::Pool,
4229 ) -> ::core::result::Result<i64, #root::sql::ExecError> {
4230 use #root::sql::CounterPool as _;
4231 #root::query::QuerySet::<Self>::default()
4232 .count(pool)
4233 .await
4234 }
4235 },
4236 );
4237 let value_method = emit_if_no_field_collision(
4238 "value",
4239 quote! {
4240 pub async fn value<U>(
4250 col: &str,
4251 pool: &#root::sql::Pool,
4252 ) -> ::core::result::Result<
4253 ::core::option::Option<U>,
4254 #root::sql::ExecError,
4255 >
4256 where
4257 U: #root::sql::FlatScalar,
4258 {
4259 let _col_static: &'static str = Self::__resolve_col(col)?;
4260 #root::query::QuerySet::<Self>::default()
4261 .values_list_flat(_col_static)
4262 .first::<U>(pool)
4263 .await
4264 }
4265 },
4266 );
4267 let sum_method = emit_if_no_field_collision(
4268 "sum",
4269 quote! {
4270 pub async fn sum<U>(
4277 col: &str,
4278 pool: &#root::sql::Pool,
4279 ) -> ::core::result::Result<
4280 ::core::option::Option<U>,
4281 #root::sql::ExecError,
4282 >
4283 where
4284 (::core::option::Option<U>,): #root::sql::MaybePgFromRow
4285 + #root::sql::MaybeMyFromRow
4286 + #root::sql::MaybeSqliteFromRow
4287 + ::core::marker::Send
4288 + ::core::marker::Unpin,
4289 {
4290 #root::query::QuerySet::<Self>::default()
4291 .sum::<U>(col, pool)
4292 .await
4293 }
4294 },
4295 );
4296 let avg_method = emit_if_no_field_collision(
4297 "avg",
4298 quote! {
4299 pub async fn avg<U>(
4305 col: &str,
4306 pool: &#root::sql::Pool,
4307 ) -> ::core::result::Result<
4308 ::core::option::Option<U>,
4309 #root::sql::ExecError,
4310 >
4311 where
4312 (::core::option::Option<U>,): #root::sql::MaybePgFromRow
4313 + #root::sql::MaybeMyFromRow
4314 + #root::sql::MaybeSqliteFromRow
4315 + ::core::marker::Send
4316 + ::core::marker::Unpin,
4317 {
4318 #root::query::QuerySet::<Self>::default()
4319 .avg::<U>(col, pool)
4320 .await
4321 }
4322 },
4323 );
4324 let min_method = emit_if_no_field_collision(
4325 "min",
4326 quote! {
4327 pub async fn min<U>(
4333 col: &str,
4334 pool: &#root::sql::Pool,
4335 ) -> ::core::result::Result<
4336 ::core::option::Option<U>,
4337 #root::sql::ExecError,
4338 >
4339 where
4340 (::core::option::Option<U>,): #root::sql::MaybePgFromRow
4341 + #root::sql::MaybeMyFromRow
4342 + #root::sql::MaybeSqliteFromRow
4343 + ::core::marker::Send
4344 + ::core::marker::Unpin,
4345 {
4346 #root::query::QuerySet::<Self>::default()
4347 .min::<U>(col, pool)
4348 .await
4349 }
4350 },
4351 );
4352 let max_method = emit_if_no_field_collision(
4353 "max",
4354 quote! {
4355 pub async fn max<U>(
4361 col: &str,
4362 pool: &#root::sql::Pool,
4363 ) -> ::core::result::Result<
4364 ::core::option::Option<U>,
4365 #root::sql::ExecError,
4366 >
4367 where
4368 (::core::option::Option<U>,): #root::sql::MaybePgFromRow
4369 + #root::sql::MaybeMyFromRow
4370 + #root::sql::MaybeSqliteFromRow
4371 + ::core::marker::Send
4372 + ::core::marker::Unpin,
4373 {
4374 #root::query::QuerySet::<Self>::default()
4375 .max::<U>(col, pool)
4376 .await
4377 }
4378 },
4379 );
4380 let first_method = emit_if_no_field_collision(
4381 "first",
4382 quote! {
4383 pub async fn first(
4390 pool: &#root::sql::Pool,
4391 ) -> ::core::result::Result<
4392 ::core::option::Option<Self>,
4393 #root::sql::ExecError,
4394 > {
4395 #root::query::QuerySet::<Self>::default()
4396 .first(pool)
4397 .await
4398 }
4399 },
4400 );
4401 let last_method = emit_if_no_field_collision(
4402 "last",
4403 quote! {
4404 pub async fn last(
4417 pool: &#root::sql::Pool,
4418 ) -> ::core::result::Result<
4419 ::core::option::Option<Self>,
4420 #root::sql::ExecError,
4421 > {
4422 #root::query::QuerySet::<Self>::default()
4423 .last(pool)
4424 .await
4425 }
4426 },
4427 );
4428 quote! {
4429 pub async fn refresh_from_db(
4448 &mut self,
4449 pool: &#root::sql::Pool,
4450 ) -> ::core::result::Result<(), #root::sql::ExecError> {
4451 match self.fresh(pool).await? {
4452 ::core::option::Option::Some(_fresh) => {
4453 *self = _fresh;
4454 ::core::result::Result::Ok(())
4455 }
4456 ::core::option::Option::None => ::core::result::Result::Err(
4457 #root::sql::ExecError::Driver(
4458 #root::sql::sqlx::Error::RowNotFound,
4459 ),
4460 ),
4461 }
4462 }
4463
4464 pub async fn increment(
4484 &self,
4485 col: &str,
4486 by: i64,
4487 pool: &#root::sql::Pool,
4488 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4489 Self::__increment_one(self, col, by, pool).await
4490 }
4491
4492 pub async fn decrement(
4501 &self,
4502 col: &str,
4503 by: i64,
4504 pool: &#root::sql::Pool,
4505 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4506 Self::__increment_one(self, col, -by, pool).await
4507 }
4508
4509 pub async fn increment_each(
4519 col: &str,
4520 by: i64,
4521 pool: &#root::sql::Pool,
4522 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4523 Self::__increment_all(col, by, pool).await
4524 }
4525
4526 pub async fn decrement_each(
4531 col: &str,
4532 by: i64,
4533 pool: &#root::sql::Pool,
4534 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4535 Self::__increment_all(col, -by, pool).await
4536 }
4537
4538 #[doc(hidden)]
4544 pub async fn __increment_one(
4545 this: &Self,
4546 col: &str,
4547 by: i64,
4548 pool: &#root::sql::Pool,
4549 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4550 let _pk_val: #root::core::SqlValue = ::core::convert::Into::into(
4551 ::core::clone::Clone::clone(&this.#pk_ident),
4552 );
4553 #root::sql::model_shortcuts::increment_one_pool::<Self>(
4554 ::core::stringify!(#pk_ident),
4555 _pk_val,
4556 col,
4557 by,
4558 pool,
4559 )
4560 .await
4561 }
4562
4563 #[doc(hidden)]
4566 pub async fn __increment_all(
4567 col: &str,
4568 by: i64,
4569 pool: &#root::sql::Pool,
4570 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4571 let _query =
4572 #root::sql::model_shortcuts::increment_all_query::<Self>(col, by)?;
4573 #bulk_update_run
4574 }
4575
4576 #[doc(hidden)]
4579 pub fn __resolve_col(
4580 col: &str,
4581 ) -> ::core::result::Result<&'static str, #root::sql::ExecError> {
4582 #root::sql::model_shortcuts::resolve_col::<Self>(col)
4583 }
4584
4585 #[doc(hidden)]
4588 #[must_use]
4589 pub fn __add_signed_expr(
4590 col_static: &'static str,
4591 signed_by: i64,
4592 ) -> #root::core::Expr {
4593 #root::sql::model_shortcuts::add_signed_expr(col_static, signed_by)
4594 }
4595
4596 pub async fn fresh(
4616 &self,
4617 pool: &#root::sql::Pool,
4618 ) -> ::core::result::Result<
4619 ::core::option::Option<Self>,
4620 #root::sql::ExecError,
4621 > {
4622 use #root::sql::FetcherPool as _;
4623 let _pk_val: #root::core::SqlValue = ::core::convert::Into::into(
4624 ::core::clone::Clone::clone(&self.#pk_ident),
4625 );
4626 let _rows: ::std::vec::Vec<Self> =
4628 #root::query::QuerySet::<Self>::default()
4629 .without_global_scopes()
4630 .filter(::core::stringify!(#pk_ident), _pk_val)
4631 .limit(1)
4632 .fetch(pool)
4633 .await?;
4634 ::core::result::Result::Ok(_rows.into_iter().next())
4635 }
4636
4637 #replicate_doc
4638 #[must_use]
4639 pub fn replicate(&self) -> Self {
4640 Self {
4641 #pk_clone_token,
4642 #( #other_field_clones, )*
4643 }
4644 }
4645
4646 #first_method
4647
4648 #last_method
4649
4650 pub async fn first_or_fail(
4660 pool: &#root::sql::Pool,
4661 ) -> ::core::result::Result<Self, #root::sql::ExecError> {
4662 match #root::query::QuerySet::<Self>::default().first(pool).await? {
4666 ::core::option::Option::Some(_row) => ::core::result::Result::Ok(_row),
4667 ::core::option::Option::None => ::core::result::Result::Err(
4668 #root::sql::ExecError::Driver(
4669 #root::sql::sqlx::Error::RowNotFound,
4670 ),
4671 ),
4672 }
4673 }
4674
4675 pub async fn pluck<U>(
4687 col: &'static str,
4688 pool: &#root::sql::Pool,
4689 ) -> ::core::result::Result<::std::vec::Vec<U>, #root::sql::ExecError>
4690 where
4691 U: #root::sql::FlatScalar,
4692 {
4693 #root::query::QuerySet::<Self>::default()
4694 .values_list_flat(col)
4695 .fetch::<U>(pool)
4696 .await
4697 }
4698
4699 pub async fn chunk<F, Fut>(
4725 n: i64,
4726 pool: &#root::sql::Pool,
4727 mut cb: F,
4728 ) -> ::core::result::Result<(), #root::sql::ExecError>
4729 where
4730 F: ::core::ops::FnMut(::std::vec::Vec<Self>) -> Fut,
4731 Fut: ::core::future::Future<
4732 Output = ::core::result::Result<(), #root::sql::ExecError>,
4733 >,
4734 {
4735 use #root::sql::FetcherPool as _;
4736 let pk_col = match <Self as #root::core::Model>::SCHEMA.primary_key() {
4738 ::core::option::Option::Some(f) => f.name,
4739 ::core::option::Option::None => {
4740 return ::core::result::Result::Ok(());
4741 }
4742 };
4743 let mut offset: i64 = 0;
4744 loop {
4745 let rows: ::std::vec::Vec<Self> =
4746 #root::query::QuerySet::<Self>::default()
4747 .order_by(&[(pk_col, false)])
4748 .limit(n)
4749 .offset(offset)
4750 .fetch(pool)
4751 .await?;
4752 if rows.is_empty() {
4753 return ::core::result::Result::Ok(());
4754 }
4755 let len = rows.len() as i64;
4756 cb(rows).await?;
4757 if len < n {
4758 return ::core::result::Result::Ok(());
4759 }
4760 offset += n;
4761 }
4762 }
4763
4764 pub async fn chunk_by_id<F, Fut>(
4776 n: i64,
4777 pool: &#root::sql::Pool,
4778 mut cb: F,
4779 ) -> ::core::result::Result<(), #root::sql::ExecError>
4780 where
4781 F: ::core::ops::FnMut(::std::vec::Vec<Self>) -> Fut,
4782 Fut: ::core::future::Future<
4783 Output = ::core::result::Result<(), #root::sql::ExecError>,
4784 >,
4785 {
4786 use #root::sql::FetcherPool as _;
4787 let pk_col = match <Self as #root::core::Model>::SCHEMA.primary_key() {
4789 ::core::option::Option::Some(f) => f.name,
4790 ::core::option::Option::None => {
4791 return ::core::result::Result::Ok(());
4792 }
4793 };
4794 let key = ::std::format!("{}__gt", pk_col);
4797 let mut last_seen: ::core::option::Option<#root::core::SqlValue> =
4798 ::core::option::Option::None;
4799 loop {
4800 let mut qs = #root::query::QuerySet::<Self>::default();
4801 if let ::core::option::Option::Some(v) = last_seen.take() {
4802 qs = qs.filter(key.as_str(), v);
4803 }
4804 let rows: ::std::vec::Vec<Self> = qs
4805 .order_by(&[(pk_col, false)])
4806 .limit(n)
4807 .fetch(pool)
4808 .await?;
4809 let len = rows.len() as i64;
4810 let ::core::option::Option::Some(max_pk) =
4813 rows.last().map(|r| r.__rustango_pk_value())
4814 else {
4815 return ::core::result::Result::Ok(());
4816 };
4817 cb(rows).await?;
4818 if len < n {
4819 return ::core::result::Result::Ok(());
4820 }
4821 last_seen = ::core::option::Option::Some(max_pk);
4822 }
4823 }
4824
4825 pub async fn each<F, Fut>(
4843 batch: i64,
4844 pool: &#root::sql::Pool,
4845 mut cb: F,
4846 ) -> ::core::result::Result<(), #root::sql::ExecError>
4847 where
4848 F: ::core::ops::FnMut(Self) -> Fut,
4849 Fut: ::core::future::Future<
4850 Output = ::core::result::Result<(), #root::sql::ExecError>,
4851 >,
4852 {
4853 use #root::sql::FetcherPool as _;
4854 let pk_col = match <Self as #root::core::Model>::SCHEMA.primary_key() {
4856 ::core::option::Option::Some(f) => f.name,
4857 ::core::option::Option::None => {
4858 return ::core::result::Result::Ok(());
4859 }
4860 };
4861 let key = ::std::format!("{}__gt", pk_col);
4862 let mut last_seen: ::core::option::Option<#root::core::SqlValue> =
4863 ::core::option::Option::None;
4864 loop {
4865 let mut qs = #root::query::QuerySet::<Self>::default();
4866 if let ::core::option::Option::Some(v) = last_seen.take() {
4867 qs = qs.filter(key.as_str(), v);
4868 }
4869 let rows: ::std::vec::Vec<Self> = qs
4870 .order_by(&[(pk_col, false)])
4871 .limit(batch)
4872 .fetch(pool)
4873 .await?;
4874 let len = rows.len() as i64;
4875 let ::core::option::Option::Some(max_pk) =
4876 rows.last().map(|r| r.__rustango_pk_value())
4877 else {
4878 return ::core::result::Result::Ok(());
4879 };
4880 for row in rows {
4881 cb(row).await?;
4882 }
4883 if len < batch {
4884 return ::core::result::Result::Ok(());
4885 }
4886 last_seen = ::core::option::Option::Some(max_pk);
4887 }
4888 }
4889
4890 pub async fn truncate(
4906 pool: &#root::sql::Pool,
4907 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
4908 #truncate_run
4909 }
4910
4911 pub async fn truncate_cascade(
4920 pool: &#root::sql::Pool,
4921 ) -> ::core::result::Result<(), #root::sql::ExecError> {
4922 #truncate_cascade_run
4923 }
4924
4925 pub async fn destroy<V>(
4940 pks: impl ::core::iter::IntoIterator<Item = V>,
4941 pool: &#root::sql::Pool,
4942 ) -> ::core::result::Result<u64, #root::sql::ExecError>
4943 where
4944 V: ::core::convert::Into<#root::core::SqlValue>,
4945 {
4946 let _values: ::std::vec::Vec<#root::core::SqlValue> =
4947 pks.into_iter().map(::core::convert::Into::into).collect();
4948 if _values.is_empty() {
4949 return ::core::result::Result::Ok(0);
4950 }
4951 let _pk = <Self as #root::core::Model>::SCHEMA
4952 .primary_key()
4953 .ok_or_else(|| {
4954 #root::sql::ExecError::Sql(#root::sql::SqlError::MissingPrimaryKey)
4955 })?;
4956 let mut _queries = ::std::vec::Vec::new();
4958 for _keys in #root::sql::__rustango_in_batches::<Self>(pool, _values)? {
4959 _queries.push(
4960 #root::query::QuerySet::<Self>::default()
4961 .filter_op(
4962 _pk.name,
4963 #root::core::Op::In,
4964 #root::core::SqlValue::List(_keys),
4965 )
4966 .compile_delete()?,
4967 );
4968 }
4969 #destroy_run
4970 }
4971
4972 pub async fn where_(
4990 col: &str,
4991 val: impl ::core::convert::Into<#root::core::SqlValue>,
4992 pool: &#root::sql::Pool,
4993 ) -> ::core::result::Result<
4994 ::std::vec::Vec<Self>,
4995 #root::sql::ExecError,
4996 > {
4997 use #root::sql::FetcherPool as _;
4998 #root::query::QuerySet::<Self>::default()
4999 .filter(col, val)
5000 .fetch(pool)
5001 .await
5002 }
5003
5004 pub async fn where_in<V>(
5018 col: &str,
5019 vals: impl ::core::iter::IntoIterator<Item = V>,
5020 pool: &#root::sql::Pool,
5021 ) -> ::core::result::Result<
5022 ::std::vec::Vec<Self>,
5023 #root::sql::ExecError,
5024 >
5025 where
5026 V: ::core::convert::Into<#root::core::SqlValue>,
5027 {
5028 use #root::sql::FetcherPool as _;
5029 let _values: ::std::vec::Vec<#root::core::SqlValue> =
5030 vals.into_iter().map(::core::convert::Into::into).collect();
5031 if _values.is_empty() {
5032 return ::core::result::Result::Ok(::std::vec::Vec::new());
5033 }
5034 let _key = ::std::format!("{}__in", col);
5035 let _batches = #root::sql::__rustango_in_batches::<Self>(pool, _values)?;
5036 let _split = _batches.len() > 1;
5037 let mut _rows = ::std::vec::Vec::new();
5038 for _keys in _batches {
5039 _rows.extend(
5040 #root::query::QuerySet::<Self>::default()
5041 .filter(&_key, #root::core::SqlValue::List(_keys))
5042 .fetch(pool)
5043 .await?,
5044 );
5045 }
5046 if _split {
5047 _rows = #root::sql::__rustango_dedupe_by_pk(_rows);
5048 }
5049 ::core::result::Result::Ok(_rows)
5050 }
5051
5052 pub async fn where_not_in<V>(
5063 col: &str,
5064 vals: impl ::core::iter::IntoIterator<Item = V>,
5065 pool: &#root::sql::Pool,
5066 ) -> ::core::result::Result<
5067 ::std::vec::Vec<Self>,
5068 #root::sql::ExecError,
5069 >
5070 where
5071 V: ::core::convert::Into<#root::core::SqlValue>,
5072 {
5073 use #root::sql::FetcherPool as _;
5074 let _values: ::std::vec::Vec<#root::core::SqlValue> =
5075 vals.into_iter().map(::core::convert::Into::into).collect();
5076 if _values.is_empty() {
5077 return #root::query::QuerySet::<Self>::default()
5078 .fetch(pool)
5079 .await;
5080 }
5081 let _key = ::std::format!("{}__not_in", col);
5082 let _values = #root::sql::__rustango_one_in_list::<Self>(pool, _values)?;
5084 #root::query::QuerySet::<Self>::default()
5085 .filter(&_key, #root::core::SqlValue::List(_values))
5086 .fetch(pool)
5087 .await
5088 }
5089
5090 pub async fn where_null(
5098 col: &str,
5099 pool: &#root::sql::Pool,
5100 ) -> ::core::result::Result<
5101 ::std::vec::Vec<Self>,
5102 #root::sql::ExecError,
5103 > {
5104 use #root::sql::FetcherPool as _;
5105 let _key = ::std::format!("{}__isnull", col);
5106 #root::query::QuerySet::<Self>::default()
5107 .filter(&_key, #root::core::SqlValue::Bool(true))
5108 .fetch(pool)
5109 .await
5110 }
5111
5112 pub async fn where_not_null(
5120 col: &str,
5121 pool: &#root::sql::Pool,
5122 ) -> ::core::result::Result<
5123 ::std::vec::Vec<Self>,
5124 #root::sql::ExecError,
5125 > {
5126 use #root::sql::FetcherPool as _;
5127 let _key = ::std::format!("{}__isnull", col);
5128 #root::query::QuerySet::<Self>::default()
5129 .filter(&_key, #root::core::SqlValue::Bool(false))
5130 .fetch(pool)
5131 .await
5132 }
5133
5134 pub async fn random_n(
5147 n: i64,
5148 pool: &#root::sql::Pool,
5149 ) -> ::core::result::Result<
5150 ::std::vec::Vec<Self>,
5151 #root::sql::ExecError,
5152 > {
5153 use #root::sql::FetcherPool as _;
5154 #root::query::QuerySet::<Self>::default()
5155 .order_random()
5156 .limit(n)
5157 .fetch(pool)
5158 .await
5159 }
5160
5161 pub async fn random(
5170 pool: &#root::sql::Pool,
5171 ) -> ::core::result::Result<
5172 ::core::option::Option<Self>,
5173 #root::sql::ExecError,
5174 > {
5175 ::core::result::Result::Ok(
5176 Self::random_n(1, pool).await?.into_iter().next(),
5177 )
5178 }
5179
5180 pub async fn oldest(
5189 field: &str,
5190 pool: &#root::sql::Pool,
5191 ) -> ::core::result::Result<
5192 ::std::vec::Vec<Self>,
5193 #root::sql::ExecError,
5194 > {
5195 use #root::sql::FetcherPool as _;
5196 #root::query::QuerySet::<Self>::default()
5197 .order_by(&[(field, false)])
5198 .fetch(pool)
5199 .await
5200 }
5201
5202 pub async fn newest(
5211 field: &str,
5212 pool: &#root::sql::Pool,
5213 ) -> ::core::result::Result<
5214 ::std::vec::Vec<Self>,
5215 #root::sql::ExecError,
5216 > {
5217 use #root::sql::FetcherPool as _;
5218 #root::query::QuerySet::<Self>::default()
5219 .order_by(&[(field, true)])
5220 .fetch(pool)
5221 .await
5222 }
5223
5224 pub async fn where_year(
5234 col: &str,
5235 year: i64,
5236 pool: &#root::sql::Pool,
5237 ) -> ::core::result::Result<
5238 ::std::vec::Vec<Self>,
5239 #root::sql::ExecError,
5240 > {
5241 use #root::sql::FetcherPool as _;
5242 let _key = ::std::format!("{}__year", col);
5243 #root::query::QuerySet::<Self>::default()
5244 .filter(&_key, #root::core::SqlValue::I64(year))
5245 .fetch(pool)
5246 .await
5247 }
5248
5249 pub async fn where_month(
5258 col: &str,
5259 month: i64,
5260 pool: &#root::sql::Pool,
5261 ) -> ::core::result::Result<
5262 ::std::vec::Vec<Self>,
5263 #root::sql::ExecError,
5264 > {
5265 use #root::sql::FetcherPool as _;
5266 let _key = ::std::format!("{}__month", col);
5267 #root::query::QuerySet::<Self>::default()
5268 .filter(&_key, #root::core::SqlValue::I64(month))
5269 .fetch(pool)
5270 .await
5271 }
5272
5273 pub async fn where_day(
5282 col: &str,
5283 day: i64,
5284 pool: &#root::sql::Pool,
5285 ) -> ::core::result::Result<
5286 ::std::vec::Vec<Self>,
5287 #root::sql::ExecError,
5288 > {
5289 use #root::sql::FetcherPool as _;
5290 let _key = ::std::format!("{}__day", col);
5291 #root::query::QuerySet::<Self>::default()
5292 .filter(&_key, #root::core::SqlValue::I64(day))
5293 .fetch(pool)
5294 .await
5295 }
5296
5297 pub async fn where_hour(
5306 col: &str,
5307 hour: i64,
5308 pool: &#root::sql::Pool,
5309 ) -> ::core::result::Result<
5310 ::std::vec::Vec<Self>,
5311 #root::sql::ExecError,
5312 > {
5313 use #root::sql::FetcherPool as _;
5314 let _key = ::std::format!("{}__hour", col);
5315 #root::query::QuerySet::<Self>::default()
5316 .filter(&_key, #root::core::SqlValue::I64(hour))
5317 .fetch(pool)
5318 .await
5319 }
5320
5321 pub async fn where_minute(
5330 col: &str,
5331 minute: i64,
5332 pool: &#root::sql::Pool,
5333 ) -> ::core::result::Result<
5334 ::std::vec::Vec<Self>,
5335 #root::sql::ExecError,
5336 > {
5337 use #root::sql::FetcherPool as _;
5338 let _key = ::std::format!("{}__minute", col);
5339 #root::query::QuerySet::<Self>::default()
5340 .filter(&_key, #root::core::SqlValue::I64(minute))
5341 .fetch(pool)
5342 .await
5343 }
5344
5345 pub async fn where_like(
5355 col: &str,
5356 pattern: impl ::core::convert::Into<::std::string::String>,
5357 pool: &#root::sql::Pool,
5358 ) -> ::core::result::Result<
5359 ::std::vec::Vec<Self>,
5360 #root::sql::ExecError,
5361 > {
5362 use #root::sql::FetcherPool as _;
5363 let _key = ::std::format!("{}__like", col);
5364 #root::query::QuerySet::<Self>::default()
5365 .filter(
5366 &_key,
5367 #root::core::SqlValue::String(pattern.into()),
5368 )
5369 .fetch(pool)
5370 .await
5371 }
5372
5373 pub async fn where_ilike(
5382 col: &str,
5383 pattern: impl ::core::convert::Into<::std::string::String>,
5384 pool: &#root::sql::Pool,
5385 ) -> ::core::result::Result<
5386 ::std::vec::Vec<Self>,
5387 #root::sql::ExecError,
5388 > {
5389 use #root::sql::FetcherPool as _;
5390 let _key = ::std::format!("{}__ilike", col);
5391 #root::query::QuerySet::<Self>::default()
5392 .filter(
5393 &_key,
5394 #root::core::SqlValue::String(pattern.into()),
5395 )
5396 .fetch(pool)
5397 .await
5398 }
5399
5400 pub async fn where_starts_with(
5409 col: &str,
5410 prefix: impl ::core::convert::Into<::std::string::String>,
5411 pool: &#root::sql::Pool,
5412 ) -> ::core::result::Result<
5413 ::std::vec::Vec<Self>,
5414 #root::sql::ExecError,
5415 > {
5416 use #root::sql::FetcherPool as _;
5417 let _key = ::std::format!("{}__startswith", col);
5418 #root::query::QuerySet::<Self>::default()
5419 .filter(
5420 &_key,
5421 #root::core::SqlValue::String(prefix.into()),
5422 )
5423 .fetch(pool)
5424 .await
5425 }
5426
5427 pub async fn where_ends_with(
5436 col: &str,
5437 suffix: impl ::core::convert::Into<::std::string::String>,
5438 pool: &#root::sql::Pool,
5439 ) -> ::core::result::Result<
5440 ::std::vec::Vec<Self>,
5441 #root::sql::ExecError,
5442 > {
5443 use #root::sql::FetcherPool as _;
5444 let _key = ::std::format!("{}__endswith", col);
5445 #root::query::QuerySet::<Self>::default()
5446 .filter(
5447 &_key,
5448 #root::core::SqlValue::String(suffix.into()),
5449 )
5450 .fetch(pool)
5451 .await
5452 }
5453
5454 pub async fn where_contains(
5463 col: &str,
5464 substr: impl ::core::convert::Into<::std::string::String>,
5465 pool: &#root::sql::Pool,
5466 ) -> ::core::result::Result<
5467 ::std::vec::Vec<Self>,
5468 #root::sql::ExecError,
5469 > {
5470 use #root::sql::FetcherPool as _;
5471 let _key = ::std::format!("{}__contains", col);
5472 #root::query::QuerySet::<Self>::default()
5473 .filter(
5474 &_key,
5475 #root::core::SqlValue::String(substr.into()),
5476 )
5477 .fetch(pool)
5478 .await
5479 }
5480
5481 pub async fn where_gt(
5489 col: &str,
5490 val: impl ::core::convert::Into<#root::core::SqlValue>,
5491 pool: &#root::sql::Pool,
5492 ) -> ::core::result::Result<
5493 ::std::vec::Vec<Self>,
5494 #root::sql::ExecError,
5495 > {
5496 use #root::sql::FetcherPool as _;
5497 let _key = ::std::format!("{}__gt", col);
5498 #root::query::QuerySet::<Self>::default()
5499 .filter(&_key, val)
5500 .fetch(pool)
5501 .await
5502 }
5503
5504 pub async fn where_gte(
5512 col: &str,
5513 val: impl ::core::convert::Into<#root::core::SqlValue>,
5514 pool: &#root::sql::Pool,
5515 ) -> ::core::result::Result<
5516 ::std::vec::Vec<Self>,
5517 #root::sql::ExecError,
5518 > {
5519 use #root::sql::FetcherPool as _;
5520 let _key = ::std::format!("{}__gte", col);
5521 #root::query::QuerySet::<Self>::default()
5522 .filter(&_key, val)
5523 .fetch(pool)
5524 .await
5525 }
5526
5527 pub async fn where_lt(
5535 col: &str,
5536 val: impl ::core::convert::Into<#root::core::SqlValue>,
5537 pool: &#root::sql::Pool,
5538 ) -> ::core::result::Result<
5539 ::std::vec::Vec<Self>,
5540 #root::sql::ExecError,
5541 > {
5542 use #root::sql::FetcherPool as _;
5543 let _key = ::std::format!("{}__lt", col);
5544 #root::query::QuerySet::<Self>::default()
5545 .filter(&_key, val)
5546 .fetch(pool)
5547 .await
5548 }
5549
5550 pub async fn where_lte(
5558 col: &str,
5559 val: impl ::core::convert::Into<#root::core::SqlValue>,
5560 pool: &#root::sql::Pool,
5561 ) -> ::core::result::Result<
5562 ::std::vec::Vec<Self>,
5563 #root::sql::ExecError,
5564 > {
5565 use #root::sql::FetcherPool as _;
5566 let _key = ::std::format!("{}__lte", col);
5567 #root::query::QuerySet::<Self>::default()
5568 .filter(&_key, val)
5569 .fetch(pool)
5570 .await
5571 }
5572
5573 pub async fn where_ne(
5581 col: &str,
5582 val: impl ::core::convert::Into<#root::core::SqlValue>,
5583 pool: &#root::sql::Pool,
5584 ) -> ::core::result::Result<
5585 ::std::vec::Vec<Self>,
5586 #root::sql::ExecError,
5587 > {
5588 use #root::sql::FetcherPool as _;
5589 let _key = ::std::format!("{}__ne", col);
5590 #root::query::QuerySet::<Self>::default()
5591 .filter(&_key, val)
5592 .fetch(pool)
5593 .await
5594 }
5595
5596 pub async fn where_any(
5610 cols: &[&str],
5611 val: impl ::core::convert::Into<#root::core::SqlValue>,
5612 pool: &#root::sql::Pool,
5613 ) -> ::core::result::Result<
5614 ::std::vec::Vec<Self>,
5615 #root::sql::ExecError,
5616 > {
5617 Self::__where_multi(cols, val, false, pool).await
5618 }
5619
5620 pub async fn where_all(
5627 cols: &[&str],
5628 val: impl ::core::convert::Into<#root::core::SqlValue>,
5629 pool: &#root::sql::Pool,
5630 ) -> ::core::result::Result<
5631 ::std::vec::Vec<Self>,
5632 #root::sql::ExecError,
5633 > {
5634 Self::__where_multi(cols, val, true, pool).await
5635 }
5636
5637 #[doc(hidden)]
5641 pub async fn __where_multi(
5642 cols: &[&str],
5643 val: impl ::core::convert::Into<#root::core::SqlValue>,
5644 all: bool,
5645 pool: &#root::sql::Pool,
5646 ) -> ::core::result::Result<
5647 ::std::vec::Vec<Self>,
5648 #root::sql::ExecError,
5649 > {
5650 #root::sql::model_shortcuts::where_multi_pool::<Self>(cols, val, all, pool)
5651 .await
5652 }
5653
5654 pub async fn take(
5664 n: i64,
5665 pool: &#root::sql::Pool,
5666 ) -> ::core::result::Result<
5667 ::std::vec::Vec<Self>,
5668 #root::sql::ExecError,
5669 > {
5670 use #root::sql::FetcherPool as _;
5671 #root::query::QuerySet::<Self>::default()
5672 .limit(n)
5673 .fetch(pool)
5674 .await
5675 }
5676
5677 pub async fn for_page(
5689 page: i64,
5690 per_page: i64,
5691 pool: &#root::sql::Pool,
5692 ) -> ::core::result::Result<
5693 ::std::vec::Vec<Self>,
5694 #root::sql::ExecError,
5695 > {
5696 use #root::sql::FetcherPool as _;
5697 let _offset = #root::list_params::page_offset(page, per_page);
5698 #root::query::QuerySet::<Self>::default()
5699 .limit(per_page)
5700 .offset(_offset)
5701 .fetch(pool)
5702 .await
5703 }
5704
5705 pub async fn paginate(
5721 page: i64,
5722 per_page: i64,
5723 pool: &#root::sql::Pool,
5724 ) -> ::core::result::Result<
5725 (::std::vec::Vec<Self>, i64),
5726 #root::sql::ExecError,
5727 > {
5728 let total = {
5734 use #root::sql::CounterPool as _;
5735 #root::query::QuerySet::<Self>::default()
5736 .count(pool)
5737 .await?
5738 };
5739 let rows = Self::for_page(page, per_page, pool).await?;
5740 ::core::result::Result::Ok((rows, total))
5741 }
5742
5743 pub async fn update_where(
5757 where_col: &str,
5758 where_val: impl ::core::convert::Into<#root::core::SqlValue>,
5759 set_col: &str,
5760 set_val: impl ::core::convert::Into<#root::core::SqlValue>,
5761 pool: &#root::sql::Pool,
5762 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
5763 let _query = #root::query::QuerySet::<Self>::default()
5764 .filter(where_col, where_val)
5765 .update()
5766 .set(set_col, set_val)
5767 .compile()?;
5768 #bulk_update_run
5769 }
5770
5771 pub async fn delete_where(
5779 where_col: &str,
5780 where_val: impl ::core::convert::Into<#root::core::SqlValue>,
5781 pool: &#root::sql::Pool,
5782 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
5783 let _query = #root::query::QuerySet::<Self>::default()
5784 .filter_op(where_col, #root::core::Op::Eq, where_val)
5785 .compile_delete()?;
5786 #bulk_delete_run
5787 }
5788
5789 pub async fn update_all(
5802 set_col: &str,
5803 set_val: impl ::core::convert::Into<#root::core::SqlValue>,
5804 pool: &#root::sql::Pool,
5805 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
5806 let _query = #root::query::QuerySet::<Self>::default()
5807 .update()
5808 .set(set_col, set_val)
5809 .compile()?;
5810 #bulk_update_run
5811 }
5812
5813 pub async fn where_not_like(
5822 col: &str,
5823 pattern: impl ::core::convert::Into<::std::string::String>,
5824 pool: &#root::sql::Pool,
5825 ) -> ::core::result::Result<
5826 ::std::vec::Vec<Self>,
5827 #root::sql::ExecError,
5828 > {
5829 use #root::sql::FetcherPool as _;
5830 let _key = ::std::format!("{}__not_like", col);
5831 #root::query::QuerySet::<Self>::default()
5832 .filter(
5833 &_key,
5834 #root::core::SqlValue::String(pattern.into()),
5835 )
5836 .fetch(pool)
5837 .await
5838 }
5839
5840 pub async fn where_not_ilike(
5849 col: &str,
5850 pattern: impl ::core::convert::Into<::std::string::String>,
5851 pool: &#root::sql::Pool,
5852 ) -> ::core::result::Result<
5853 ::std::vec::Vec<Self>,
5854 #root::sql::ExecError,
5855 > {
5856 use #root::sql::FetcherPool as _;
5857 let _key = ::std::format!("{}__not_ilike", col);
5858 #root::query::QuerySet::<Self>::default()
5859 .filter(
5860 &_key,
5861 #root::core::SqlValue::String(pattern.into()),
5862 )
5863 .fetch(pool)
5864 .await
5865 }
5866
5867 pub async fn where_not_between(
5876 col: &str,
5877 lo: impl ::core::convert::Into<#root::core::SqlValue>,
5878 hi: impl ::core::convert::Into<#root::core::SqlValue>,
5879 pool: &#root::sql::Pool,
5880 ) -> ::core::result::Result<
5881 ::std::vec::Vec<Self>,
5882 #root::sql::ExecError,
5883 > {
5884 use #root::sql::FetcherPool as _;
5885 let _key = ::std::format!("{}__not_between", col);
5886 let _vals = #root::core::SqlValue::List(::std::vec![
5887 ::core::convert::Into::into(lo),
5888 ::core::convert::Into::into(hi),
5889 ]);
5890 #root::query::QuerySet::<Self>::default()
5891 .filter(&_key, _vals)
5892 .fetch(pool)
5893 .await
5894 }
5895
5896 #[must_use]
5899 pub fn table_name() -> &'static str {
5900 <Self as #root::core::Model>::SCHEMA.table
5901 }
5902
5903 #[must_use]
5908 pub fn primary_key_column() -> ::core::option::Option<&'static str> {
5909 <Self as #root::core::Model>::SCHEMA
5910 .primary_key()
5911 .map(|f| f.column)
5912 }
5913
5914 pub async fn where_between(
5923 col: &str,
5924 lo: impl ::core::convert::Into<#root::core::SqlValue>,
5925 hi: impl ::core::convert::Into<#root::core::SqlValue>,
5926 pool: &#root::sql::Pool,
5927 ) -> ::core::result::Result<
5928 ::std::vec::Vec<Self>,
5929 #root::sql::ExecError,
5930 > {
5931 use #root::sql::FetcherPool as _;
5932 let _key = ::std::format!("{}__between", col);
5933 let _vals = #root::core::SqlValue::List(::std::vec![
5934 ::core::convert::Into::into(lo),
5935 ::core::convert::Into::into(hi),
5936 ]);
5937 #root::query::QuerySet::<Self>::default()
5938 .filter(&_key, _vals)
5939 .fetch(pool)
5940 .await
5941 }
5942
5943 pub async fn first_where(
5958 col: &str,
5959 val: impl ::core::convert::Into<#root::core::SqlValue>,
5960 pool: &#root::sql::Pool,
5961 ) -> ::core::result::Result<
5962 ::core::option::Option<Self>,
5963 #root::sql::ExecError,
5964 > {
5965 #root::query::QuerySet::<Self>::default()
5966 .filter(col, val)
5967 .first(pool)
5968 .await
5969 }
5970
5971 #value_method
5972
5973 pub async fn latest(
5987 field: &str,
5988 pool: &#root::sql::Pool,
5989 ) -> ::core::result::Result<
5990 ::core::option::Option<Self>,
5991 #root::sql::ExecError,
5992 > {
5993 #root::query::QuerySet::<Self>::default()
5994 .latest(field, pool)
5995 .await
5996 }
5997
5998 pub async fn earliest(
6004 field: &str,
6005 pool: &#root::sql::Pool,
6006 ) -> ::core::result::Result<
6007 ::core::option::Option<Self>,
6008 #root::sql::ExecError,
6009 > {
6010 #root::query::QuerySet::<Self>::default()
6011 .earliest(field, pool)
6012 .await
6013 }
6014
6015 #count_method
6016
6017 pub async fn exists(
6026 pool: &#root::sql::Pool,
6027 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
6028 use #root::sql::ExistsPool as _;
6029 #root::query::QuerySet::<Self>::default()
6030 .exists(pool)
6031 .await
6032 }
6033
6034 pub async fn doesnt_exist(
6041 pool: &#root::sql::Pool,
6042 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
6043 Self::exists(pool).await.map(|e| !e)
6044 }
6045
6046 pub async fn contains_pk(
6060 pk: impl ::core::convert::Into<#root::core::SqlValue> + ::core::marker::Send,
6061 pool: &#root::sql::Pool,
6062 ) -> ::core::result::Result<bool, #root::sql::ExecError> {
6063 use #root::sql::ExistsPool as _;
6064 #root::query::QuerySet::<Self>::default()
6065 .contains_pk(pool, pk)
6066 .await
6067 }
6068
6069 #sum_method
6070 #avg_method
6071 #min_method
6072 #max_method
6073
6074 pub async fn all(
6090 pool: &#root::sql::Pool,
6091 ) -> ::core::result::Result<::std::vec::Vec<Self>, #root::sql::ExecError>
6092 {
6093 use #root::sql::FetcherPool as _;
6094 #root::query::QuerySet::<Self>::default()
6095 .fetch(pool)
6096 .await
6097 }
6098
6099 pub async fn find_many<V>(
6122 pks: impl ::core::iter::IntoIterator<Item = V>,
6123 pool: &#root::sql::Pool,
6124 ) -> ::core::result::Result<
6125 ::std::vec::Vec<Self>,
6126 #root::sql::ExecError,
6127 >
6128 where
6129 V: ::core::convert::Into<#root::core::SqlValue>,
6130 {
6131 use #root::sql::FetcherPool as _;
6132 let _values: ::std::vec::Vec<#root::core::SqlValue> =
6133 pks.into_iter().map(::core::convert::Into::into).collect();
6134 if _values.is_empty() {
6135 return ::core::result::Result::Ok(::std::vec::Vec::new());
6136 }
6137 let _key = ::std::format!("{}__in", ::core::stringify!(#pk_ident));
6138 let _batches = #root::sql::__rustango_in_batches::<Self>(pool, _values)?;
6139 let _split = _batches.len() > 1;
6140 let mut _rows = ::std::vec::Vec::new();
6141 for _keys in _batches {
6142 _rows.extend(
6143 #root::query::QuerySet::<Self>::default()
6144 .filter(&_key, #root::core::SqlValue::List(_keys))
6145 .fetch(pool)
6146 .await?,
6147 );
6148 }
6149 if _split {
6150 _rows = #root::sql::__rustango_dedupe_by_pk(_rows);
6151 }
6152 ::core::result::Result::Ok(_rows)
6153 }
6154
6155 pub async fn find(
6170 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6171 pool: &#root::sql::Pool,
6172 ) -> ::core::result::Result<::core::option::Option<Self>, #root::sql::ExecError>
6173 {
6174 use #root::sql::FetcherPool as _;
6175 let _pk_val: #root::core::SqlValue = pk.into();
6176 let mut _rows: ::std::vec::Vec<Self> =
6177 #root::query::QuerySet::<Self>::default()
6178 .filter(::core::stringify!(#pk_ident), _pk_val)
6179 .limit(1)
6180 .fetch(pool)
6181 .await?;
6182 ::core::result::Result::Ok(_rows.into_iter().next())
6183 }
6184
6185 pub async fn find_or_fail(
6202 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6203 pool: &#root::sql::Pool,
6204 ) -> ::core::result::Result<Self, #root::sql::ExecError> {
6205 match Self::find(pk, pool).await? {
6206 ::core::option::Option::Some(_row) => ::core::result::Result::Ok(_row),
6207 ::core::option::Option::None => ::core::result::Result::Err(
6208 #root::sql::ExecError::Driver(
6209 #root::sql::sqlx::Error::RowNotFound,
6210 ),
6211 ),
6212 }
6213 }
6214
6215 pub async fn find_many_or_fail<V>(
6235 pks: impl ::core::iter::IntoIterator<Item = V>,
6236 pool: &#root::sql::Pool,
6237 ) -> ::core::result::Result<
6238 ::std::vec::Vec<Self>,
6239 #root::sql::ExecError,
6240 >
6241 where
6242 V: ::core::convert::Into<#root::core::SqlValue>,
6243 {
6244 let _values: ::std::vec::Vec<#root::core::SqlValue> =
6245 pks.into_iter().map(::core::convert::Into::into).collect();
6246 if _values.is_empty() {
6247 return ::core::result::Result::Ok(::std::vec::Vec::new());
6248 }
6249 use #root::sql::ExistsPool as _;
6250 let mut _seen: ::std::collections::HashSet<
6251 ::std::string::String,
6252 > = ::std::collections::HashSet::new();
6253 let mut _keys: ::std::vec::Vec<#root::core::SqlValue> =
6254 ::std::vec::Vec::new();
6255 for v in &_values {
6256 if _seen.insert(v.to_display_string()) {
6257 _keys.push(::core::clone::Clone::clone(v));
6258 }
6259 }
6260 let _rows = Self::find_many(_values, pool).await?;
6261 let _found: ::std::collections::HashSet<::std::string::String> = _rows
6262 .iter()
6263 .map(|r| r.__rustango_pk_value().to_display_string())
6264 .collect();
6265 for _key in _keys {
6268 if _found.contains(&_key.to_display_string()) {
6269 continue;
6270 }
6271 let _hit = #root::query::QuerySet::<Self>::default()
6272 .filter(::core::stringify!(#pk_ident), _key)
6273 .exists(pool)
6274 .await?;
6275 if !_hit {
6276 return ::core::result::Result::Err(
6277 #root::sql::ExecError::Driver(
6278 #root::sql::sqlx::Error::RowNotFound,
6279 ),
6280 );
6281 }
6282 }
6283 ::core::result::Result::Ok(_rows)
6284 }
6285
6286 pub async fn find_or<F>(
6301 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6302 pool: &#root::sql::Pool,
6303 fallback: F,
6304 ) -> ::core::result::Result<Self, #root::sql::ExecError>
6305 where
6306 F: ::core::ops::FnOnce() -> Self,
6307 {
6308 ::core::result::Result::Ok(
6309 Self::find(pk, pool).await?.unwrap_or_else(fallback),
6310 )
6311 }
6312
6313 pub async fn find_or_new<F>(
6327 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6328 pool: &#root::sql::Pool,
6329 fallback: F,
6330 ) -> ::core::result::Result<(Self, bool), #root::sql::ExecError>
6331 where
6332 F: ::core::ops::FnOnce() -> Self,
6333 {
6334 match Self::find(pk, pool).await? {
6335 ::core::option::Option::Some(_row) => ::core::result::Result::Ok((_row, true)),
6336 ::core::option::Option::None => {
6337 ::core::result::Result::Ok((fallback(), false))
6338 }
6339 }
6340 }
6341
6342 pub async fn find_or_insert<F>(
6366 pk: impl ::core::convert::Into<#root::core::SqlValue>,
6367 pool: &#root::sql::Pool,
6368 fallback: F,
6369 ) -> ::core::result::Result<(Self, bool), #root::sql::ExecError>
6370 where
6371 F: ::core::ops::FnOnce() -> Self,
6372 {
6373 if let ::core::option::Option::Some(_row) = Self::find(pk, pool).await? {
6374 return ::core::result::Result::Ok((_row, true));
6375 }
6376 let mut _new = fallback();
6377 _new.save(pool).await?;
6378 ::core::result::Result::Ok((_new, false))
6379 }
6380
6381 pub async fn first_or<F>(
6388 pool: &#root::sql::Pool,
6389 fallback: F,
6390 ) -> ::core::result::Result<Self, #root::sql::ExecError>
6391 where
6392 F: ::core::ops::FnOnce() -> Self,
6393 {
6394 ::core::result::Result::Ok(
6398 #root::query::QuerySet::<Self>::default()
6399 .first(pool)
6400 .await?
6401 .unwrap_or_else(fallback),
6402 )
6403 }
6404
6405 pub async fn sole(
6415 col: &str,
6416 val: impl ::core::convert::Into<#root::core::SqlValue>,
6417 pool: &#root::sql::Pool,
6418 ) -> ::core::result::Result<Self, #root::sql::ExecError> {
6419 let mut _rows = Self::where_(col, val, pool).await?;
6420 match _rows.len() {
6421 0 => ::core::result::Result::Err(
6422 #root::sql::ExecError::Driver(
6423 #root::sql::sqlx::Error::RowNotFound,
6424 ),
6425 ),
6426 1 => ::core::result::Result::Ok(_rows.remove(0)),
6427 n => ::core::result::Result::Err(
6428 #root::sql::ExecError::MultipleRowsReturned {
6429 op: "sole",
6430 table: <Self as #root::core::Model>::SCHEMA.name,
6431 count: n,
6432 },
6433 ),
6434 }
6435 }
6436 }
6437 } else {
6438 quote!()
6439 };
6440
6441 let tx_emit = |op: TokenStream2| -> TokenStream2 {
6444 if audited_fields.is_some() && primary_key.is_some() {
6445 quote! {
6446 #root::audit::__emit_many_tx(
6447 tx,
6448 &[self.__rustango_audit_entry(#root::audit::AuditOp::#op)],
6449 )
6450 .await?;
6451 }
6452 } else {
6453 quote!()
6454 }
6455 };
6456 let tx_insert_method = {
6457 let self_ref = if fields.has_auto {
6458 quote!(&mut self)
6459 } else {
6460 quote!(&self)
6461 };
6462 let emit = tx_emit(quote!(Create));
6463 let refuse_pkless = if audited_fields.is_some() && primary_key.is_none() {
6464 quote! {
6465 return ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
6466 table: <Self as #root::core::Model>::SCHEMA.table,
6467 });
6468 }
6469 } else {
6470 quote!()
6471 };
6472 quote! {
6473 #[allow(unreachable_code)]
6479 pub async fn insert_tx(
6480 #self_ref,
6481 tx: &mut #root::sql::PoolTx<'_>,
6482 ) -> ::core::result::Result<(), #root::sql::ExecError> {
6483 #refuse_pkless
6484 self.__rustango_insert_exec(&mut *tx).await?;
6485 #emit
6486 ::core::result::Result::Ok(())
6487 }
6488 }
6489 };
6490 let tx_save_method = if let Some((pk_ident, _)) = primary_key {
6491 let emit_create = tx_emit(quote!(Create));
6492 let emit_update = tx_emit(quote!(Update));
6493 let was_unset = if fields.pk_is_auto {
6494 quote!(matches!(self.#pk_ident, #root::sql::Auto::Unset))
6495 } else {
6496 quote!(false)
6497 };
6498 quote! {
6499 pub async fn save_tx(
6505 &mut self,
6506 tx: &mut #root::sql::PoolTx<'_>,
6507 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6508 let _was_unset = #was_unset;
6509 let _n = self.__rustango_save_exec(&mut *tx).await?;
6510 if _n > 0 {
6511 if _was_unset {
6512 #emit_create
6513 } else {
6514 #emit_update
6515 }
6516 }
6517 ::core::result::Result::Ok(_n)
6518 }
6519 }
6520 } else {
6521 quote!()
6522 };
6523 let tx_delete_method = if primary_key.is_some() {
6524 let emit = tx_emit(quote!(Delete));
6525 quote! {
6526 pub async fn delete_tx(
6532 &self,
6533 tx: &mut #root::sql::PoolTx<'_>,
6534 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6535 let _n = self.__rustango_delete_exec(&mut *tx).await?;
6536 if _n > 0 {
6537 #emit
6538 }
6539 ::core::result::Result::Ok(_n)
6540 }
6541 }
6542 } else {
6543 quote!()
6544 };
6545
6546 let refuse = |method: &str| -> TokenStream2 {
6550 let reason = format!("`{method}` cannot audit; use the `&Pool` method");
6551 quote!(#root::sql::__macro_internals::refuse_audited::<Self>(#reason)?;)
6552 };
6553 let refuse_save = refuse("save_on");
6554 let save_method = if let Some((pk_ident, pk_column)) = primary_key {
6555 let pk_column_lit = pk_column.as_str();
6556 let assignments = &fields.update_assignments;
6557 let dispatch_unset = if fields.pk_is_auto {
6558 quote! {
6559 if matches!(self.#pk_ident, #root::sql::Auto::Unset) {
6560 return self.__rustango_insert_exec(_executor).await.map(|()| 1u64);
6561 }
6562 }
6563 } else {
6564 quote!()
6565 };
6566 let write = restore_auto_now_unless(
6567 &fields.auto_now_idents,
6568 &save_ret,
6569 &save_keep,
6570 "e! {
6571 let _query = #root::core::UpdateQuery::new(
6572 <Self as #root::core::Model>::SCHEMA,
6573 ::std::vec![ #( #assignments ),* ],
6574 #root::core::WhereExpr::Predicate(#root::core::Filter::new(
6575 #pk_column_lit,
6576 #root::core::Op::Eq,
6577 ::core::convert::Into::<#root::core::SqlValue>::into(
6578 ::core::clone::Clone::clone(&self.#pk_ident)
6579 ),
6580 )),
6581 );
6582 #root::sql::__macro_internals::update_on(_executor, &_query)
6583 .await
6584 .and_then(#root::sql::__macro_internals::row_saved)
6585 },
6586 );
6587 let upsert = if fields.pk_is_auto {
6588 let upsert_cols = &fields.upsert_update_columns;
6589 let upsert_pushes = &fields.insert_pushes;
6590 let (gen_idents, gen_cols): (Vec<_>, Vec<_>) =
6591 fields.uuid_v7_fields.iter().cloned().unzip();
6592 let upsert_returning = &fields.returning_cols;
6593 let auto_now_idents = &fields.auto_now_idents;
6594 let upsert_target_columns: Vec<String> =
6597 upsert_unique.map_or_else(|| vec![pk_column.clone()], <[String]>::to_vec);
6598 let upsert_target_lits = upsert_target_columns
6599 .iter()
6600 .map(String::as_str)
6601 .collect::<Vec<_>>();
6602 let conflict_clause = if fields.upsert_update_columns.is_empty() {
6603 quote!(#root::core::ConflictClause::DoNothing)
6604 } else {
6605 quote!(#root::core::ConflictClause::DoUpdate {
6606 target: ::std::vec![ #( #upsert_target_lits ),* ],
6607 update_columns: ::std::vec![ #( #upsert_cols ),* ],
6608 })
6609 };
6610 let unit_ret = quote!(::core::result::Result<(), #root::sql::ExecError>);
6611 let unit_keep = quote!(_saved.is_ok());
6612 let exec_body = restore_auto_now_unless(
6613 auto_now_idents,
6614 &unit_ret,
6615 &unit_keep,
6616 "e! {
6617 let _query = self.__rustango_upsert_query();
6618 let _row = #root::sql::__macro_internals::insert_returning_on(
6619 _executor, &_query,
6620 )
6621 .await?;
6622 #root::sql::apply_auto_pk(_row, self)
6623 },
6624 );
6625 let pool_body = if audited_fields.is_some() {
6626 restore_auto_now_unless(
6627 auto_now_idents,
6628 &unit_ret,
6629 &unit_keep,
6630 "e! {
6631 let _query = self.__rustango_upsert_query();
6632 #root::audit::insert_one_with_audit(pool, &_query, self, |_m: &Self, _op| {
6633 _m.__rustango_audit_entry(_op)
6634 })
6635 .await
6636 },
6637 )
6638 } else {
6639 quote!(self.__rustango_upsert_exec(pool).await)
6640 };
6641 let refuse_upsert = refuse("upsert_on");
6642 quote! {
6643 pub async fn upsert(
6651 &mut self,
6652 pool: &#root::sql::Pool,
6653 ) -> ::core::result::Result<(), #root::sql::ExecError> {
6654 #pool_body
6655 }
6656
6657 pub async fn upsert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6663 &mut self,
6664 _executor: _E,
6665 ) -> ::core::result::Result<(), #root::sql::ExecError> {
6666 #refuse_upsert
6667 self.__rustango_upsert_exec(_executor).await
6668 }
6669
6670 #[doc(hidden)]
6671 pub async fn __rustango_upsert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6672 &mut self,
6673 _executor: _E,
6674 ) -> ::core::result::Result<(), #root::sql::ExecError> {
6675 #exec_body
6676 }
6677
6678 #[doc(hidden)]
6680 pub fn __rustango_upsert_query(&mut self) -> #root::core::InsertQuery {
6681 let mut _columns: ::std::vec::Vec<&'static str> = ::std::vec::Vec::new();
6682 let mut _values: ::std::vec::Vec<#root::core::SqlValue> =
6683 ::std::vec::Vec::new();
6684 #[allow(unused_mut)]
6685 let mut _generated: ::std::vec::Vec<&'static str> = ::std::vec::Vec::new();
6686 #(
6687 if matches!(&self.#gen_idents, #root::sql::Auto::Unset) {
6688 _generated.push(#gen_cols);
6689 }
6690 )*
6691 #( self.#auto_now_idents = #root::sql::Auto::Set(#root::__chrono::Utc::now()); )*
6692 #( #upsert_pushes )*
6693 #root::core::InsertQuery::new(
6694 <Self as #root::core::Model>::SCHEMA,
6695 _columns,
6696 _values,
6697 )
6698 .returning(::std::vec![ #( #upsert_returning ),* ])
6699 .on_conflict(#conflict_clause)
6700 .generated(_generated)
6701 }
6702 }
6703 } else {
6704 quote!()
6705 };
6706 Some(quote! {
6707 pub async fn save_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6714 &mut self,
6715 _executor: _E,
6716 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6717 #refuse_save
6718 self.__rustango_save_exec(_executor).await
6719 }
6720
6721 #[doc(hidden)]
6722 pub async fn __rustango_save_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6723 &mut self,
6724 _executor: _E,
6725 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6726 #dispatch_unset
6727 #write
6728 }
6729
6730 #[deprecated(since = "0.62.0", note = "use `save`")]
6735 pub async fn save_pool(
6736 &mut self,
6737 pool: &#root::sql::Pool,
6738 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6739 self.save(pool).await
6740 }
6741
6742 #upsert
6743 })
6744 } else {
6745 None
6746 };
6747
6748 let pk_methods = primary_key.map(|(pk_ident, pk_column)| {
6749 let pk_column_lit = pk_column.as_str();
6750 let refuse_delete = refuse("delete_on");
6751 let soft_delete_methods = if let Some(col) = fields.soft_delete_column.as_deref() {
6758 let col_lit = col;
6759 let sd_field_ident = fields
6760 .soft_delete_field_ident
6761 .clone()
6762 .expect("soft_delete_column without ident");
6763 let refuse_soft_delete = refuse("soft_delete_on");
6764 let refuse_restore = refuse("restore_on");
6765 let mark_deleted = quote! {
6767 if _n > 0 {
6768 self.#sd_field_ident = ::core::option::Option::Some(_now);
6769 }
6770 };
6771 let mark_restored = quote! {
6772 if _n > 0 {
6773 self.#sd_field_ident = ::core::option::Option::None;
6774 }
6775 };
6776 quote! {
6777 pub async fn soft_delete_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6784 &mut self,
6785 _executor: _E,
6786 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6787 #refuse_soft_delete
6788 let _now = #root::__chrono::Utc::now();
6789 let _query = #root::soft_delete::__mark_query(
6790 <Self as #root::core::Model>::SCHEMA,
6791 #col_lit,
6792 #pk_column_lit,
6793 ::core::convert::Into::<#root::core::SqlValue>::into(
6794 ::core::clone::Clone::clone(&self.#pk_ident)
6795 ),
6796 ::core::option::Option::Some(_now),
6797 );
6798 let _n = #root::sql::__macro_internals::update_on(_executor, &_query).await?;
6799 #mark_deleted
6800 ::core::result::Result::Ok(_n)
6801 }
6802
6803 pub async fn restore_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
6809 &mut self,
6810 _executor: _E,
6811 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6812 #refuse_restore
6813 let _query = #root::soft_delete::__mark_query(
6814 <Self as #root::core::Model>::SCHEMA,
6815 #col_lit,
6816 #pk_column_lit,
6817 ::core::convert::Into::<#root::core::SqlValue>::into(
6818 ::core::clone::Clone::clone(&self.#pk_ident)
6819 ),
6820 ::core::option::Option::None,
6821 );
6822 let _n = #root::sql::__macro_internals::update_on(_executor, &_query).await?;
6823 #mark_restored
6824 ::core::result::Result::Ok(_n)
6825 }
6826
6827 pub async fn soft_delete(
6842 &mut self,
6843 pool: &#root::sql::Pool,
6844 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6845 let _now = #root::__chrono::Utc::now();
6846 let _query = #root::soft_delete::__mark_query(
6847 <Self as #root::core::Model>::SCHEMA,
6848 #col_lit,
6849 #pk_column_lit,
6850 ::core::convert::Into::<#root::core::SqlValue>::into(
6851 ::core::clone::Clone::clone(&self.#pk_ident)
6852 ),
6853 ::core::option::Option::Some(_now),
6854 );
6855 let _n = { #pool_softdelete_run }?;
6856 #mark_deleted
6857 ::core::result::Result::Ok(_n)
6858 }
6859
6860 pub async fn restore(
6870 &mut self,
6871 pool: &#root::sql::Pool,
6872 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6873 let _query = #root::soft_delete::__mark_query(
6874 <Self as #root::core::Model>::SCHEMA,
6875 #col_lit,
6876 #pk_column_lit,
6877 ::core::convert::Into::<#root::core::SqlValue>::into(
6878 ::core::clone::Clone::clone(&self.#pk_ident)
6879 ),
6880 ::core::option::Option::None,
6881 );
6882 let _n = { #pool_restore_run }?;
6883 #mark_restored
6884 ::core::result::Result::Ok(_n)
6885 }
6886
6887 pub async fn force_delete(
6901 &self,
6902 pool: &#root::sql::Pool,
6903 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
6904 Self::delete(self, pool).await
6905 }
6906
6907 pub fn trashed(&self) -> bool {
6916 ::core::option::Option::is_some(&self.#sd_field_ident)
6917 }
6918
6919 pub async fn active(
6932 pool: &#root::sql::Pool,
6933 ) -> ::core::result::Result<
6934 ::std::vec::Vec<Self>,
6935 #root::sql::ExecError,
6936 > {
6937 use #root::sql::FetcherPool as _;
6938 #root::query::QuerySet::<Self>::default()
6939 .active()
6940 .fetch(pool)
6941 .await
6942 }
6943
6944 pub async fn only_trashed(
6956 pool: &#root::sql::Pool,
6957 ) -> ::core::result::Result<
6958 ::std::vec::Vec<Self>,
6959 #root::sql::ExecError,
6960 > {
6961 use #root::sql::FetcherPool as _;
6962 #root::query::QuerySet::<Self>::default()
6963 .only_trashed()
6964 .fetch(pool)
6965 .await
6966 }
6967
6968 pub async fn with_trashed(
6984 pool: &#root::sql::Pool,
6985 ) -> ::core::result::Result<
6986 ::std::vec::Vec<Self>,
6987 #root::sql::ExecError,
6988 > {
6989 use #root::sql::FetcherPool as _;
6990 #root::query::QuerySet::<Self>::default()
6991 .with_trashed()
6992 .fetch(pool)
6993 .await
6994 }
6995
6996 }
6997 } else {
6998 quote!()
6999 };
7000 quote! {
7001
7002
7003 pub async fn delete_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7009 &self,
7010 _executor: _E,
7011 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
7012 #refuse_delete
7013 self.__rustango_delete_exec(_executor).await
7014 }
7015
7016 #[doc(hidden)]
7017 pub async fn __rustango_delete_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7018 &self,
7019 _executor: _E,
7020 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
7021 let _query = #root::core::DeleteQuery::by_pk(
7022 <Self as #root::core::Model>::SCHEMA,
7023 #pk_column_lit,
7024 ::core::convert::Into::<#root::core::SqlValue>::into(
7025 ::core::clone::Clone::clone(&self.#pk_ident)
7026 ),
7027 );
7028 #root::sql::__macro_internals::delete_on(_executor, &_query).await
7029 }
7030
7031 #[deprecated(since = "0.62.0", note = "use `delete`")]
7036 pub async fn delete_pool(
7037 &self,
7038 pool: &#root::sql::Pool,
7039 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
7040 self.delete(pool).await
7041 }
7042
7043
7044 #pool_delete_method
7045 #pool_insert_method
7046 #audit_entry_method
7047 #pool_save_method
7048 #refresh_replicate_methods
7049 #tx_delete_method
7050 #tx_insert_method
7051 #tx_save_method
7052 #soft_delete_methods
7053
7054 pub fn is(&self, other: &Self) -> bool {
7063 self.#pk_ident == other.#pk_ident
7064 }
7065
7066 pub fn is_not(&self, other: &Self) -> bool {
7069 self.#pk_ident != other.#pk_ident
7070 }
7071
7072 #[must_use]
7080 pub fn get_key(&self) -> #root::core::SqlValue {
7081 ::core::convert::Into::into(::core::clone::Clone::clone(&self.#pk_ident))
7082 }
7083
7084 }
7085 });
7086
7087 let refuse_insert = refuse("insert_on");
7088 let audited_insert = audited_fields.is_some() && primary_key.is_some();
7091 let insert_pool_body = if audited_fields.is_some() {
7092 quote! {
7093 ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
7094 table: <Self as #root::core::Model>::SCHEMA.table,
7095 })
7096 }
7097 } else {
7098 quote!(self.__rustango_insert_exec(pool).await)
7099 };
7100 let plain_insert_auto = if audited_insert {
7101 quote!()
7102 } else {
7103 quote! {
7104 pub async fn insert(
7110 &mut self,
7111 pool: &#root::sql::Pool,
7112 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7113 #insert_pool_body
7114 }
7115 }
7116 };
7117 let plain_insert_plain = if audited_insert {
7118 quote!()
7119 } else {
7120 quote! {
7121 pub async fn insert(
7126 &self,
7127 pool: &#root::sql::Pool,
7128 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7129 #insert_pool_body
7130 }
7131 }
7132 };
7133 let insert_method = if fields.has_auto {
7134 let pushes = &fields.insert_pushes;
7135 let returning_cols = &fields.returning_cols;
7136 quote! {
7137 #plain_insert_auto
7138 pub async fn insert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7144 &mut self,
7145 _executor: _E,
7146 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7147 #refuse_insert
7148 self.__rustango_insert_exec(_executor).await
7149 }
7150
7151 #[doc(hidden)]
7152 pub async fn __rustango_insert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7153 &mut self,
7154 _executor: _E,
7155 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7156 let _query = self.__rustango_insert_query();
7157 if _query.returning.is_empty() {
7159 return #root::sql::__macro_internals::insert_on(_executor, &_query).await;
7160 }
7161 let _row = #root::sql::__macro_internals::insert_returning_on(
7162 _executor, &_query,
7163 )
7164 .await?;
7165 #root::sql::apply_auto_pk(_row, self)
7166 }
7167
7168 #[doc(hidden)]
7170 pub fn __rustango_insert_query(&mut self) -> #root::core::InsertQuery {
7171 let mut _columns: ::std::vec::Vec<&'static str> = ::std::vec::Vec::new();
7172 let mut _values: ::std::vec::Vec<#root::core::SqlValue> = ::std::vec::Vec::new();
7173 #( #pushes )*
7174 #root::core::InsertQuery::new(
7175 <Self as #root::core::Model>::SCHEMA,
7176 _columns,
7177 _values,
7178 )
7179 .returning(::std::vec![ #( #returning_cols ),* ])
7180 }
7181
7182 #[deprecated(since = "0.62.0", note = "use `insert`")]
7187 pub async fn insert_pool(
7188 &mut self,
7189 pool: &#root::sql::Pool,
7190 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7191 self.insert(pool).await
7192 }
7193 }
7194 } else {
7195 let insert_columns = &fields.insert_columns;
7196 let insert_values = &fields.insert_values;
7197 let pool_self = if audited_fields.is_some() {
7199 quote!(&mut self)
7200 } else {
7201 quote!(&self)
7202 };
7203 quote! {
7204 #plain_insert_plain
7205 pub async fn insert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7211 &self,
7212 _executor: _E,
7213 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7214 #refuse_insert
7215 self.__rustango_insert_exec(_executor).await
7216 }
7217
7218 #[doc(hidden)]
7219 pub async fn __rustango_insert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7220 &self,
7221 _executor: _E,
7222 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7223 #root::sql::__macro_internals::insert_on(_executor, &self.__rustango_insert_query())
7224 .await
7225 }
7226
7227 #[doc(hidden)]
7229 pub fn __rustango_insert_query(&self) -> #root::core::InsertQuery {
7230 #root::core::InsertQuery::new(
7231 <Self as #root::core::Model>::SCHEMA,
7232 ::std::vec![ #( #insert_columns ),* ],
7233 ::std::vec![ #( #insert_values ),* ],
7234 )
7235 }
7236
7237 #[deprecated(since = "0.62.0", note = "use `insert`")]
7242 pub async fn insert_pool(
7243 #pool_self,
7244 pool: &#root::sql::Pool,
7245 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7246 self.insert(pool).await
7247 }
7248 }
7249 };
7250
7251 let bulk_pool_body = if audited_fields.is_some() {
7254 quote! {
7255 let mut _tx = #root::sql::__macro_internals::write_tx(pool).await?;
7256 Self::__rustango_bulk_insert_exec(rows, &mut *_tx).await?;
7257 if rows.iter().any(|_row| _row.__rustango_audit_pk().is_empty()) {
7259 return ::core::result::Result::Err(#root::sql::ExecError::AuditUnsupported {
7260 table: <Self as #root::core::Model>::SCHEMA.table,
7261 reason: "MySQL has no RETURNING to read the written rows",
7262 });
7263 }
7264 let _entries: ::std::vec::Vec<#root::audit::PendingEntry> = rows
7265 .iter()
7266 .map(|_row| _row.__rustango_audit_entry(#root::audit::AuditOp::Create))
7267 .collect();
7268 #root::audit::__emit_many_tx(&mut _tx, &_entries).await?;
7269 _tx.commit().await?;
7270 ::core::result::Result::Ok(())
7271 }
7272 } else {
7273 quote!(Self::__rustango_bulk_insert_exec(rows, pool).await)
7274 };
7275 let refuse_bulk = refuse("bulk_insert_on");
7276 let bulk_insert_method = if fields.has_auto {
7277 let cols_no_auto = &fields.bulk_columns_no_auto;
7278 let cols_all = &fields.bulk_columns_all;
7279 let pushes_no_auto = &fields.bulk_pushes_no_auto;
7280 let pushes_all = &fields.bulk_pushes_all;
7281 let returning_cols = &fields.returning_cols;
7282 let uniformity = &fields.bulk_auto_uniformity;
7283 let fill_in_place = &fields.bulk_fill_in_place;
7284 let first_auto_ident = fields
7286 .first_db_auto
7287 .as_ref()
7288 .map(|(ident, _)| ident)
7289 .or(fields.first_auto_ident.as_ref())
7290 .expect("has_auto implies first_auto_ident is Some");
7291 quote! {
7292 pub async fn bulk_insert(
7302 rows: &mut [Self],
7303 pool: &#root::sql::Pool,
7304 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7305 #bulk_pool_body
7306 }
7307
7308 pub async fn bulk_insert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7314 rows: &mut [Self],
7315 _executor: _E,
7316 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7317 #refuse_bulk
7318 Self::__rustango_bulk_insert_exec(rows, _executor).await
7319 }
7320
7321 #[doc(hidden)]
7322 pub async fn __rustango_bulk_insert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7323 rows: &mut [Self],
7324 _executor: _E,
7325 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7326 if rows.is_empty() {
7327 return ::core::result::Result::Ok(());
7328 }
7329 for _row in rows.iter_mut() {
7330 #( #fill_in_place )*
7331 }
7332 let (_columns, _all_rows) = Self::__rustango_bulk_rows(rows)?;
7333 let _query = #root::core::BulkInsertQuery::new(
7334 <Self as #root::core::Model>::SCHEMA,
7335 _columns,
7336 _all_rows,
7337 )
7338 .returning(::std::vec![ #( #returning_cols ),* ]);
7339 let _returned =
7340 #root::sql::__macro_internals::bulk_insert_on(_executor, &_query).await?;
7341 if _returned.is_empty() {
7342 return ::core::result::Result::Ok(());
7343 }
7344 if _returned.len() != rows.len() {
7345 return ::core::result::Result::Err(#root::sql::ExecError::Sql(
7346 #root::sql::SqlError::BulkInsertReturningMismatch {
7347 expected: rows.len(),
7348 actual: _returned.len(),
7349 },
7350 ));
7351 }
7352 for (_row, _row_mut) in _returned.into_iter().zip(rows.iter_mut()) {
7353 #root::sql::apply_auto_pk(_row, _row_mut)?;
7354 }
7355 ::core::result::Result::Ok(())
7356 }
7357
7358 #[doc(hidden)]
7362 pub fn __rustango_bulk_rows(
7363 rows: &[Self],
7364 ) -> ::core::result::Result<
7365 (
7366 ::std::vec::Vec<&'static str>,
7367 ::std::vec::Vec<::std::vec::Vec<#root::core::SqlValue>>,
7368 ),
7369 #root::sql::ExecError,
7370 > {
7371 let _first_unset = match rows.first() {
7372 ::core::option::Option::Some(_r) => {
7373 matches!(_r.#first_auto_ident, #root::sql::Auto::Unset)
7374 }
7375 ::core::option::Option::None => true,
7376 };
7377 #( #uniformity )*
7378 let mut _all_rows: ::std::vec::Vec<
7379 ::std::vec::Vec<#root::core::SqlValue>,
7380 > = ::std::vec::Vec::with_capacity(rows.len());
7381 let _columns: ::std::vec::Vec<&'static str> = if _first_unset {
7382 for _row in rows.iter() {
7383 let mut _row_vals: ::std::vec::Vec<#root::core::SqlValue> =
7384 ::std::vec::Vec::new();
7385 #( #pushes_no_auto )*
7386 _all_rows.push(_row_vals);
7387 }
7388 ::std::vec![ #( #cols_no_auto ),* ]
7389 } else {
7390 for _row in rows.iter() {
7391 let mut _row_vals: ::std::vec::Vec<#root::core::SqlValue> =
7392 ::std::vec::Vec::new();
7393 #( #pushes_all )*
7394 _all_rows.push(_row_vals);
7395 }
7396 ::std::vec![ #( #cols_all ),* ]
7397 };
7398 ::core::result::Result::Ok((_columns, _all_rows))
7399 }
7400 }
7401 } else {
7402 let cols_all = &fields.bulk_columns_all;
7403 let pushes_all = &fields.bulk_pushes_all;
7404 quote! {
7405 pub async fn bulk_insert(
7412 rows: &[Self],
7413 pool: &#root::sql::Pool,
7414 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7415 #bulk_pool_body
7416 }
7417
7418 pub async fn bulk_insert_on<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7424 rows: &[Self],
7425 _executor: _E,
7426 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7427 #refuse_bulk
7428 Self::__rustango_bulk_insert_exec(rows, _executor).await
7429 }
7430
7431 #[doc(hidden)]
7432 pub async fn __rustango_bulk_insert_exec<'_c, _E: #root::sql::SqlExecutor<'_c>>(
7433 rows: &[Self],
7434 _executor: _E,
7435 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7436 if rows.is_empty() {
7437 return ::core::result::Result::Ok(());
7438 }
7439 let (_columns, _all_rows) = Self::__rustango_bulk_rows(rows)?;
7440 let _query = #root::core::BulkInsertQuery::new(
7441 <Self as #root::core::Model>::SCHEMA,
7442 _columns,
7443 _all_rows,
7444 );
7445 #root::sql::__macro_internals::bulk_insert_on(_executor, &_query)
7446 .await
7447 .map(drop)
7448 }
7449
7450 #[doc(hidden)]
7452 pub fn __rustango_bulk_rows(
7453 rows: &[Self],
7454 ) -> ::core::result::Result<
7455 (
7456 ::std::vec::Vec<&'static str>,
7457 ::std::vec::Vec<::std::vec::Vec<#root::core::SqlValue>>,
7458 ),
7459 #root::sql::ExecError,
7460 > {
7461 let mut _all_rows: ::std::vec::Vec<
7462 ::std::vec::Vec<#root::core::SqlValue>,
7463 > = ::std::vec::Vec::with_capacity(rows.len());
7464 for _row in rows.iter() {
7465 let mut _row_vals: ::std::vec::Vec<#root::core::SqlValue> =
7466 ::std::vec::Vec::new();
7467 #( #pushes_all )*
7468 _all_rows.push(_row_vals);
7469 }
7470 ::core::result::Result::Ok((::std::vec![ #( #cols_all ),* ], _all_rows))
7471 }
7472 }
7473 };
7474
7475 let run_conflict_bulk_insert = match (audited_fields.is_some(), primary_key.is_some()) {
7482 (false, _) => quote!(#root::sql::bulk_insert_pool(pool, &_query).await),
7483 (true, true) => quote! {
7484 #root::audit::bulk_insert_with_audit::<Self>(pool, &_query, |_r: &Self, _op| {
7485 _r.__rustango_audit_entry(_op)
7486 })
7487 .await
7488 },
7489 (true, false) => quote! {
7490 ::core::result::Result::Err(#root::sql::ExecError::MissingPrimaryKey {
7491 table: <Self as #root::core::Model>::SCHEMA.table,
7492 })
7493 },
7494 };
7495 let bulk_upsert_pool_method = {
7496 let (gen_idents, gen_cols): (Vec<_>, Vec<_>) =
7497 fields.uuid_v7_fields.iter().cloned().unzip();
7498 quote! {
7499 pub async fn bulk_upsert_pool(
7528 rows: &[Self],
7529 target: &[&'static str],
7530 update_cols: &[&'static str],
7531 pool: &#root::sql::Pool,
7532 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7533 if rows.is_empty() {
7534 return ::core::result::Result::Ok(());
7535 }
7536 let (_columns, _all_rows) = Self::__rustango_bulk_rows(rows)?;
7537 #[allow(unused_mut)]
7538 let mut _generated: ::std::vec::Vec<&'static str> = ::std::vec::Vec::new();
7539 #(
7540 if rows.iter().all(|_r| matches!(&_r.#gen_idents, #root::sql::Auto::Unset)) {
7541 _generated.push(#gen_cols);
7542 }
7543 )*
7544 let _query = #root::core::BulkInsertQuery::new(
7545 <Self as #root::core::Model>::SCHEMA,
7546 _columns,
7547 _all_rows,
7548 )
7549 .on_conflict_do_update(target, update_cols)
7550 .generated(_generated);
7551 #run_conflict_bulk_insert
7552 }
7553
7554 pub async fn bulk_insert_or_ignore_pool(
7564 rows: &[Self],
7565 pool: &#root::sql::Pool,
7566 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7567 if rows.is_empty() {
7568 return ::core::result::Result::Ok(());
7569 }
7570 let (_columns, _all_rows) = Self::__rustango_bulk_rows(rows)?;
7571 let _query = #root::core::BulkInsertQuery::new(
7572 <Self as #root::core::Model>::SCHEMA,
7573 _columns,
7574 _all_rows,
7575 )
7576 .on_conflict_do_nothing();
7577 #run_conflict_bulk_insert
7578 }
7579 }
7580 };
7581
7582 let bulk_update_rows_run = if audited_fields.is_some() {
7591 quote!(#root::audit::bulk_update_with_audit::<Self>(pool, &_query, |_r: &Self| {
7592 _r.__rustango_audit_entry(#root::audit::AuditOp::Update)
7593 })
7594 .await)
7595 } else {
7596 quote!(#root::sql::bulk_update_pool(pool, &_query).await)
7597 };
7598 let bulk_update_method = match &fields.primary_key {
7599 None => quote! {},
7600 Some((pk_ident, pk_col)) => {
7601 let mut col_arms: Vec<TokenStream2> = Vec::new();
7605 let mut val_arms: Vec<TokenStream2> = Vec::new();
7606 for entry in &fields.column_entries {
7607 if &entry.column == pk_col {
7608 continue;
7609 }
7610 let col = &entry.column;
7611 let ident = &entry.ident;
7612 col_arms.push(quote! { #col => #col, });
7613 val_arms.push(quote! {
7614 #col => _row_vals.push(
7615 ::core::convert::Into::<#root::core::SqlValue>::into(
7616 ::core::clone::Clone::clone(&_o.#ident)
7617 )
7618 ),
7619 });
7620 }
7621 quote! {
7622 pub async fn bulk_update(
7648 objs: &[Self],
7649 fields: &[&str],
7650 pool: &#root::sql::Pool,
7651 ) -> ::core::result::Result<u64, #root::sql::ExecError> {
7652 if objs.is_empty() || fields.is_empty() {
7653 return ::core::result::Result::Ok(0);
7654 }
7655 let _model_name = <Self as #root::core::Model>::SCHEMA.name;
7656 let mut _update_columns: ::std::vec::Vec<&'static str> =
7657 ::std::vec::Vec::with_capacity(fields.len());
7658 for &_f in fields {
7659 let _col: &'static str = match _f {
7660 #pk_col => {
7661 return ::core::result::Result::Err(
7662 ::core::convert::Into::into(
7663 #root::core::QueryError::BulkUpdatePrimaryKey {
7664 model: _model_name,
7665 field: ::std::string::ToString::to_string(_f),
7666 }
7667 )
7668 );
7669 }
7670 #( #col_arms )*
7671 _ => {
7672 return ::core::result::Result::Err(
7673 ::core::convert::Into::into(
7674 #root::core::QueryError::UnknownField {
7675 model: _model_name,
7676 field: ::std::string::ToString::to_string(_f),
7677 }
7678 )
7679 );
7680 }
7681 };
7682 #[allow(unreachable_code)]
7691 _update_columns.push(_col);
7692 }
7693 let mut _rows: ::std::vec::Vec<
7694 ::std::vec::Vec<#root::core::SqlValue>,
7695 > = ::std::vec::Vec::with_capacity(objs.len());
7696 for _o in objs.iter() {
7697 let mut _row_vals: ::std::vec::Vec<#root::core::SqlValue> =
7698 ::std::vec::Vec::with_capacity(fields.len() + 1);
7699 _row_vals.push(
7701 ::core::convert::Into::<#root::core::SqlValue>::into(
7702 ::core::clone::Clone::clone(&_o.#pk_ident)
7703 )
7704 );
7705 for &_f in fields {
7706 match _f {
7707 #( #val_arms )*
7708 _ => {}
7711 }
7712 }
7713 _rows.push(_row_vals);
7714 }
7715 let _query = #root::core::BulkUpdateQuery::new(
7716 <Self as #root::core::Model>::SCHEMA,
7717 _update_columns,
7718 _rows,
7719 );
7720 #bulk_update_rows_run
7721 }
7722 }
7723 }
7724 };
7725
7726 let pk_value_helper = primary_key.map(|(pk_ident, _)| {
7727 quote! {
7728 #[doc(hidden)]
7733 pub fn __rustango_pk_value(&self) -> #root::core::SqlValue {
7734 ::core::convert::Into::<#root::core::SqlValue>::into(
7735 ::core::clone::Clone::clone(&self.#pk_ident)
7736 )
7737 }
7738 }
7739 });
7740
7741 let has_pk_value_impl = primary_key.map(|(pk_ident, _)| {
7742 let column_arms = fields.column_entries.iter().map(|entry| {
7743 let col = &entry.column;
7744 let ident = &entry.ident;
7745 quote! {
7746 #col => ::core::option::Option::Some(
7747 ::core::convert::Into::<#root::core::SqlValue>::into(
7748 ::core::clone::Clone::clone(&self.#ident)
7749 )
7750 ),
7751 }
7752 });
7753 quote! {
7754 impl #root::sql::HasPkValue for #struct_name {
7755 fn __rustango_pk_value_impl(&self) -> #root::core::SqlValue {
7756 ::core::convert::Into::<#root::core::SqlValue>::into(
7757 ::core::clone::Clone::clone(&self.#pk_ident)
7758 )
7759 }
7760 fn __rustango_column_value(
7761 &self,
7762 column: &str,
7763 ) -> ::core::option::Option<#root::core::SqlValue> {
7764 match column {
7765 #( #column_arms )*
7766 _ => ::core::option::Option::None,
7767 }
7768 }
7769 }
7770 }
7771 });
7772
7773 let fk_pk_access_impl = fk_pk_access_impl_tokens(struct_name, &fields.fk_relations);
7774
7775 let assign_auto_pk_pool_impl = {
7781 let auto_assigns = &fields.auto_assigns;
7782 let auto_assigns_sqlite: Vec<TokenStream2> = fields
7788 .auto_field_idents
7789 .iter()
7790 .map(|(ident, column, uuid)| {
7791 if *uuid {
7793 return quote! {
7794 self.#ident = #root::sql::Auto::Set(#root::sql::try_get_flat_sqlite(
7795 _returning_row, #column
7796 )?);
7797 };
7798 }
7799 quote! {
7800 self.#ident = #root::sql::try_get_returning_sqlite(
7801 _returning_row, #column
7802 )?;
7803 }
7804 })
7805 .collect();
7806 let generated_assigns: Vec<TokenStream2> = fields
7810 .generated_field_idents
7811 .iter()
7812 .map(|(ident, column)| {
7813 quote! {
7814 self.#ident = #root::sql::try_get_returning(_returning_row, #column)?;
7815 }
7816 })
7817 .collect();
7818 let generated_assigns_sqlite: Vec<TokenStream2> = fields
7819 .generated_field_idents
7820 .iter()
7821 .map(|(ident, column)| {
7822 quote! {
7823 self.#ident = #root::sql::try_get_returning_sqlite(
7824 _returning_row, #column
7825 )?;
7826 }
7827 })
7828 .collect();
7829 let mysql_body = if let Some((first, value_ty)) = fields.first_db_auto.as_ref() {
7830 quote! {
7848 let _converted = <#value_ty as #root::sql::MysqlAutoIdSet>
7849 ::rustango_from_mysql_auto_id(_id)?;
7850 self.#first = #root::sql::Auto::Set(_converted);
7851 ::core::result::Result::Ok(())
7852 }
7853 } else {
7854 quote! {
7855 let _ = _id;
7856 ::core::result::Result::Ok(())
7857 }
7858 };
7859 quote! {
7860 impl #root::sql::AssignAutoPkPool for #struct_name {
7861 fn __rustango_assign_from_pg_row(
7862 &mut self,
7863 _returning_row: &#root::sql::PgReturningRow,
7864 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7865 #( #auto_assigns )*
7866 #( #generated_assigns )*
7867 ::core::result::Result::Ok(())
7868 }
7869 fn __rustango_assign_from_mysql_id(
7870 &mut self,
7871 _id: i64,
7872 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7873 #mysql_body
7874 }
7875 fn __rustango_assign_from_sqlite_row(
7876 &mut self,
7877 _returning_row: &#root::sql::SqliteReturningRow,
7878 ) -> ::core::result::Result<(), #root::sql::ExecError> {
7879 #( #auto_assigns_sqlite )*
7880 #( #generated_assigns_sqlite )*
7881 ::core::result::Result::Ok(())
7882 }
7883 }
7884 }
7885 };
7886
7887 let from_aliased_row_inits = &fields.from_aliased_row_inits;
7888 let aliased_row_helper = quote! {
7889 #[doc(hidden)]
7895 #[cfg(feature = "postgres")]
7896 pub fn __rustango_from_aliased_row(
7897 row: &#root::sql::sqlx::postgres::PgRow,
7898 prefix: &str,
7899 ) -> ::core::result::Result<Self, #root::sql::sqlx::Error> {
7900 ::core::result::Result::Ok(Self {
7901 #( #from_aliased_row_inits ),*
7902 })
7903 }
7904 };
7905 let from_aliased_row_inits_my = &fields.from_aliased_row_inits_my;
7908 let aliased_row_helper_my = quote! {
7909 #root::__impl_my_aliased_row_decoder!(#struct_name, |row, prefix| {
7910 #( #from_aliased_row_inits_my ),*
7911 });
7912 };
7913
7914 let aliased_row_helper_sqlite = quote! {
7917 #root::__impl_sqlite_aliased_row_decoder!(#struct_name, |row, prefix| {
7918 #( #from_aliased_row_inits ),*
7919 });
7920 };
7921
7922 let load_related_impl = load_related_impl_tokens(struct_name, &fields.fk_relations);
7923 let load_related_impl_my = load_related_impl_my_tokens(struct_name, &fields.fk_relations);
7924 let load_related_impl_sqlite =
7925 load_related_impl_sqlite_tokens(struct_name, &fields.fk_relations);
7926
7927 let extra_manager_fns: Vec<TokenStream2> = manager_fns
7933 .iter()
7934 .map(|fn_ident| {
7935 let model_name_str = struct_name.to_string();
7936 let fn_name_str = fn_ident.to_string();
7937 let doc = format!(
7938 "Custom-named QuerySet accessor for [`{model_name_str}`]. \
7939 Generated by `#[rustango(manager_fn = \"{fn_name_str}\")]` — \
7940 equivalent to `Self::objects()`. Chains with any \
7941 `impl ... for QuerySet<{model_name_str}> {{ ... }}` \
7942 extension methods."
7943 );
7944 quote! {
7945 #[doc = #doc]
7946 #[must_use]
7947 pub fn #fn_ident() -> #root::query::QuerySet<#struct_name> {
7948 #root::query::QuerySet::new()
7949 }
7950 }
7951 })
7952 .collect();
7953
7954 quote! {
7955 impl #struct_name {
7956 #[must_use]
7958 pub fn objects() -> #root::query::QuerySet<#struct_name> {
7959 #root::query::QuerySet::new()
7960 }
7961
7962 #[must_use]
7975 pub fn query() -> #root::query::QuerySet<#struct_name> {
7976 #root::query::QuerySet::new()
7977 }
7978
7979 #( #extra_manager_fns )*
7980
7981 #insert_method
7982
7983 #bulk_insert_method
7984
7985 #bulk_upsert_pool_method
7986
7987 #bulk_update_method
7988
7989 #save_method
7990
7991 #pk_methods
7992
7993 #pk_value_helper
7994
7995 #aliased_row_helper
7996
7997 #column_consts
7998 }
7999
8000 #aliased_row_helper_my
8001
8002 #aliased_row_helper_sqlite
8003
8004 #load_related_impl
8005
8006 #load_related_impl_my
8007
8008 #load_related_impl_sqlite
8009
8010 #has_pk_value_impl
8011
8012 #fk_pk_access_impl
8013
8014 #assign_auto_pk_pool_impl
8015 }
8016}
8017
8018fn column_const_tokens(module_ident: &syn::Ident, entries: &[ColumnEntry]) -> TokenStream2 {
8020 let lines = entries.iter().map(|e| {
8021 let ident = &e.ident;
8022 let col_ty = column_type_ident(ident);
8023 quote! {
8024 #[allow(non_upper_case_globals)]
8025 pub const #ident: #module_ident::#col_ty = #module_ident::#col_ty;
8026 }
8027 });
8028 quote! { #(#lines)* }
8029}
8030
8031fn column_module_tokens(
8034 module_ident: &syn::Ident,
8035 struct_name: &syn::Ident,
8036 entries: &[ColumnEntry],
8037) -> TokenStream2 {
8038 let root = rustango_root();
8039 let items = entries.iter().map(|e| {
8040 let col_ty = column_type_ident(&e.ident);
8041 let value_ty = &e.value_ty;
8042 let name = &e.name;
8043 let column = &e.column;
8044 let field_type_tokens = &e.field_type_tokens;
8045 quote! {
8046 #[derive(::core::clone::Clone, ::core::marker::Copy)]
8047 pub struct #col_ty;
8048
8049 impl #root::core::Column for #col_ty {
8050 type Model = super::#struct_name;
8051 type Value = #value_ty;
8052 const NAME: &'static str = #name;
8053 const COLUMN: &'static str = #column;
8054 const FIELD_TYPE: #root::core::FieldType = #field_type_tokens;
8055 }
8056 }
8057 });
8058 quote! {
8059 #[doc(hidden)]
8060 #[allow(non_camel_case_types, non_snake_case)]
8061 pub mod #module_ident {
8062 #[allow(unused_imports)]
8067 use super::*;
8068 #(#items)*
8069 }
8070 }
8071}
8072
8073fn column_type_ident(field_ident: &syn::Ident) -> syn::Ident {
8074 let name = ident_name(field_ident);
8075 syn::Ident::new(&format!("{name}_col"), field_ident.span())
8076}
8077
8078fn restore_auto_now_unless(
8081 idents: &[syn::Ident],
8082 ret: &TokenStream2,
8083 keep: &TokenStream2,
8084 body: &TokenStream2,
8085) -> TokenStream2 {
8086 if idents.is_empty() {
8087 return body.clone();
8088 }
8089 let prev: Vec<syn::Ident> = (0..idents.len())
8090 .map(|i| quote::format_ident!("__rustango_auto_now_prev_{}", i))
8091 .collect();
8092 quote! {
8093 #( let #prev = ::core::clone::Clone::clone(&self.#idents); )*
8094 let _saved: #ret = async { #body }.await;
8095 if !(#keep) {
8096 #( self.#idents = #prev; )*
8097 }
8098 _saved
8099 }
8100}
8101
8102fn ident_name(ident: &syn::Ident) -> String {
8104 syn::ext::IdentExt::unraw(ident).to_string()
8105}
8106
8107fn ident_from_name(name: &str, span: proc_macro2::Span) -> syn::Ident {
8109 match syn::parse_str::<syn::Ident>(name) {
8110 Ok(mut ident) => {
8111 ident.set_span(span);
8112 ident
8113 }
8114 Err(_) => syn::Ident::new_raw(name, span),
8115 }
8116}
8117
8118fn column_module_ident(struct_name: &syn::Ident) -> syn::Ident {
8119 syn::Ident::new(
8120 &format!("__rustango_cols_{struct_name}"),
8121 struct_name.span(),
8122 )
8123}
8124
8125fn from_row_impl_tokens(
8126 struct_name: &syn::Ident,
8127 from_row_inits: &[TokenStream2],
8128 from_row_inits_my: &[TokenStream2],
8129) -> TokenStream2 {
8130 let root = rustango_root();
8131 quote! {
8142 #[cfg(feature = "postgres")]
8143 impl<'r> #root::sql::sqlx::FromRow<'r, #root::sql::sqlx::postgres::PgRow>
8144 for #struct_name
8145 {
8146 fn from_row(
8147 row: &'r #root::sql::sqlx::postgres::PgRow,
8148 ) -> ::core::result::Result<Self, #root::sql::sqlx::Error> {
8149 ::core::result::Result::Ok(Self {
8150 #( #from_row_inits ),*
8151 })
8152 }
8153 }
8154
8155 #root::__impl_my_from_row!(#struct_name, |row| {
8156 #( #from_row_inits_my ),*
8157 });
8158
8159 #root::__impl_sqlite_from_row!(#struct_name, |row| {
8160 #( #from_row_inits ),*
8161 });
8162 }
8163}
8164
8165struct ContainerAttrs {
8166 table: Option<String>,
8167 display: Option<(String, proc_macro2::Span)>,
8168 app: Option<String>,
8175 admin: Option<AdminAttrs>,
8180 audit: Option<AuditAttrs>,
8186 permissions: bool,
8190 m2m: Vec<M2MAttr>,
8194 generic_m2m: Vec<GenericM2MAttr>,
8199 indexes: Vec<IndexAttr>,
8205 checks: Vec<CheckAttr>,
8208 excludes: Vec<ExcludeAttr>,
8214 composite_fks: Vec<CompositeFkAttr>,
8218 generic_fks: Vec<GenericFkAttr>,
8222 scope: Option<String>,
8228 manager_fns: Vec<syn::Ident>,
8233 default_order: Vec<(String, bool, proc_macro2::Span)>,
8239 is_view: bool,
8244 managed: bool,
8250 base_manager_name: Option<String>,
8254 order_with_respect_to: Option<String>,
8259 proxy: bool,
8265 required_db_features: Vec<String>,
8270 required_db_vendor: Option<String>,
8276 default_related_name: Option<String>,
8282 db_table_comment: Option<String>,
8287 get_latest_by: Option<(String, bool, proc_macro2::Span)>,
8293 extra_permissions: Vec<(String, String)>,
8298 default_permissions: Vec<String>,
8305 verbose_name: Option<String>,
8311 verbose_name_plural: Option<String>,
8315 global_scopes: Vec<GlobalScopeAttr>,
8321 through_relations: Vec<ThroughAttr>,
8330 reverse_has_relations: Vec<ReverseHasAttr>,
8339 generic_has_relations: Vec<GenericHasAttr>,
8344}
8345
8346struct GlobalScopeAttr {
8352 name: String,
8353 apply: syn::Path,
8354}
8355
8356struct ThroughAttr {
8386 name: String,
8388 far: syn::Ident,
8392 far_fk_column: String,
8398 intermediate: syn::Ident,
8402 intermediate_fk_column: String,
8406 intermediate_pk_column: String,
8411}
8412
8413struct ReverseHasAttr {
8452 name: String,
8455 child: syn::Ident,
8459 child_fk_column: String,
8463 self_pk_column: Option<String>,
8466 self_pk_span: Option<proc_macro2::Span>,
8468}
8469
8470impl ReverseHasAttr {
8471 fn self_pk_column(&self) -> &str {
8472 self.self_pk_column.as_deref().unwrap_or("id")
8473 }
8474}
8475
8476struct GenericHasAttr {
8482 name: String,
8484 child: syn::Ident,
8487 ct_column: String,
8490 pk_column: String,
8493 self_pk_column: Option<String>,
8496 self_pk_span: Option<proc_macro2::Span>,
8498}
8499
8500impl GenericHasAttr {
8501 fn self_pk_column(&self) -> &str {
8502 self.self_pk_column.as_deref().unwrap_or("id")
8503 }
8504}
8505
8506struct IndexAttr {
8508 span: proc_macro2::Span,
8510 name: Option<String>,
8512 columns: Vec<String>,
8514 unique: bool,
8516 method: String,
8522 where_clause: Option<String>,
8526 include: Vec<String>,
8529}
8530
8531struct CheckAttr {
8533 name: String,
8534 expr: String,
8535}
8536
8537struct ExcludeAttr {
8542 name: String,
8544 using: String,
8546 elements: Vec<(String, String)>,
8549 where_clause: Option<String>,
8551}
8552
8553struct CompositeFkAttr {
8559 name: String,
8561 to: String,
8563 from: Vec<String>,
8565 on: Vec<String>,
8567}
8568
8569struct GenericFkAttr {
8574 name: String,
8576 ct_column: String,
8578 pk_column: String,
8580}
8581
8582struct M2MAttr {
8584 name: String,
8586 to: String,
8588 through: String,
8590 src: String,
8592 dst: String,
8594 auto_create: bool,
8599}
8600
8601struct GenericM2MAttr {
8606 name: String,
8608 through: String,
8610 pk_column: String,
8612 ct_column: String,
8615 related_column: String,
8617}
8618
8619#[derive(Default)]
8625struct AuditAttrs {
8626 track: Option<(Vec<String>, proc_macro2::Span)>,
8630}
8631
8632#[derive(Default)]
8637struct AdminAttrs {
8638 list_display: Option<(Vec<String>, proc_macro2::Span)>,
8639 search_fields: Option<(Vec<String>, proc_macro2::Span)>,
8640 list_per_page: Option<usize>,
8641 ordering: Option<(Vec<(String, bool)>, proc_macro2::Span)>,
8642 readonly_fields: Option<(Vec<String>, proc_macro2::Span)>,
8643 list_filter: Option<(Vec<String>, proc_macro2::Span)>,
8644 actions: Option<(Vec<String>, proc_macro2::Span)>,
8647 fieldsets: Option<(Vec<(String, Vec<String>)>, proc_macro2::Span)>,
8651 list_display_links: Option<(Vec<String>, proc_macro2::Span)>,
8655 search_help_text: Option<String>,
8659 actions_on_top: Option<bool>,
8662 actions_on_bottom: Option<bool>,
8666 date_hierarchy: Option<String>,
8671 prepopulated_fields: Option<(Vec<(String, Vec<String>)>, proc_macro2::Span)>,
8677 raw_id_fields: Option<(Vec<String>, proc_macro2::Span)>,
8681 autocomplete_fields: Option<(Vec<String>, proc_macro2::Span)>,
8686 list_select_related: Option<String>,
8691 formfield_overrides: Option<(Vec<(String, String)>, proc_macro2::Span)>,
8697}
8698
8699#[derive(Clone, Copy, PartialEq, Eq)]
8702enum ModelAttrName {
8703 Rustango,
8704 Rgo,
8705}
8706
8707impl ModelAttrName {
8708 fn of(attr: &syn::Attribute) -> Option<Self> {
8709 let path = attr.path();
8710 if path.is_ident("rustango") {
8711 Some(Self::Rustango)
8712 } else if path.is_ident("rgo") {
8713 Some(Self::Rgo)
8714 } else {
8715 None
8716 }
8717 }
8718
8719 fn as_str(self) -> &'static str {
8720 match self {
8721 Self::Rustango => "rustango",
8722 Self::Rgo => "rgo",
8723 }
8724 }
8725}
8726
8727fn model_attrs(attrs: &[syn::Attribute]) -> syn::Result<Vec<&syn::Attribute>> {
8730 let mut first: Option<ModelAttrName> = None;
8731 let mut out = Vec::new();
8732 for attr in attrs {
8733 let Some(name) = ModelAttrName::of(attr) else {
8734 continue;
8735 };
8736 match first {
8737 Some(seen) if seen != name => {
8738 return Err(syn::Error::new_spanned(
8739 attr.path(),
8740 format!(
8741 "`#[{}(...)]` and `#[{}(...)]` are the same attribute; \
8742 use one name on an item or field",
8743 seen.as_str(),
8744 name.as_str(),
8745 ),
8746 ));
8747 }
8748 _ => first = Some(name),
8749 }
8750 out.push(attr);
8751 }
8752 Ok(out)
8753}
8754
8755fn parse_container_attrs(input: &DeriveInput) -> syn::Result<ContainerAttrs> {
8756 let mut out = ContainerAttrs {
8757 table: None,
8758 display: None,
8759 app: None,
8760 admin: None,
8761 audit: None,
8762 permissions: true,
8771 m2m: Vec::new(),
8772 generic_m2m: Vec::new(),
8773 indexes: Vec::new(),
8774 checks: Vec::new(),
8775 excludes: Vec::new(),
8776 composite_fks: Vec::new(),
8777 generic_fks: Vec::new(),
8778 scope: None,
8779 manager_fns: Vec::new(),
8780 default_order: Vec::new(),
8781 is_view: false,
8782 managed: true,
8783 verbose_name: None,
8784 verbose_name_plural: None,
8785 base_manager_name: None,
8786 order_with_respect_to: None,
8787 proxy: false,
8788 required_db_features: Vec::new(),
8789 required_db_vendor: None,
8790 default_related_name: None,
8791 db_table_comment: None,
8792 get_latest_by: None,
8793 extra_permissions: Vec::new(),
8794 default_permissions: Vec::new(),
8795 global_scopes: Vec::new(),
8796 through_relations: Vec::new(),
8797 reverse_has_relations: Vec::new(),
8798 generic_has_relations: Vec::new(),
8799 };
8800 for attr in model_attrs(&input.attrs)? {
8801 let written = ModelAttrName::of(attr).map_or("rustango", ModelAttrName::as_str);
8803 attr.parse_nested_meta(|meta| {
8804 if meta.path.is_ident("table") {
8805 let s: LitStr = meta.value()?.parse()?;
8806 let name = s.value();
8807 validate_table_name(&name, s.span())?;
8817 out.table = Some(name);
8818 return Ok(());
8819 }
8820 if meta.path.is_ident("display") {
8821 let s: LitStr = meta.value()?.parse()?;
8822 out.display = Some((s.value(), s.span()));
8823 return Ok(());
8824 }
8825 if meta.path.is_ident("app") {
8826 let s: LitStr = meta.value()?.parse()?;
8827 out.app = Some(s.value());
8828 return Ok(());
8829 }
8830 if meta.path.is_ident("scope") {
8831 let s: LitStr = meta.value()?.parse()?;
8832 let val = s.value();
8833 if !matches!(val.to_ascii_lowercase().as_str(), "registry" | "tenant") {
8834 return Err(meta.error(format!(
8835 "`scope` must be \"registry\" or \"tenant\", got {val:?}"
8836 )));
8837 }
8838 out.scope = Some(val);
8839 return Ok(());
8840 }
8841 if meta.path.is_ident("admin") {
8842 let mut admin = AdminAttrs::default();
8843 meta.parse_nested_meta(|inner| {
8844 if inner.path.is_ident("list_display") {
8845 let s: LitStr = inner.value()?.parse()?;
8846 admin.list_display =
8847 Some((split_field_list(&s.value()), s.span()));
8848 return Ok(());
8849 }
8850 if inner.path.is_ident("search_fields") {
8851 let s: LitStr = inner.value()?.parse()?;
8852 admin.search_fields =
8853 Some((split_field_list(&s.value()), s.span()));
8854 return Ok(());
8855 }
8856 if inner.path.is_ident("readonly_fields") {
8857 let s: LitStr = inner.value()?.parse()?;
8858 admin.readonly_fields =
8859 Some((split_field_list(&s.value()), s.span()));
8860 return Ok(());
8861 }
8862 if inner.path.is_ident("list_per_page") {
8863 let lit: syn::LitInt = inner.value()?.parse()?;
8864 admin.list_per_page = Some(lit.base10_parse::<usize>()?);
8865 return Ok(());
8866 }
8867 if inner.path.is_ident("ordering") {
8868 let s: LitStr = inner.value()?.parse()?;
8869 admin.ordering = Some((
8870 parse_ordering_list(&s.value()),
8871 s.span(),
8872 ));
8873 return Ok(());
8874 }
8875 if inner.path.is_ident("list_filter") {
8876 let s: LitStr = inner.value()?.parse()?;
8877 admin.list_filter =
8878 Some((split_field_list(&s.value()), s.span()));
8879 return Ok(());
8880 }
8881 if inner.path.is_ident("actions") {
8882 let s: LitStr = inner.value()?.parse()?;
8883 admin.actions =
8884 Some((split_field_list(&s.value()), s.span()));
8885 return Ok(());
8886 }
8887 if inner.path.is_ident("fieldsets") {
8888 let s: LitStr = inner.value()?.parse()?;
8889 admin.fieldsets =
8890 Some((parse_fieldset_list(&s.value()), s.span()));
8891 return Ok(());
8892 }
8893 if inner.path.is_ident("list_display_links") {
8894 let s: LitStr = inner.value()?.parse()?;
8895 admin.list_display_links =
8896 Some((split_field_list(&s.value()), s.span()));
8897 return Ok(());
8898 }
8899 if inner.path.is_ident("search_help_text") {
8900 let s: LitStr = inner.value()?.parse()?;
8901 admin.search_help_text = Some(s.value());
8902 return Ok(());
8903 }
8904 if inner.path.is_ident("actions_on_top") {
8905 let lit: syn::LitBool = inner.value()?.parse()?;
8906 admin.actions_on_top = Some(lit.value);
8907 return Ok(());
8908 }
8909 if inner.path.is_ident("actions_on_bottom") {
8910 let lit: syn::LitBool = inner.value()?.parse()?;
8911 admin.actions_on_bottom = Some(lit.value);
8912 return Ok(());
8913 }
8914 if inner.path.is_ident("date_hierarchy") {
8915 let s: LitStr = inner.value()?.parse()?;
8916 admin.date_hierarchy = Some(s.value());
8917 return Ok(());
8918 }
8919 if inner.path.is_ident("prepopulated_fields") {
8920 let s: LitStr = inner.value()?.parse()?;
8921 admin.prepopulated_fields =
8922 Some((parse_prepopulated_list(&s.value()), s.span()));
8923 return Ok(());
8924 }
8925 if inner.path.is_ident("raw_id_fields") {
8926 let s: LitStr = inner.value()?.parse()?;
8927 admin.raw_id_fields =
8928 Some((split_field_list(&s.value()), s.span()));
8929 return Ok(());
8930 }
8931 if inner.path.is_ident("autocomplete_fields") {
8932 let s: LitStr = inner.value()?.parse()?;
8933 admin.autocomplete_fields =
8934 Some((split_field_list(&s.value()), s.span()));
8935 return Ok(());
8936 }
8937 if inner.path.is_ident("list_select_related") {
8938 let s: LitStr = inner.value()?.parse()?;
8939 admin.list_select_related = Some(s.value());
8940 return Ok(());
8941 }
8942 if inner.path.is_ident("formfield_overrides") {
8943 let s: LitStr = inner.value()?.parse()?;
8944 admin.formfield_overrides =
8945 Some((parse_formfield_overrides(&s.value()), s.span()));
8946 return Ok(());
8947 }
8948 Err(inner.error(
8949 "unknown admin attribute (supported: \
8950 `list_display`, `list_display_links`, \
8951 `search_fields`, `search_help_text`, \
8952 `readonly_fields`, \
8953 `list_filter`, `list_per_page`, `ordering`, `actions`, \
8954 `actions_on_top`, `actions_on_bottom`, \
8955 `date_hierarchy`, \
8956 `prepopulated_fields`, \
8957 `raw_id_fields`, \
8958 `autocomplete_fields`, \
8959 `list_select_related`, \
8960 `formfield_overrides`, \
8961 `fieldsets`)",
8962 ))
8963 })?;
8964 out.admin = Some(admin);
8965 return Ok(());
8966 }
8967 if meta.path.is_ident("manager") {
8968 return Err(meta.error(
8970 "`manager(ext = ...)` was removed (#2132): declare your own \
8971 `trait FooManagerExt: Sized { … }` and implement it for `QuerySet<Foo>`",
8972 ));
8973 }
8974 if meta.path.is_ident("manager_fn") {
8975 let s: LitStr = meta.value()?.parse()?;
8980 let name = s.value();
8981 if name.is_empty() {
8982 return Err(meta.error("`manager_fn = \"...\"` cannot be empty"));
8983 }
8984 if name == "objects" {
8985 return Err(meta.error(
8986 "`manager_fn = \"objects\"` collides with the default \
8987 accessor — pick a different name",
8988 ));
8989 }
8990 let ident = syn::Ident::new(&name, s.span());
8991 if out.manager_fns.iter().any(|prev| *prev == ident) {
8992 return Err(meta.error(format!(
8993 "duplicate `manager_fn = \"{name}\"`"
8994 )));
8995 }
8996 out.manager_fns.push(ident);
8997 return Ok(());
8998 }
8999 if meta.path.is_ident("default_order") {
9000 let s: LitStr = meta.value()?.parse()?;
9005 let raw = s.value();
9006 let span = s.span();
9007 let mut parsed: Vec<(String, bool, proc_macro2::Span)> =
9008 Vec::new();
9009 for entry in raw.split(',') {
9010 let trimmed = entry.trim();
9011 if trimmed.is_empty() {
9012 return Err(syn::Error::new(
9013 span,
9014 "`default_order = \"...\"` has an empty entry — \
9015 check for a stray comma",
9016 ));
9017 }
9018 let (desc, name) = if let Some(rest) = trimmed.strip_prefix('-') {
9019 (true, rest.trim().to_owned())
9020 } else if let Some(rest) = trimmed.strip_prefix('+') {
9021 (false, rest.trim().to_owned())
9022 } else {
9023 (false, trimmed.to_owned())
9024 };
9025 if name.is_empty() {
9026 return Err(syn::Error::new(
9027 span,
9028 "`default_order` entry has no column name after the prefix",
9029 ));
9030 }
9031 if parsed.iter().any(|(n, _, _)| *n == name) {
9032 return Err(syn::Error::new(
9033 span,
9034 format!("duplicate column `{name}` in `default_order`"),
9035 ));
9036 }
9037 parsed.push((name, desc, span));
9038 }
9039 if parsed.is_empty() {
9040 return Err(syn::Error::new(
9041 span,
9042 "`default_order = \"...\"` cannot be empty",
9043 ));
9044 }
9045 out.default_order = parsed;
9046 return Ok(());
9047 }
9048 if meta.path.is_ident("global_scope") {
9049 let span = meta.path.span();
9055 let mut scope_name: Option<String> = None;
9056 let mut apply_path: Option<syn::Path> = None;
9057 meta.parse_nested_meta(|inner| {
9058 if inner.path.is_ident("name") {
9059 let s: LitStr = inner.value()?.parse()?;
9060 let raw = s.value();
9061 if raw.trim().is_empty() {
9062 return Err(syn::Error::new(
9063 s.span(),
9064 "`global_scope(name = \"...\")` must not be empty",
9065 ));
9066 }
9067 scope_name = Some(raw);
9068 return Ok(());
9069 }
9070 if inner.path.is_ident("apply") {
9071 let p: syn::Path = inner.value()?.parse()?;
9072 apply_path = Some(p);
9073 return Ok(());
9074 }
9075 Err(inner.error(
9076 "unknown `global_scope` attribute (supported: \
9077 `name`, `apply`)",
9078 ))
9079 })?;
9080 let Some(name) = scope_name else {
9081 return Err(syn::Error::new(
9082 span,
9083 "`global_scope` requires `name = \"...\"`",
9084 ));
9085 };
9086 let Some(apply) = apply_path else {
9087 return Err(syn::Error::new(
9088 span,
9089 "`global_scope` requires `apply = fn_path`",
9090 ));
9091 };
9092 if out.global_scopes.iter().any(|s| s.name == name) {
9093 return Err(syn::Error::new(
9094 span,
9095 format!(
9096 "duplicate global scope name `{name}` — \
9097 pick a unique identifier so \
9098 `QuerySet::without_global_scope(\"{name}\")` \
9099 is unambiguous"
9100 ),
9101 ));
9102 }
9103 out.global_scopes.push(GlobalScopeAttr { name, apply });
9104 return Ok(());
9105 }
9106 if meta.path.is_ident("through") {
9107 let span = meta.path.span();
9112 let mut accessor_name: Option<String> = None;
9113 let mut far_ident: Option<syn::Ident> = None;
9114 let mut far_fk_column: Option<String> = None;
9115 let mut intermediate_ident: Option<syn::Ident> = None;
9116 let mut intermediate_fk_column: Option<String> = None;
9117 let mut intermediate_pk_column: Option<String> = None;
9118 fn parse_nonempty_string(
9119 inner: &syn::meta::ParseNestedMeta<'_>,
9120 field: &str,
9121 ) -> syn::Result<String> {
9122 let s: LitStr = inner.value()?.parse()?;
9123 let raw = s.value();
9124 let trimmed = raw.trim();
9125 if trimmed.is_empty() {
9126 return Err(syn::Error::new(
9127 s.span(),
9128 format!("`through({field} = \"...\")` must not be empty"),
9129 ));
9130 }
9131 Ok(trimmed.to_owned())
9132 }
9133 meta.parse_nested_meta(|inner| {
9134 if inner.path.is_ident("name") {
9135 accessor_name = Some(parse_nonempty_string(&inner, "name")?);
9136 return Ok(());
9137 }
9138 if inner.path.is_ident("far") {
9139 let s: LitStr = inner.value()?.parse()?;
9145 let raw = s.value();
9146 let trimmed = raw.trim();
9147 if trimmed.is_empty() {
9148 return Err(syn::Error::new(
9149 s.span(),
9150 "`through(far = \"...\")` must not be empty",
9151 ));
9152 }
9153 far_ident = Some(syn::Ident::new(trimmed, s.span()));
9154 return Ok(());
9155 }
9156 if inner.path.is_ident("far_fk_column") {
9157 far_fk_column =
9158 Some(parse_nonempty_string(&inner, "far_fk_column")?);
9159 return Ok(());
9160 }
9161 if inner.path.is_ident("intermediate") {
9162 let s: LitStr = inner.value()?.parse()?;
9163 let raw = s.value();
9164 let trimmed = raw.trim();
9165 if trimmed.is_empty() {
9166 return Err(syn::Error::new(
9167 s.span(),
9168 "`through(intermediate = \"...\")` must not be empty",
9169 ));
9170 }
9171 intermediate_ident = Some(syn::Ident::new(trimmed, s.span()));
9172 return Ok(());
9173 }
9174 if inner.path.is_ident("intermediate_fk_column") {
9175 intermediate_fk_column =
9176 Some(parse_nonempty_string(&inner, "intermediate_fk_column")?);
9177 return Ok(());
9178 }
9179 if inner.path.is_ident("intermediate_pk_column") {
9180 intermediate_pk_column =
9181 Some(parse_nonempty_string(&inner, "intermediate_pk_column")?);
9182 return Ok(());
9183 }
9184 Err(inner.error(
9185 "unknown `through` attribute (supported: \
9186 `name`, `far`, `far_fk_column`, \
9187 `intermediate`, `intermediate_fk_column`, \
9188 `intermediate_pk_column`)",
9189 ))
9190 })?;
9191 let Some(name) = accessor_name else {
9192 return Err(syn::Error::new(
9193 span,
9194 "`through` requires `name = \"...\"`",
9195 ));
9196 };
9197 let Some(far) = far_ident else {
9198 return Err(syn::Error::new(
9199 span,
9200 "`through` requires `far = \"FarModelType\"`",
9201 ));
9202 };
9203 let Some(far_fk_column) = far_fk_column else {
9204 return Err(syn::Error::new(
9205 span,
9206 "`through` requires `far_fk_column = \"<column>\"`",
9207 ));
9208 };
9209 let Some(intermediate) = intermediate_ident else {
9210 return Err(syn::Error::new(
9211 span,
9212 "`through` requires `intermediate = \"IntermediateModelType\"`",
9213 ));
9214 };
9215 let Some(intermediate_fk_column) = intermediate_fk_column else {
9216 return Err(syn::Error::new(
9217 span,
9218 "`through` requires `intermediate_fk_column = \"<column>\"`",
9219 ));
9220 };
9221 let intermediate_pk_column =
9222 intermediate_pk_column.unwrap_or_else(|| "id".to_owned());
9223 if out.through_relations.iter().any(|t| t.name == name) {
9224 return Err(syn::Error::new(
9225 span,
9226 format!(
9227 "duplicate `through(name = \"{name}\")` — \
9228 pick a unique accessor name"
9229 ),
9230 ));
9231 }
9232 out.through_relations.push(ThroughAttr {
9233 name,
9234 far,
9235 far_fk_column,
9236 intermediate,
9237 intermediate_fk_column,
9238 intermediate_pk_column,
9239 });
9240 return Ok(());
9241 }
9242 if meta.path.is_ident("reverse_has") {
9243 let span = meta.path.span();
9247 let mut accessor_name: Option<String> = None;
9248 let mut child_ident: Option<syn::Ident> = None;
9249 let mut child_fk_column: Option<String> = None;
9250 let mut self_pk_column: Option<String> = None;
9251 let mut self_pk_span: Option<proc_macro2::Span> = None;
9252 meta.parse_nested_meta(|inner| {
9253 if inner.path.is_ident("name") {
9254 let s: LitStr = inner.value()?.parse()?;
9255 let raw = s.value();
9256 if raw.trim().is_empty() {
9257 return Err(syn::Error::new(
9258 s.span(),
9259 "`reverse_has(name = \"...\")` must not be empty",
9260 ));
9261 }
9262 accessor_name = Some(raw);
9263 return Ok(());
9264 }
9265 if inner.path.is_ident("child") {
9266 let s: LitStr = inner.value()?.parse()?;
9267 let raw = s.value();
9268 let trimmed = raw.trim();
9269 if trimmed.is_empty() {
9270 return Err(syn::Error::new(
9271 s.span(),
9272 "`reverse_has(child = \"...\")` must not be empty",
9273 ));
9274 }
9275 child_ident = Some(syn::Ident::new(trimmed, s.span()));
9276 return Ok(());
9277 }
9278 if inner.path.is_ident("child_fk_column") {
9279 let s: LitStr = inner.value()?.parse()?;
9280 let raw = s.value();
9281 let trimmed = raw.trim();
9282 if trimmed.is_empty() {
9283 return Err(syn::Error::new(
9284 s.span(),
9285 "`reverse_has(child_fk_column = \"...\")` must not be empty",
9286 ));
9287 }
9288 child_fk_column = Some(trimmed.to_owned());
9289 return Ok(());
9290 }
9291 if inner.path.is_ident("self_pk_column") {
9292 let s: LitStr = inner.value()?.parse()?;
9293 let raw = s.value();
9294 let trimmed = raw.trim();
9295 if trimmed.is_empty() {
9296 return Err(syn::Error::new(
9297 s.span(),
9298 "`reverse_has(self_pk_column = \"...\")` must not be empty",
9299 ));
9300 }
9301 self_pk_column = Some(trimmed.to_owned());
9302 self_pk_span = Some(s.span());
9303 return Ok(());
9304 }
9305 Err(inner.error(
9306 "unknown `reverse_has` attribute (supported: \
9307 `name`, `child`, `child_fk_column`, \
9308 `self_pk_column`)",
9309 ))
9310 })?;
9311 let Some(name) = accessor_name else {
9312 return Err(syn::Error::new(
9313 span,
9314 "`reverse_has` requires `name = \"...\"`",
9315 ));
9316 };
9317 let Some(child) = child_ident else {
9318 return Err(syn::Error::new(
9319 span,
9320 "`reverse_has` requires `child = \"ChildModelType\"`",
9321 ));
9322 };
9323 let Some(child_fk_column) = child_fk_column else {
9324 return Err(syn::Error::new(
9325 span,
9326 "`reverse_has` requires `child_fk_column = \"<column>\"`",
9327 ));
9328 };
9329 if out.reverse_has_relations.iter().any(|r| r.name == name) {
9330 return Err(syn::Error::new(
9331 span,
9332 format!(
9333 "duplicate `reverse_has(name = \"{name}\")` — \
9334 pick a unique accessor name"
9335 ),
9336 ));
9337 }
9338 out.reverse_has_relations.push(ReverseHasAttr {
9339 name,
9340 child,
9341 child_fk_column,
9342 self_pk_column,
9343 self_pk_span,
9344 });
9345 return Ok(());
9346 }
9347 if meta.path.is_ident("generic_has") {
9348 let span = meta.path.span();
9354 let mut accessor_name: Option<String> = None;
9355 let mut child_ident: Option<syn::Ident> = None;
9356 let mut ct_column: Option<String> = None;
9357 let mut pk_column: Option<String> = None;
9358 let mut self_pk_column: Option<String> = None;
9359 let mut self_pk_span: Option<proc_macro2::Span> = None;
9360 meta.parse_nested_meta(|inner| {
9361 if inner.path.is_ident("name") {
9362 let s: LitStr = inner.value()?.parse()?;
9363 let raw = s.value();
9364 if raw.trim().is_empty() {
9365 return Err(syn::Error::new(
9366 s.span(),
9367 "`generic_has(name = \"...\")` must not be empty",
9368 ));
9369 }
9370 accessor_name = Some(raw);
9371 return Ok(());
9372 }
9373 if inner.path.is_ident("child") {
9374 let s: LitStr = inner.value()?.parse()?;
9375 let trimmed = s.value().trim().to_owned();
9376 if trimmed.is_empty() {
9377 return Err(syn::Error::new(
9378 s.span(),
9379 "`generic_has(child = \"...\")` must not be empty",
9380 ));
9381 }
9382 child_ident = Some(syn::Ident::new(&trimmed, s.span()));
9383 return Ok(());
9384 }
9385 if inner.path.is_ident("ct_column") {
9386 let s: LitStr = inner.value()?.parse()?;
9387 let trimmed = s.value().trim().to_owned();
9388 if trimmed.is_empty() {
9389 return Err(syn::Error::new(
9390 s.span(),
9391 "`generic_has(ct_column = \"...\")` must not be empty",
9392 ));
9393 }
9394 ct_column = Some(trimmed);
9395 return Ok(());
9396 }
9397 if inner.path.is_ident("pk_column") {
9398 let s: LitStr = inner.value()?.parse()?;
9399 let trimmed = s.value().trim().to_owned();
9400 if trimmed.is_empty() {
9401 return Err(syn::Error::new(
9402 s.span(),
9403 "`generic_has(pk_column = \"...\")` must not be empty",
9404 ));
9405 }
9406 pk_column = Some(trimmed);
9407 return Ok(());
9408 }
9409 if inner.path.is_ident("self_pk_column") {
9410 let s: LitStr = inner.value()?.parse()?;
9411 let trimmed = s.value().trim().to_owned();
9412 if trimmed.is_empty() {
9413 return Err(syn::Error::new(
9414 s.span(),
9415 "`generic_has(self_pk_column = \"...\")` must not be empty",
9416 ));
9417 }
9418 self_pk_column = Some(trimmed);
9419 self_pk_span = Some(s.span());
9420 return Ok(());
9421 }
9422 Err(inner.error(
9423 "unknown `generic_has` attribute (supported: \
9424 `name`, `child`, `ct_column`, `pk_column`, \
9425 `self_pk_column`)",
9426 ))
9427 })?;
9428 let Some(name) = accessor_name else {
9429 return Err(syn::Error::new(
9430 span,
9431 "`generic_has` requires `name = \"...\"`",
9432 ));
9433 };
9434 let Some(child) = child_ident else {
9435 return Err(syn::Error::new(
9436 span,
9437 "`generic_has` requires `child = \"ChildModelType\"`",
9438 ));
9439 };
9440 let ct_column = ct_column.unwrap_or_else(|| "content_type_id".to_owned());
9441 let pk_column = pk_column.unwrap_or_else(|| "object_pk".to_owned());
9442 if out.generic_has_relations.iter().any(|r| r.name == name) {
9443 return Err(syn::Error::new(
9444 span,
9445 format!(
9446 "duplicate `generic_has(name = \"{name}\")` — \
9447 pick a unique accessor name"
9448 ),
9449 ));
9450 }
9451 out.generic_has_relations.push(GenericHasAttr {
9452 name,
9453 child,
9454 ct_column,
9455 pk_column,
9456 self_pk_column,
9457 self_pk_span,
9458 });
9459 return Ok(());
9460 }
9461 if meta.path.is_ident("audit") {
9462 let mut audit = AuditAttrs::default();
9463 meta.parse_nested_meta(|inner| {
9464 if inner.path.is_ident("track") {
9465 let s: LitStr = inner.value()?.parse()?;
9466 audit.track =
9467 Some((split_field_list(&s.value()), s.span()));
9468 return Ok(());
9469 }
9470 Err(inner.error(
9471 "unknown audit attribute (supported: `track`)",
9472 ))
9473 })?;
9474 out.audit = Some(audit);
9475 return Ok(());
9476 }
9477 if meta.path.is_ident("permissions") {
9478 if let Ok(v) = meta.value() {
9483 let lit: syn::LitBool = v.parse()?;
9484 out.permissions = lit.value;
9485 } else {
9486 out.permissions = true;
9487 }
9488 return Ok(());
9489 }
9490 if meta.path.is_ident("view") {
9491 if let Ok(v) = meta.value() {
9497 let lit: syn::LitBool = v.parse()?;
9498 out.is_view = lit.value;
9499 } else {
9500 out.is_view = true;
9501 }
9502 return Ok(());
9503 }
9504 if meta.path.is_ident("managed") {
9505 let v = meta.value()?;
9513 let lit: syn::LitBool = v.parse()?;
9514 out.managed = lit.value;
9515 return Ok(());
9516 }
9517 if meta.path.is_ident("verbose_name") {
9518 let s: LitStr = meta.value()?.parse()?;
9519 out.verbose_name = Some(s.value());
9520 return Ok(());
9521 }
9522 if meta.path.is_ident("verbose_name_plural") {
9523 let s: LitStr = meta.value()?.parse()?;
9524 out.verbose_name_plural = Some(s.value());
9525 return Ok(());
9526 }
9527 if meta.path.is_ident("db_table_comment") {
9528 let s: LitStr = meta.value()?.parse()?;
9531 out.db_table_comment = Some(s.value());
9532 return Ok(());
9533 }
9534 if meta.path.is_ident("proxy") {
9535 let value = if meta.input.peek(syn::Token![=]) {
9544 meta.value()?.parse::<syn::LitBool>()?.value
9545 } else {
9546 true
9547 };
9548 out.proxy = value;
9549 return Ok(());
9550 }
9551 if meta.path.is_ident("order_with_respect_to") {
9552 let s: LitStr = meta.value()?.parse()?;
9566 let raw = s.value();
9567 if raw.is_empty() {
9568 return Err(syn::Error::new(
9569 s.span(),
9570 "`order_with_respect_to` must be a non-empty FK field name",
9571 ));
9572 }
9573 let valid = raw
9574 .chars()
9575 .all(|c| c == '_' || c.is_ascii_alphanumeric())
9576 && !raw.chars().next().is_some_and(|c| c.is_ascii_digit());
9577 if !valid {
9578 return Err(syn::Error::new(
9579 s.span(),
9580 format!(
9581 "`order_with_respect_to` must be a valid Rust \
9582 identifier (letters / digits / underscores, \
9583 not starting with a digit); got `{raw}`"
9584 ),
9585 ));
9586 }
9587 out.order_with_respect_to = Some(raw);
9588 return Ok(());
9589 }
9590 if meta.path.is_ident("required_db_features") {
9591 let s: LitStr = meta.value()?.parse()?;
9606 let raw = s.value();
9607 let features: Vec<String> = raw
9608 .split(',')
9609 .map(str::trim)
9610 .filter(|s| !s.is_empty())
9611 .map(str::to_owned)
9612 .collect();
9613 if features.is_empty() {
9614 return Err(syn::Error::new(
9615 s.span(),
9616 "`required_db_features` must list at least one \
9617 comma-separated capability token",
9618 ));
9619 }
9620 out.required_db_features = features;
9621 return Ok(());
9622 }
9623 if meta.path.is_ident("required_db_vendor") {
9624 let s: LitStr = meta.value()?.parse()?;
9632 let raw = s.value().to_ascii_lowercase();
9633 match raw.as_str() {
9634 "postgresql" | "postgres" | "pg" => {
9635 out.required_db_vendor = Some("postgres".to_owned());
9636 }
9637 "mysql" | "mariadb" => {
9638 out.required_db_vendor = Some("mysql".to_owned());
9639 }
9640 "sqlite" | "sqlite3" => {
9641 out.required_db_vendor = Some("sqlite".to_owned());
9642 }
9643 _ => {
9644 return Err(syn::Error::new(
9645 s.span(),
9646 format!(
9647 "unknown required_db_vendor `{raw}` — \
9648 expected `postgres` (aliases: `postgresql`, `pg`), \
9649 `mysql` (alias: `mariadb`), or `sqlite` \
9650 (alias: `sqlite3`)"
9651 ),
9652 ));
9653 }
9654 }
9655 return Ok(());
9656 }
9657 if meta.path.is_ident("base_manager_name") {
9658 let s: LitStr = meta.value()?.parse()?;
9667 let raw = s.value();
9668 if raw.is_empty() {
9669 return Err(syn::Error::new(
9670 s.span(),
9671 "`base_manager_name` must be a non-empty string",
9672 ));
9673 }
9674 let valid = raw
9675 .chars()
9676 .all(|c| c == '_' || c.is_ascii_alphanumeric())
9677 && !raw.chars().next().is_some_and(|c| c.is_ascii_digit());
9678 if !valid {
9679 return Err(syn::Error::new(
9680 s.span(),
9681 format!(
9682 "`base_manager_name` must be a valid Rust \
9683 identifier (letters / digits / underscores, \
9684 not starting with a digit); got `{raw}`"
9685 ),
9686 ));
9687 }
9688 out.base_manager_name = Some(raw);
9689 return Ok(());
9690 }
9691 if meta.path.is_ident("default_related_name") {
9692 let s: LitStr = meta.value()?.parse()?;
9703 let raw = s.value();
9704 if raw.is_empty() {
9705 return Err(syn::Error::new(
9706 s.span(),
9707 "`default_related_name` must be a non-empty string",
9708 ));
9709 }
9710 let valid = raw
9711 .chars()
9712 .all(|c| c == '_' || c.is_ascii_lowercase() || c.is_ascii_digit())
9713 && !raw.chars().next().is_some_and(|c| c.is_ascii_digit());
9714 if !valid {
9715 return Err(syn::Error::new(
9716 s.span(),
9717 format!(
9718 "`default_related_name` must be snake_case ASCII \
9719 (lowercase letters / digits / underscores, not \
9720 starting with a digit); got `{raw}`"
9721 ),
9722 ));
9723 }
9724 out.default_related_name = Some(raw);
9725 return Ok(());
9726 }
9727 if meta.path.is_ident("extra_permissions") {
9728 let s: LitStr = meta.value()?.parse()?;
9730 let raw = s.value();
9731 let mut pairs = Vec::new();
9732 for entry in raw.split(',') {
9733 let entry = entry.trim();
9734 if entry.is_empty() {
9735 continue;
9736 }
9737 let (codename, label) = match entry.split_once(':') {
9738 Some((c, l)) => (c.trim().to_owned(), l.trim().to_owned()),
9739 None => (entry.to_owned(), entry.to_owned()),
9740 };
9741 if codename.is_empty() {
9742 return Err(meta.error(
9743 "`extra_permissions` entries must be `codename:label` pairs",
9744 ));
9745 }
9746 pairs.push((codename, label));
9747 }
9748 if pairs.is_empty() {
9749 return Err(meta
9750 .error("`extra_permissions = \"…\"` must list at least one pair"));
9751 }
9752 out.extra_permissions = pairs;
9753 return Ok(());
9754 }
9755 if meta.path.is_ident("default_permissions") {
9756 let s: LitStr = meta.value()?.parse()?;
9759 let raw = s.value();
9760 let mut actions: Vec<String> = Vec::new();
9761 for entry in raw.split(',') {
9762 let action = entry.trim().to_ascii_lowercase();
9763 if action.is_empty() {
9764 continue;
9765 }
9766 match action.as_str() {
9767 "add" | "change" | "delete" | "view" => {}
9768 other => {
9769 return Err(syn::Error::new(
9770 s.span(),
9771 format!(
9772 "unknown default_permissions action `{other}` — \
9773 expected one of `add`, `change`, `delete`, `view`"
9774 ),
9775 ));
9776 }
9777 }
9778 if !actions.contains(&action) {
9779 actions.push(action);
9780 }
9781 }
9782 if actions.is_empty() {
9783 return Err(syn::Error::new(
9784 s.span(),
9785 "`default_permissions = \"…\"` must list at least one action; \
9786 use `permissions = false` on the container if you want NO \
9787 permissions seeded for this model.",
9788 ));
9789 }
9790 out.default_permissions = actions;
9791 return Ok(());
9792 }
9793 if meta.path.is_ident("get_latest_by") {
9794 let s: LitStr = meta.value()?.parse()?;
9796 let raw = s.value();
9797 let trimmed = raw.trim();
9798 if trimmed.is_empty() {
9799 return Err(meta.error("`get_latest_by` must name a column"));
9800 }
9801 let (col, desc) = if let Some(stripped) = trimmed.strip_prefix('-') {
9802 (stripped.to_owned(), true)
9803 } else if let Some(stripped) = trimmed.strip_prefix('+') {
9804 (stripped.to_owned(), false)
9805 } else {
9806 (trimmed.to_owned(), false)
9807 };
9808 if col.is_empty() {
9809 return Err(meta.error("`get_latest_by` must name a column"));
9810 }
9811 out.get_latest_by = Some((col, desc, s.span()));
9812 return Ok(());
9813 }
9814 if meta.path.is_ident("unique_together") {
9815 let (columns, name) = parse_together_attr(&meta, "unique_together", written)?;
9824 out.indexes.push(IndexAttr {
9825 span: meta.path.span(),
9826 name,
9827 columns,
9828 unique: true,
9829 method: "btree".to_owned(),
9830 where_clause: None,
9831 include: Vec::new(),
9832 });
9833 return Ok(());
9834 }
9835 if meta.path.is_ident("index_together") {
9836 let (columns, name) = parse_together_attr(&meta, "index_together", written)?;
9842 out.indexes.push(IndexAttr {
9843 span: meta.path.span(),
9844 name,
9845 columns,
9846 unique: false,
9847 method: "btree".to_owned(),
9848 where_clause: None,
9849 include: Vec::new(),
9850 });
9851 return Ok(());
9852 }
9853 if meta.path.is_ident("unique_when") {
9854 let mut columns: Option<Vec<String>> = None;
9868 let mut condition: Option<String> = None;
9869 let mut name: Option<String> = None;
9870 let mut include: Vec<String> = Vec::new();
9871 meta.parse_nested_meta(|inner| {
9872 if inner.path.is_ident("columns") {
9873 let s: LitStr = inner.value()?.parse()?;
9874 columns = Some(split_field_list(&s.value()));
9875 return Ok(());
9876 }
9877 if inner.path.is_ident("condition") {
9878 let s: LitStr = inner.value()?.parse()?;
9879 condition = Some(s.value());
9880 return Ok(());
9881 }
9882 if inner.path.is_ident("name") {
9883 let s: LitStr = inner.value()?.parse()?;
9884 name = Some(s.value());
9885 return Ok(());
9886 }
9887 if inner.path.is_ident("include") {
9888 let s: LitStr = inner.value()?.parse()?;
9892 include = split_field_list(&s.value());
9893 return Ok(());
9894 }
9895 Err(inner.error(
9896 "unknown unique_when attribute (supported: \
9897 `columns = \"...\"`, `condition = \"...\"`, \
9898 `name = \"...\"`, `include = \"...\"`)",
9899 ))
9900 })?;
9901 let columns = columns.ok_or_else(|| {
9902 meta.error("`unique_when(...)` requires `columns = \"...\"`")
9903 })?;
9904 let condition = condition.ok_or_else(|| {
9905 meta.error("`unique_when(...)` requires `condition = \"...\"`")
9906 })?;
9907 if columns.is_empty() {
9908 return Err(meta.error("`unique_when(columns = \"\")` is empty"));
9909 }
9910 out.indexes.push(IndexAttr {
9911 span: meta.path.span(),
9912 name,
9913 columns,
9914 unique: true,
9915 method: "btree".to_owned(),
9916 where_clause: Some(condition),
9917 include,
9918 });
9919 return Ok(());
9920 }
9921 if meta.path.is_ident("index_when") {
9922 let mut columns: Option<Vec<String>> = None;
9939 let mut condition: Option<String> = None;
9940 let mut name: Option<String> = None;
9941 let mut method: String = "btree".to_owned();
9942 let mut include: Vec<String> = Vec::new();
9943 meta.parse_nested_meta(|inner| {
9944 if inner.path.is_ident("columns") {
9945 let s: LitStr = inner.value()?.parse()?;
9946 columns = Some(split_field_list(&s.value()));
9947 return Ok(());
9948 }
9949 if inner.path.is_ident("condition") {
9950 let s: LitStr = inner.value()?.parse()?;
9951 condition = Some(s.value());
9952 return Ok(());
9953 }
9954 if inner.path.is_ident("name") {
9955 let s: LitStr = inner.value()?.parse()?;
9956 name = Some(s.value());
9957 return Ok(());
9958 }
9959 if inner.path.is_ident("method") {
9960 let s: LitStr = inner.value()?.parse()?;
9961 method = s.value();
9962 return Ok(());
9963 }
9964 if inner.path.is_ident("include") {
9965 let s: LitStr = inner.value()?.parse()?;
9969 include = split_field_list(&s.value());
9970 return Ok(());
9971 }
9972 Err(inner.error(
9973 "unknown index_when attribute (supported: \
9974 `columns = \"...\"`, `condition = \"...\"`, \
9975 `name = \"...\"`, `method = \"btree|gin|gist|...\"`, \
9976 `include = \"...\"`)",
9977 ))
9978 })?;
9979 let columns = columns
9980 .ok_or_else(|| meta.error("`index_when(...)` requires `columns = \"...\"`"))?;
9981 let condition = condition.ok_or_else(|| {
9982 meta.error("`index_when(...)` requires `condition = \"...\"`")
9983 })?;
9984 if columns.is_empty() {
9985 return Err(meta.error("`index_when(columns = \"\")` is empty"));
9986 }
9987 out.indexes.push(IndexAttr {
9988 span: meta.path.span(),
9989 name,
9990 columns,
9991 unique: false,
9992 method,
9993 where_clause: Some(condition),
9994 include,
9995 });
9996 return Ok(());
9997 }
9998 if meta.path.is_ident("index") {
9999 let cols_lit: LitStr;
10010 let mut unique = false;
10011 let mut name: Option<String> = None;
10012 let mut method = "btree".to_owned();
10013 if meta.input.peek(syn::token::Paren) {
10014 let content;
10015 syn::parenthesized!(content in meta.input);
10016 cols_lit = content.parse()?;
10017 while content.peek(syn::Token![,]) {
10018 content.parse::<syn::Token![,]>()?;
10019 if content.is_empty() {
10020 break;
10021 }
10022 let flag: syn::Ident = content.parse()?;
10023 if flag == "unique" {
10024 unique = true;
10025 } else if flag == "name" {
10026 content.parse::<syn::Token![=]>()?;
10027 let s: LitStr = content.parse()?;
10028 name = Some(s.value());
10029 } else if flag == "method" {
10030 content.parse::<syn::Token![=]>()?;
10031 let s: LitStr = content.parse()?;
10032 let v = s.value();
10033 match v.as_str() {
10034 "btree" | "gin" | "gist" | "brin" | "spgist" | "hash"
10035 | "bloom" => method = v,
10036 other => {
10037 return Err(syn::Error::new(
10038 s.span(),
10039 format!("unknown index method `{other}` (supported: btree, gin, gist, brin, spgist, hash, bloom)"),
10040 ));
10041 }
10042 }
10043 } else {
10044 return Err(syn::Error::new(
10045 flag.span(),
10046 "unknown index flag (supported: `unique`, `name`, `method`)",
10047 ));
10048 }
10049 }
10050 } else {
10051 cols_lit = meta.value()?.parse()?;
10052 }
10053 let columns = split_field_list(&cols_lit.value());
10054 out.indexes.push(IndexAttr {
10055 span: meta.path.span(),
10056 name,
10057 columns,
10058 unique,
10059 method,
10060 where_clause: None,
10061 include: Vec::new(),
10062 });
10063 return Ok(());
10064 }
10065 if meta.path.is_ident("check") {
10066 let mut name: Option<String> = None;
10068 let mut expr: Option<String> = None;
10069 meta.parse_nested_meta(|inner| {
10070 if inner.path.is_ident("name") {
10071 let s: LitStr = inner.value()?.parse()?;
10072 name = Some(s.value());
10073 return Ok(());
10074 }
10075 if inner.path.is_ident("expr") {
10076 let s: LitStr = inner.value()?.parse()?;
10077 expr = Some(s.value());
10078 return Ok(());
10079 }
10080 Err(inner.error("unknown check attribute (supported: `name`, `expr`)"))
10081 })?;
10082 let name = name.ok_or_else(|| meta.error("check requires `name = \"...\"`"))?;
10083 let expr = expr.ok_or_else(|| meta.error("check requires `expr = \"...\"`"))?;
10084 out.checks.push(CheckAttr { name, expr });
10085 return Ok(());
10086 }
10087 if meta.path.is_ident("exclude") {
10088 let mut name: Option<String> = None;
10092 let mut using: Option<String> = None;
10093 let mut elements_raw: Option<(String, proc_macro2::Span)> = None;
10094 let mut where_clause: Option<String> = None;
10095 meta.parse_nested_meta(|inner| {
10096 if inner.path.is_ident("name") {
10097 let s: LitStr = inner.value()?.parse()?;
10098 name = Some(s.value());
10099 return Ok(());
10100 }
10101 if inner.path.is_ident("using") {
10102 let s: LitStr = inner.value()?.parse()?;
10103 using = Some(s.value());
10104 return Ok(());
10105 }
10106 if inner.path.is_ident("elements") {
10107 let s: LitStr = inner.value()?.parse()?;
10108 elements_raw = Some((s.value(), s.span()));
10109 return Ok(());
10110 }
10111 if inner.path.is_ident("where") || inner.path.is_ident("where_clause") {
10112 let s: LitStr = inner.value()?.parse()?;
10113 where_clause = Some(s.value());
10114 return Ok(());
10115 }
10116 Err(inner.error(
10117 "unknown exclude attribute (supported: `name`, `using`, `elements`, `where`)",
10118 ))
10119 })?;
10120 let name = name.ok_or_else(|| meta.error("exclude requires `name = \"...\"`"))?;
10121 let using = using.unwrap_or_else(|| "gist".to_owned());
10122 let (elements_str, elements_span) = elements_raw.ok_or_else(|| {
10123 meta.error(
10124 "exclude requires `elements = \"col WITH op, col WITH op\"`",
10125 )
10126 })?;
10127 let mut elements: Vec<(String, String)> = Vec::new();
10129 for pair in elements_str.split(',') {
10130 let pair = pair.trim();
10131 if pair.is_empty() {
10132 continue;
10133 }
10134 let mut split = pair.splitn(2, |c: char| c.is_whitespace());
10135 let col = split.next().unwrap_or("").trim();
10136 let rest = split.next().unwrap_or("").trim();
10137 let rest_lc = rest.to_ascii_lowercase();
10139 let op = rest_lc
10140 .strip_prefix("with")
10141 .map(|r| r.trim_start())
10142 .filter(|r| !r.is_empty())
10143 .map(|_| {
10144 rest[4..].trim_start().to_owned()
10147 });
10148 let Some(op) = op else {
10149 return Err(syn::Error::new(
10150 elements_span,
10151 format!(
10152 "exclude elements: `{pair}` must be `<col> WITH <op>` \
10153 (e.g. `room_id WITH =` or `during WITH &&`)"
10154 ),
10155 ));
10156 };
10157 if col.is_empty() || op.is_empty() {
10158 return Err(syn::Error::new(
10159 elements_span,
10160 format!(
10161 "exclude elements: `{pair}` must be `<col> WITH <op>` \
10162 (both sides non-empty)"
10163 ),
10164 ));
10165 }
10166 elements.push((col.to_owned(), op));
10167 }
10168 if elements.is_empty() {
10169 return Err(syn::Error::new(
10170 elements_span,
10171 "exclude requires at least one `col WITH op` element",
10172 ));
10173 }
10174 out.excludes.push(ExcludeAttr {
10175 name,
10176 using,
10177 elements,
10178 where_clause,
10179 });
10180 return Ok(());
10181 }
10182 if meta.path.is_ident("generic_fk") {
10183 let mut gfk = GenericFkAttr {
10184 name: String::new(),
10185 ct_column: String::new(),
10186 pk_column: String::new(),
10187 };
10188 meta.parse_nested_meta(|inner| {
10189 if inner.path.is_ident("name") {
10190 let s: LitStr = inner.value()?.parse()?;
10191 gfk.name = s.value();
10192 return Ok(());
10193 }
10194 if inner.path.is_ident("ct_column") {
10195 let s: LitStr = inner.value()?.parse()?;
10196 gfk.ct_column = s.value();
10197 return Ok(());
10198 }
10199 if inner.path.is_ident("pk_column") {
10200 let s: LitStr = inner.value()?.parse()?;
10201 gfk.pk_column = s.value();
10202 return Ok(());
10203 }
10204 Err(inner.error(
10205 "unknown generic_fk attribute (supported: `name`, `ct_column`, `pk_column`)",
10206 ))
10207 })?;
10208 if gfk.name.is_empty() {
10209 return Err(meta.error("generic_fk requires `name = \"...\"`"));
10210 }
10211 if gfk.ct_column.is_empty() {
10212 return Err(meta.error("generic_fk requires `ct_column = \"...\"`"));
10213 }
10214 if gfk.pk_column.is_empty() {
10215 return Err(meta.error("generic_fk requires `pk_column = \"...\"`"));
10216 }
10217 out.generic_fks.push(gfk);
10218 return Ok(());
10219 }
10220 if meta.path.is_ident("fk_composite") {
10221 let mut fk = CompositeFkAttr {
10222 name: String::new(),
10223 to: String::new(),
10224 from: Vec::new(),
10225 on: Vec::new(),
10226 };
10227 meta.parse_nested_meta(|inner| {
10228 if inner.path.is_ident("name") {
10229 let s: LitStr = inner.value()?.parse()?;
10230 fk.name = s.value();
10231 return Ok(());
10232 }
10233 if inner.path.is_ident("to") {
10234 let s: LitStr = inner.value()?.parse()?;
10235 fk.to = s.value();
10236 return Ok(());
10237 }
10238 if inner.path.is_ident("on") || inner.path.is_ident("from") {
10241 let value = inner.value()?;
10242 let content;
10243 syn::parenthesized!(content in value);
10244 let lits: syn::punctuated::Punctuated<syn::LitStr, syn::Token![,]> =
10245 content.parse_terminated(
10246 |p| p.parse::<syn::LitStr>(),
10247 syn::Token![,],
10248 )?;
10249 let cols: Vec<String> = lits.iter().map(syn::LitStr::value).collect();
10250 if inner.path.is_ident("on") {
10251 fk.on = cols;
10252 } else {
10253 fk.from = cols;
10254 }
10255 return Ok(());
10256 }
10257 Err(inner.error(
10258 "unknown fk_composite attribute (supported: `name`, `to`, `on`, `from`)",
10259 ))
10260 })?;
10261 if fk.name.is_empty() {
10262 return Err(meta.error("fk_composite requires `name = \"...\"`"));
10263 }
10264 if fk.to.is_empty() {
10265 return Err(meta.error("fk_composite requires `to = \"...\"`"));
10266 }
10267 if fk.from.is_empty() || fk.on.is_empty() {
10268 return Err(meta.error(
10269 "fk_composite requires non-empty `from = (...)` and `on = (...)` tuples",
10270 ));
10271 }
10272 if fk.from.len() != fk.on.len() {
10273 return Err(meta.error(format!(
10274 "fk_composite `from` ({} cols) and `on` ({} cols) must be the same length",
10275 fk.from.len(),
10276 fk.on.len(),
10277 )));
10278 }
10279 out.composite_fks.push(fk);
10280 return Ok(());
10281 }
10282 if meta.path.is_ident("m2m") {
10283 let mut m2m = M2MAttr {
10284 name: String::new(),
10285 to: String::new(),
10286 through: String::new(),
10287 src: String::new(),
10288 dst: String::new(),
10289 auto_create: true,
10290 };
10291 meta.parse_nested_meta(|inner| {
10292 if inner.path.is_ident("name") {
10293 let s: LitStr = inner.value()?.parse()?;
10294 m2m.name = s.value();
10295 return Ok(());
10296 }
10297 if inner.path.is_ident("to") {
10298 let s: LitStr = inner.value()?.parse()?;
10299 m2m.to = s.value();
10300 return Ok(());
10301 }
10302 if inner.path.is_ident("through") {
10303 let s: LitStr = inner.value()?.parse()?;
10304 m2m.through = s.value();
10305 return Ok(());
10306 }
10307 if inner.path.is_ident("src") {
10308 let s: LitStr = inner.value()?.parse()?;
10309 m2m.src = s.value();
10310 return Ok(());
10311 }
10312 if inner.path.is_ident("dst") {
10313 let s: LitStr = inner.value()?.parse()?;
10314 m2m.dst = s.value();
10315 return Ok(());
10316 }
10317 if inner.path.is_ident("auto_create") {
10318 let lit: syn::LitBool = inner.value()?.parse()?;
10319 m2m.auto_create = lit.value;
10320 return Ok(());
10321 }
10322 Err(inner.error("unknown m2m attribute (supported: `name`, `to`, `through`, `src`, `dst`, `auto_create`)"))
10323 })?;
10324 if m2m.name.is_empty() {
10325 return Err(meta.error("m2m requires `name = \"...\"`"));
10326 }
10327 if m2m.to.is_empty() {
10328 return Err(meta.error("m2m requires `to = \"...\"`"));
10329 }
10330 if m2m.through.is_empty() {
10331 return Err(meta.error("m2m requires `through = \"...\"`"));
10332 }
10333 if m2m.src.is_empty() {
10334 return Err(meta.error("m2m requires `src = \"...\"`"));
10335 }
10336 if m2m.dst.is_empty() {
10337 return Err(meta.error("m2m requires `dst = \"...\"`"));
10338 }
10339 out.m2m.push(m2m);
10340 return Ok(());
10341 }
10342 if meta.path.is_ident("generic_m2m") {
10343 let mut gm = GenericM2MAttr {
10344 name: String::new(),
10345 through: String::new(),
10346 pk_column: String::new(),
10347 ct_column: String::new(),
10348 related_column: String::new(),
10349 };
10350 meta.parse_nested_meta(|inner| {
10351 let field = |inner: &syn::meta::ParseNestedMeta| -> syn::Result<String> {
10352 let s: LitStr = inner.value()?.parse()?;
10353 Ok(s.value())
10354 };
10355 if inner.path.is_ident("name") {
10356 gm.name = field(&inner)?;
10357 return Ok(());
10358 }
10359 if inner.path.is_ident("through") {
10360 gm.through = field(&inner)?;
10361 return Ok(());
10362 }
10363 if inner.path.is_ident("pk_column") {
10364 gm.pk_column = field(&inner)?;
10365 return Ok(());
10366 }
10367 if inner.path.is_ident("ct_column") {
10368 gm.ct_column = field(&inner)?;
10369 return Ok(());
10370 }
10371 if inner.path.is_ident("related_column") {
10372 gm.related_column = field(&inner)?;
10373 return Ok(());
10374 }
10375 Err(inner.error("unknown generic_m2m attribute (supported: `name`, `through`, `pk_column`, `ct_column`, `related_column`)"))
10376 })?;
10377 for (val, label) in [
10378 (&gm.name, "name"),
10379 (&gm.through, "through"),
10380 (&gm.pk_column, "pk_column"),
10381 (&gm.ct_column, "ct_column"),
10382 (&gm.related_column, "related_column"),
10383 ] {
10384 if val.is_empty() {
10385 return Err(meta.error(format!("generic_m2m requires `{label} = \"...\"`")));
10386 }
10387 }
10388 out.generic_m2m.push(gm);
10389 return Ok(());
10390 }
10391 Err(unknown_key_error(&meta, "container", CONTAINER_ATTR_KEYS))
10392 })?;
10393 }
10394 Ok(out)
10395}
10396
10397fn split_field_list(raw: &str) -> Vec<String> {
10401 raw.split(',')
10402 .map(str::trim)
10403 .filter(|s| !s.is_empty())
10404 .map(str::to_owned)
10405 .collect()
10406}
10407
10408fn parse_together_attr(
10416 meta: &syn::meta::ParseNestedMeta<'_>,
10417 attr: &str,
10418 written: &str,
10419) -> syn::Result<(Vec<String>, Option<String>)> {
10420 if meta.input.peek(syn::Token![=]) {
10423 let cols_lit: LitStr = meta.value()?.parse()?;
10424 let columns = split_field_list(&cols_lit.value());
10425 check_together_columns(meta, attr, written, &columns)?;
10426 return Ok((columns, None));
10427 }
10428 let mut columns: Option<Vec<String>> = None;
10429 let mut name: Option<String> = None;
10430 meta.parse_nested_meta(|inner| {
10431 if inner.path.is_ident("columns") {
10432 let s: LitStr = inner.value()?.parse()?;
10433 columns = Some(split_field_list(&s.value()));
10434 return Ok(());
10435 }
10436 if inner.path.is_ident("name") {
10437 let s: LitStr = inner.value()?.parse()?;
10438 name = Some(s.value());
10439 return Ok(());
10440 }
10441 Err(inner.error("unknown sub-attribute (supported: `columns`, `name`)"))
10442 })?;
10443 let columns = columns.ok_or_else(|| {
10444 meta.error(format!(
10445 "{attr}(...) requires a `columns = \"col1, col2\"` argument",
10446 ))
10447 })?;
10448 check_together_columns(meta, attr, written, &columns)?;
10449 Ok((columns, name))
10450}
10451
10452fn check_together_columns(
10453 meta: &syn::meta::ParseNestedMeta<'_>,
10454 attr: &str,
10455 written: &str,
10456 columns: &[String],
10457) -> syn::Result<()> {
10458 if columns.len() < 2 {
10459 let flag = if attr == "unique_together" {
10460 "unique"
10461 } else {
10462 "index"
10463 };
10464 let single = format!("#[{written}({flag})] on the field");
10465 return Err(meta.error(format!(
10466 "{attr} expects two or more columns; for a single-column equivalent use {single}",
10467 )));
10468 }
10469 Ok(())
10470}
10471
10472fn parse_fieldset_list(raw: &str) -> Vec<(String, Vec<String>)> {
10481 raw.split('|')
10482 .map(str::trim)
10483 .filter(|s| !s.is_empty())
10484 .map(|section| {
10485 let (title, rest) = match section.split_once(':') {
10487 Some((title, rest)) if !title.contains(',') => (title.trim().to_owned(), rest),
10488 _ => (String::new(), section),
10489 };
10490 let fields = split_field_list(rest);
10491 (title, fields)
10492 })
10493 .collect()
10494}
10495
10496fn parse_prepopulated_list(raw: &str) -> Vec<(String, Vec<String>)> {
10501 raw.split(',')
10502 .map(str::trim)
10503 .filter(|s| !s.is_empty())
10504 .filter_map(|entry| {
10505 let (target, sources_raw) = entry.split_once(':')?;
10506 let target = target.trim().to_owned();
10507 if target.is_empty() {
10508 return None;
10509 }
10510 let sources: Vec<String> = sources_raw
10511 .split('+')
10512 .map(|s| s.trim().to_owned())
10513 .filter(|s| !s.is_empty())
10514 .collect();
10515 if sources.is_empty() {
10516 return None;
10517 }
10518 Some((target, sources))
10519 })
10520 .collect()
10521}
10522
10523fn parse_formfield_overrides(raw: &str) -> Vec<(String, String)> {
10529 raw.split(',')
10530 .map(str::trim)
10531 .filter(|s| !s.is_empty())
10532 .filter_map(|entry| {
10533 let (field, widget) = entry.split_once(':')?;
10534 let field = field.trim().to_owned();
10535 let widget = widget.trim().to_owned();
10536 if field.is_empty() || widget.is_empty() {
10537 return None;
10538 }
10539 Some((field, widget))
10540 })
10541 .collect()
10542}
10543
10544fn parse_ordering_list(raw: &str) -> Vec<(String, bool)> {
10547 raw.split(',')
10548 .map(str::trim)
10549 .filter(|s| !s.is_empty())
10550 .map(|spec| {
10551 spec.strip_prefix('-')
10552 .map_or((spec.to_owned(), false), |rest| {
10553 (rest.trim().to_owned(), true)
10554 })
10555 })
10556 .collect()
10557}
10558
10559struct FieldAttrs {
10560 column: Option<String>,
10561 primary_key: bool,
10562 fk: Option<String>,
10563 o2o: Option<String>,
10564 on: Option<String>,
10565 on_delete: Option<String>,
10575 related_name: Option<String>,
10583 max_length: Option<u32>,
10584 vector_dims: Option<u32>,
10588 geometry_srid: Option<u32>,
10592 min: Option<i64>,
10593 max: Option<i64>,
10594 default: Option<String>,
10595 auto_uuid: bool,
10601 default_uuid_v7: bool,
10612 auto_now_add: bool,
10617 auto_now: bool,
10623 soft_delete: bool,
10628 unique: bool,
10631 index: bool,
10635 index_unique: bool,
10636 index_name: Option<String>,
10637 index_method: String,
10640 generated_as: Option<String>,
10646 help_text: Option<String>,
10651 choices: Option<Vec<(String, String)>>,
10659 db_comment: Option<String>,
10665 verbose_name: Option<String>,
10671 editable: bool,
10678 blank: bool,
10682 case_insensitive: bool,
10687 validators: Vec<syn::Ident>,
10693 secret: Option<bool>,
10695}
10696
10697const FIELD_ATTR_KEYS: &[&str] = &[
10699 "column",
10700 "primary_key",
10701 "fk",
10702 "o2o",
10703 "on",
10704 "on_delete",
10705 "related_name",
10706 "max_length",
10707 "vector",
10708 "geometry",
10709 "min",
10710 "max",
10711 "default",
10712 "generated_as",
10713 "help_text",
10714 "choices",
10715 "db_comment",
10716 "verbose_name",
10717 "editable",
10718 "secret",
10719 "blank",
10720 "citext",
10721 "validators",
10722 "auto_uuid",
10723 "default_uuid_v7",
10724 "auto_now_add",
10725 "auto_now",
10726 "soft_delete",
10727 "unique",
10728 "index",
10729];
10730
10731const CONTAINER_ATTR_KEYS: &[&str] = &[
10733 "table",
10734 "display",
10735 "app",
10736 "scope",
10737 "admin",
10738 "manager",
10739 "manager_fn",
10740 "default_order",
10741 "global_scope",
10742 "through",
10743 "reverse_has",
10744 "generic_has",
10745 "audit",
10746 "permissions",
10747 "view",
10748 "managed",
10749 "verbose_name",
10750 "verbose_name_plural",
10751 "db_table_comment",
10752 "proxy",
10753 "order_with_respect_to",
10754 "required_db_features",
10755 "required_db_vendor",
10756 "base_manager_name",
10757 "default_related_name",
10758 "extra_permissions",
10759 "default_permissions",
10760 "get_latest_by",
10761 "unique_together",
10762 "index_together",
10763 "unique_when",
10764 "index_when",
10765 "index",
10766 "check",
10767 "exclude",
10768 "generic_fk",
10769 "fk_composite",
10770 "m2m",
10771 "generic_m2m",
10772];
10773
10774fn parse_flag(meta: &syn::meta::ParseNestedMeta<'_>, key: &str) -> syn::Result<()> {
10777 if meta.input.peek(syn::Token![=]) {
10778 let lit: syn::LitBool = meta.value()?.parse()?;
10779 if !lit.value {
10780 return Err(syn::Error::new(
10781 lit.span(),
10782 format!("`{key} = false` is the default; remove the attribute"),
10783 ));
10784 }
10785 }
10786 Ok(())
10787}
10788
10789fn unknown_key_error(
10791 meta: &syn::meta::ParseNestedMeta<'_>,
10792 what: &str,
10793 keys: &[&str],
10794) -> syn::Error {
10795 let path = &meta.path;
10796 let got = path
10797 .get_ident()
10798 .map_or_else(|| quote!(#path).to_string(), ToString::to_string);
10799 let mut msg = format!("unknown rustango {what} attribute `{got}`");
10800 if let Some(near) = keys
10801 .iter()
10802 .map(|k| (edit_distance(&got, k), *k))
10803 .filter(|(d, k)| *d <= 2.max(k.len() / 4))
10804 .min_by_key(|(d, _)| *d)
10805 .map(|(_, k)| k)
10806 {
10807 msg.push_str(&format!("; did you mean `{near}`?"));
10808 }
10809 msg.push_str(&format!(" Supported: {}", keys.join(", ")));
10810 meta.error(msg)
10811}
10812
10813fn edit_distance(a: &str, b: &str) -> usize {
10815 let b: Vec<char> = b.chars().collect();
10816 let mut prev: Vec<usize> = (0..=b.len()).collect();
10817 for (i, ca) in a.chars().enumerate() {
10818 let mut cur = vec![i + 1];
10819 for (j, cb) in b.iter().enumerate() {
10820 let sub = prev[j] + usize::from(ca != *cb);
10821 cur.push(sub.min(prev[j + 1] + 1).min(cur[j] + 1));
10822 }
10823 prev = cur;
10824 }
10825 prev[b.len()]
10826}
10827
10828fn parse_field_attrs(field: &syn::Field) -> syn::Result<FieldAttrs> {
10829 let mut out = FieldAttrs {
10830 column: None,
10831 primary_key: false,
10832 fk: None,
10833 o2o: None,
10834 on: None,
10835 on_delete: None,
10836 related_name: None,
10837 max_length: None,
10838 vector_dims: None,
10839 geometry_srid: None,
10840 min: None,
10841 max: None,
10842 default: None,
10843 auto_uuid: false,
10844 default_uuid_v7: false,
10845 auto_now_add: false,
10846 auto_now: false,
10847 soft_delete: false,
10848 unique: false,
10849 index: false,
10850 index_unique: false,
10851 index_name: None,
10852 index_method: "btree".to_owned(),
10853 generated_as: None,
10854 help_text: None,
10855 choices: None,
10856 db_comment: None,
10857 verbose_name: None,
10858 editable: true,
10859 blank: false,
10860 case_insensitive: false,
10861 validators: Vec::new(),
10862 secret: None,
10863 };
10864 for attr in model_attrs(&field.attrs)? {
10865 attr.parse_nested_meta(|meta| {
10866 if meta.path.is_ident("column") {
10867 let s: LitStr = meta.value()?.parse()?;
10868 let name = s.value();
10869 validate_sql_identifier(&name, "column", s.span())?;
10870 out.column = Some(name);
10871 return Ok(());
10872 }
10873 if meta.path.is_ident("primary_key") {
10874 parse_flag(&meta, "primary_key")?;
10875 out.primary_key = true;
10876 return Ok(());
10877 }
10878 if meta.path.is_ident("fk") {
10879 let s: LitStr = meta.value()?.parse()?;
10880 out.fk = Some(s.value());
10881 return Ok(());
10882 }
10883 if meta.path.is_ident("o2o") {
10884 let s: LitStr = meta.value()?.parse()?;
10885 out.o2o = Some(s.value());
10886 return Ok(());
10887 }
10888 if meta.path.is_ident("on") {
10889 let s: LitStr = meta.value()?.parse()?;
10890 out.on = Some(s.value());
10891 return Ok(());
10892 }
10893 if meta.path.is_ident("on_delete") {
10894 let s: LitStr = meta.value()?.parse()?;
10895 let raw = s.value();
10896 let normalized = raw.trim().to_ascii_lowercase();
10897 match normalized.as_str() {
10900 "cascade" | "restrict" | "set_null" | "set_default" | "no_action" => {}
10901 _ => {
10902 return Err(syn::Error::new(
10903 s.span(),
10904 format!(
10905 "unknown on_delete action `{raw}`; expected one of \
10906 `cascade`, `restrict`, `set_null`, `set_default`, `no_action`"
10907 ),
10908 ));
10909 }
10910 }
10911 out.on_delete = Some(normalized);
10912 return Ok(());
10913 }
10914 if meta.path.is_ident("related_name") {
10915 let s: LitStr = meta.value()?.parse()?;
10916 let raw = s.value();
10917 if raw.trim().is_empty() {
10918 return Err(syn::Error::new(
10919 s.span(),
10920 "`related_name` must be a non-empty identifier",
10921 ));
10922 }
10923 let snake = raw
10928 .chars()
10929 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
10930 && !raw.starts_with(char::is_numeric);
10931 if !snake && raw != NO_REVERSE_ACCESSOR {
10932 return Err(syn::Error::new(
10933 s.span(),
10934 "`related_name` must be snake_case ASCII (lowercase letters, \
10935 digits, underscores; no leading digit), or \"+\" for none",
10936 ));
10937 }
10938 out.related_name = Some(raw);
10939 return Ok(());
10940 }
10941 if meta.path.is_ident("max_length") {
10942 let lit: syn::LitInt = meta.value()?.parse()?;
10943 out.max_length = Some(lit.base10_parse::<u32>()?);
10944 return Ok(());
10945 }
10946 if meta.path.is_ident("vector") {
10950 meta.parse_nested_meta(|inner| {
10951 if inner.path.is_ident("dims") {
10952 let lit: syn::LitInt = inner.value()?.parse()?;
10953 out.vector_dims = Some(lit.base10_parse::<u32>()?);
10954 return Ok(());
10955 }
10956 Err(inner.error("unknown `vector` attribute (supported: `dims`)"))
10957 })?;
10958 return Ok(());
10959 }
10960 if meta.path.is_ident("geometry") {
10963 meta.parse_nested_meta(|inner| {
10964 if inner.path.is_ident("srid") {
10965 let lit: syn::LitInt = inner.value()?.parse()?;
10966 out.geometry_srid = Some(lit.base10_parse::<u32>()?);
10967 return Ok(());
10968 }
10969 Err(inner.error("unknown `geometry` attribute (supported: `srid`)"))
10970 })?;
10971 return Ok(());
10972 }
10973 if meta.path.is_ident("min") {
10974 out.min = Some(parse_signed_i64(&meta)?);
10975 return Ok(());
10976 }
10977 if meta.path.is_ident("max") {
10978 out.max = Some(parse_signed_i64(&meta)?);
10979 return Ok(());
10980 }
10981 if meta.path.is_ident("default") {
10982 let s: LitStr = meta.value()?.parse()?;
10983 out.default = Some(s.value());
10984 return Ok(());
10985 }
10986 if meta.path.is_ident("generated_as") {
10987 let s: LitStr = meta.value()?.parse()?;
10988 out.generated_as = Some(s.value());
10989 return Ok(());
10990 }
10991 if meta.path.is_ident("help_text") {
10992 let s: LitStr = meta.value()?.parse()?;
10993 out.help_text = Some(s.value());
10994 return Ok(());
10995 }
10996 if meta.path.is_ident("choices") {
10997 let s: LitStr = meta.value()?.parse()?;
10998 let raw = s.value();
10999 let mut pairs: Vec<(String, String)> = Vec::new();
11000 for chunk in raw.split(',') {
11001 let chunk = chunk.trim();
11002 if chunk.is_empty() {
11003 continue;
11004 }
11005 let (value, label) = match chunk.split_once(':') {
11006 Some((v, l)) => (v.trim().to_owned(), l.trim().to_owned()),
11007 None => (chunk.to_owned(), chunk.to_owned()),
11008 };
11009 if value.is_empty() {
11010 return Err(syn::Error::new(
11011 s.span(),
11012 "`choices` entry has empty value before `:`",
11013 ));
11014 }
11015 pairs.push((value, label));
11016 }
11017 if pairs.is_empty() {
11018 return Err(syn::Error::new(
11019 s.span(),
11020 "`choices = \"…\"` must contain at least one value",
11021 ));
11022 }
11023 out.choices = Some(pairs);
11024 return Ok(());
11025 }
11026 if meta.path.is_ident("db_comment") {
11027 let s: LitStr = meta.value()?.parse()?;
11028 out.db_comment = Some(s.value());
11029 return Ok(());
11030 }
11031 if meta.path.is_ident("verbose_name") {
11032 let s: LitStr = meta.value()?.parse()?;
11033 out.verbose_name = Some(s.value());
11034 return Ok(());
11035 }
11036 if meta.path.is_ident("secret") {
11037 out.secret = Some(if let Ok(v) = meta.value() {
11039 v.parse::<syn::LitBool>()?.value
11040 } else {
11041 true
11042 });
11043 return Ok(());
11044 }
11045 if meta.path.is_ident("editable") {
11046 if let Ok(v) = meta.value() {
11051 let lit: syn::LitBool = v.parse()?;
11052 out.editable = lit.value;
11053 } else {
11054 out.editable = true;
11055 }
11056 return Ok(());
11057 }
11058 if meta.path.is_ident("blank") {
11059 if let Ok(v) = meta.value() {
11063 let lit: syn::LitBool = v.parse()?;
11064 out.blank = lit.value;
11065 } else {
11066 out.blank = true;
11067 }
11068 return Ok(());
11069 }
11070 if meta.path.is_ident("citext") {
11071 if let Ok(v) = meta.value() {
11080 let lit: syn::LitBool = v.parse()?;
11081 out.case_insensitive = lit.value;
11082 } else {
11083 out.case_insensitive = true;
11084 }
11085 return Ok(());
11086 }
11087 if meta.path.is_ident("validators") {
11088 let s: LitStr = meta.value()?.parse()?;
11089 let raw = s.value();
11090 out.validators = raw
11091 .split(',')
11092 .map(str::trim)
11093 .filter(|s| !s.is_empty())
11094 .map(|name| {
11095 let mut ident = syn::parse_str::<syn::Ident>(name).map_err(|_| {
11096 syn::Error::new(s.span(), format!("unknown validator `{name}`"))
11097 })?;
11098 ident.set_span(s.span());
11099 Ok(ident)
11100 })
11101 .collect::<syn::Result<_>>()?;
11102 if out.validators.is_empty() {
11103 return Err(syn::Error::new(
11104 s.span(),
11105 "`validators = \"…\"` must list at least one name",
11106 ));
11107 }
11108 return Ok(());
11109 }
11110 if meta.path.is_ident("auto_uuid") {
11111 parse_flag(&meta, "auto_uuid")?;
11112 out.auto_uuid = true;
11113 out.primary_key = true;
11117 if out.default.is_none() {
11118 out.default = Some("gen_random_uuid()".into());
11119 }
11120 return Ok(());
11121 }
11122 if meta.path.is_ident("default_uuid_v7") {
11123 parse_flag(&meta, "default_uuid_v7")?;
11124 out.default_uuid_v7 = true;
11130 out.primary_key = true;
11131 return Ok(());
11132 }
11133 if meta.path.is_ident("auto_now_add") {
11134 parse_flag(&meta, "auto_now_add")?;
11135 out.auto_now_add = true;
11136 if out.default.is_none() {
11137 out.default = Some("now()".into());
11138 }
11139 return Ok(());
11140 }
11141 if meta.path.is_ident("auto_now") {
11142 parse_flag(&meta, "auto_now")?;
11143 out.auto_now = true;
11144 if out.default.is_none() {
11145 out.default = Some("now()".into());
11146 }
11147 return Ok(());
11148 }
11149 if meta.path.is_ident("soft_delete") {
11150 parse_flag(&meta, "soft_delete")?;
11151 out.soft_delete = true;
11152 return Ok(());
11153 }
11154 if meta.path.is_ident("unique") {
11155 parse_flag(&meta, "unique")?;
11156 out.unique = true;
11157 return Ok(());
11158 }
11159 if meta.path.is_ident("index") {
11160 parse_flag(&meta, "index")?;
11161 out.index = true;
11162 if meta.input.peek(syn::token::Paren) {
11164 meta.parse_nested_meta(|inner| {
11165 if inner.path.is_ident("unique") {
11166 out.index_unique = true;
11167 return Ok(());
11168 }
11169 if inner.path.is_ident("name") {
11170 let s: LitStr = inner.value()?.parse()?;
11171 out.index_name = Some(s.value());
11172 return Ok(());
11173 }
11174 if inner.path.is_ident("method") {
11175 let s: LitStr = inner.value()?.parse()?;
11176 let v = s.value();
11177 match v.as_str() {
11178 "btree" | "gin" | "gist" | "brin" | "spgist" | "hash" | "bloom" => {
11179 out.index_method = v;
11180 }
11181 other => {
11182 return Err(inner.error(format!(
11183 "unknown index method `{other}` (supported: btree, gin, gist, brin, spgist, hash, bloom)",
11184 )));
11185 }
11186 }
11187 return Ok(());
11188 }
11189 Err(inner.error(
11190 "unknown index sub-attribute (supported: `unique`, `name`, `method`)",
11191 ))
11192 })?;
11193 }
11194 return Ok(());
11195 }
11196 Err(unknown_key_error(&meta, "field", FIELD_ATTR_KEYS))
11197 })?;
11198 }
11199 Ok(out)
11200}
11201
11202fn parse_signed_i64(meta: &syn::meta::ParseNestedMeta<'_>) -> syn::Result<i64> {
11204 let expr: syn::Expr = meta.value()?.parse()?;
11205 match expr {
11206 syn::Expr::Lit(syn::ExprLit {
11207 lit: syn::Lit::Int(lit),
11208 ..
11209 }) => lit.base10_parse::<i64>(),
11210 syn::Expr::Unary(syn::ExprUnary {
11211 op: syn::UnOp::Neg(_),
11212 expr,
11213 ..
11214 }) => {
11215 if let syn::Expr::Lit(syn::ExprLit {
11216 lit: syn::Lit::Int(lit),
11217 ..
11218 }) = *expr
11219 {
11220 let v: i64 = lit.base10_parse()?;
11221 Ok(-v)
11222 } else {
11223 Err(syn::Error::new_spanned(expr, "expected integer literal"))
11224 }
11225 }
11226 other => Err(syn::Error::new_spanned(
11227 other,
11228 "expected integer literal (signed)",
11229 )),
11230 }
11231}
11232
11233struct FieldInfo<'a> {
11234 ident: &'a syn::Ident,
11235 column: String,
11236 primary_key: bool,
11237 auto: bool,
11241 value_ty: &'a Type,
11244 field_type_tokens: TokenStream2,
11246 schema: TokenStream2,
11247 from_row_init: TokenStream2,
11248 from_aliased_row_init: TokenStream2,
11254 from_row_init_my: TokenStream2,
11257 from_aliased_row_init_my: TokenStream2,
11258 uuid_column: bool,
11260 fk_inner: Option<Type>,
11264 fk_pk_kind: DetectedKind,
11270 nullable: bool,
11278 auto_now: bool,
11284 auto_now_add: bool,
11290 soft_delete: bool,
11295 generated_as: Option<String>,
11300 default_uuid_v7: bool,
11307 related_name: Option<String>,
11312}
11313
11314fn validate_table_name(name: &str, span: proc_macro2::Span) -> syn::Result<()> {
11328 validate_sql_identifier(name, "table", span)
11329}
11330
11331fn validate_sql_identifier(name: &str, kind: &str, span: proc_macro2::Span) -> syn::Result<()> {
11336 if name.is_empty() {
11337 return Err(syn::Error::new(
11338 span,
11339 format!("`{kind} = \"\"` is not a valid SQL identifier"),
11340 ));
11341 }
11342 let mut chars = name.chars();
11343 let first = chars.next().unwrap();
11344 if !(first.is_ascii_alphabetic() || first == '_') {
11345 return Err(syn::Error::new(
11346 span,
11347 format!("{kind} name `{name}` must start with a letter or underscore (got {first:?})"),
11348 ));
11349 }
11350 for c in chars {
11351 if !(c.is_ascii_alphanumeric() || c == '_') {
11352 return Err(syn::Error::new(
11353 span,
11354 format!(
11355 "{kind} name `{name}` contains invalid character {c:?} — \
11356 SQL identifiers must match `[a-zA-Z_][a-zA-Z0-9_]*`. \
11357 Hyphens in particular break FK / index name derivation \
11358 downstream; use underscores instead (e.g. `{}`)",
11359 name.replace(|x: char| !x.is_ascii_alphanumeric() && x != '_', "_"),
11360 ),
11361 ));
11362 }
11363 }
11364 Ok(())
11365}
11366
11367fn process_field<'a>(field: &'a syn::Field, table: &str) -> syn::Result<FieldInfo<'a>> {
11368 let root = rustango_root();
11369 let attrs = parse_field_attrs(field)?;
11370 let ident = field
11371 .ident
11372 .as_ref()
11373 .ok_or_else(|| syn::Error::new(field.span(), "tuple structs are not supported"))?;
11374 let name = ident_name(ident);
11375 let column = attrs.column.clone().unwrap_or_else(|| name.clone());
11376 let primary_key = attrs.primary_key;
11377 let DetectedType {
11378 kind,
11379 nullable,
11380 auto: detected_auto,
11381 fk_inner,
11382 } = detect_type(&field.ty)?;
11383 check_bound_compatibility(field, &attrs, kind)?;
11384 let auto = detected_auto;
11385 if attrs.auto_uuid {
11391 if kind != DetectedKind::Uuid {
11392 return Err(syn::Error::new_spanned(
11393 field,
11394 "`#[rustango(auto_uuid)]` requires the field type to be \
11395 `Auto<uuid::Uuid>`",
11396 ));
11397 }
11398 if !detected_auto {
11399 return Err(syn::Error::new_spanned(
11400 field,
11401 "`#[rustango(auto_uuid)]` requires the field type to be \
11402 wrapped in `Auto<...>` so the macro skips the column on \
11403 INSERT and the DB DEFAULT (`gen_random_uuid()`) fires",
11404 ));
11405 }
11406 }
11407 if attrs.default_uuid_v7 {
11408 if kind != DetectedKind::Uuid {
11409 return Err(syn::Error::new_spanned(
11410 field,
11411 "`#[rustango(default_uuid_v7)]` requires the field type to be \
11412 `Auto<uuid::Uuid>`",
11413 ));
11414 }
11415 if !detected_auto {
11416 return Err(syn::Error::new_spanned(
11417 field,
11418 "`#[rustango(default_uuid_v7)]` requires the field type to be \
11419 wrapped in `Auto<...>` so the macro can detect the \
11420 unset-vs-set state and fill a fresh UUIDv7 before INSERT",
11421 ));
11422 }
11423 if attrs.auto_uuid {
11424 return Err(syn::Error::new_spanned(
11425 field,
11426 "`#[rustango(default_uuid_v7)]` is mutually exclusive with \
11427 `#[rustango(auto_uuid)]` — the former generates the UUID \
11428 Rust-side, the latter relies on the DB's `gen_random_uuid()`. \
11429 Pick one.",
11430 ));
11431 }
11432 }
11433 if attrs.auto_now_add || attrs.auto_now {
11434 if kind != DetectedKind::DateTime {
11435 return Err(syn::Error::new_spanned(
11436 field,
11437 "`#[rustango(auto_now_add)]` / `#[rustango(auto_now)]` require \
11438 the field type to be `Auto<chrono::DateTime<chrono::Utc>>`",
11439 ));
11440 }
11441 if !detected_auto {
11442 return Err(syn::Error::new_spanned(
11443 field,
11444 "`#[rustango(auto_now_add)]` / `#[rustango(auto_now)]` require \
11445 the field type to be wrapped in `Auto<...>` so the macro skips \
11446 the column on INSERT and the DB DEFAULT (`now()`) fires",
11447 ));
11448 }
11449 }
11450 if attrs.soft_delete && !(kind == DetectedKind::DateTime && nullable) {
11451 return Err(syn::Error::new_spanned(
11452 field,
11453 "`#[rustango(soft_delete)]` requires the field type to be \
11454 `Option<chrono::DateTime<chrono::Utc>>`",
11455 ));
11456 }
11457 let is_mixin_auto =
11458 attrs.auto_uuid || attrs.default_uuid_v7 || attrs.auto_now_add || attrs.auto_now;
11459 if detected_auto && !primary_key && !is_mixin_auto {
11460 return Err(syn::Error::new_spanned(
11461 field,
11462 "`Auto<T>` is only valid on a `#[rustango(primary_key)]` field, \
11463 or on a field carrying one of `auto_uuid`, `auto_now_add`, or \
11464 `auto_now`",
11465 ));
11466 }
11467 if detected_auto && attrs.default.is_some() && !is_mixin_auto {
11468 return Err(syn::Error::new_spanned(
11469 field,
11470 "`#[rustango(default = \"…\")]` is redundant on an `Auto<T>` field — \
11471 SERIAL / BIGSERIAL already supplies a default sequence.",
11472 ));
11473 }
11474 if fk_inner.is_some() && primary_key {
11475 return Err(syn::Error::new_spanned(
11476 field,
11477 "`ForeignKey<T>` is not allowed on a primary-key field — \
11478 a row's PK is its own identity, not a reference to a parent.",
11479 ));
11480 }
11481 if attrs.generated_as.is_some() {
11482 if primary_key {
11483 return Err(syn::Error::new_spanned(
11484 field,
11485 "`#[rustango(generated_as = \"…\")]` is not allowed on a \
11486 primary-key field — a PK must be writable so the row \
11487 has an identity at INSERT time.",
11488 ));
11489 }
11490 if attrs.default.is_some() {
11491 return Err(syn::Error::new_spanned(
11492 field,
11493 "`#[rustango(generated_as = \"…\")]` cannot combine with \
11494 `default = \"…\"` — Postgres rejects DEFAULT on \
11495 generated columns. The expression IS the default.",
11496 ));
11497 }
11498 if detected_auto {
11499 return Err(syn::Error::new_spanned(
11500 field,
11501 "`#[rustango(generated_as = \"…\")]` is not allowed on \
11502 an `Auto<T>` field — generated columns are computed \
11503 by the DB, not server-assigned via a sequence. Use a \
11504 plain Rust type (e.g. `f64`).",
11505 ));
11506 }
11507 if fk_inner.is_some() {
11508 return Err(syn::Error::new_spanned(
11509 field,
11510 "`#[rustango(generated_as = \"…\")]` is not allowed on a \
11511 ForeignKey field.",
11512 ));
11513 }
11514 }
11515 let relation = relation_tokens(field, &attrs, fk_inner, table)?;
11516 let column_lit = column.as_str();
11517 let field_type_tokens = if kind == DetectedKind::Vector {
11522 let root = rustango_root();
11523 let dims = attrs.vector_dims.unwrap_or(0);
11524 quote!(#root::core::FieldType::Vector(#dims))
11525 } else if kind == DetectedKind::Geometry {
11526 let root = rustango_root();
11530 let srid = attrs.geometry_srid.unwrap_or(0);
11531 quote!(#root::core::FieldType::Geometry(#srid))
11532 } else {
11533 kind.variant_tokens()
11534 };
11535 let max_length = optional_u32(attrs.max_length);
11536 let min = optional_i64(attrs.min);
11537 let max = optional_i64(attrs.max);
11538 let default = optional_str(attrs.default.as_deref());
11539
11540 let unique = attrs.unique;
11541 let generated_as = optional_str(attrs.generated_as.as_deref());
11542 let help_text = optional_str(attrs.help_text.as_deref());
11543 let choices = optional_choices(attrs.choices.as_deref());
11544 let db_comment = optional_str(attrs.db_comment.as_deref());
11545 let verbose_name = optional_str(attrs.verbose_name.as_deref());
11546 let editable = attrs.editable;
11547 let secret = match attrs.secret {
11548 Some(v) => quote!(::core::option::Option::Some(#v)),
11549 None => quote!(::core::option::Option::None),
11550 };
11551 let blank = attrs.blank;
11552 let case_insensitive = attrs.case_insensitive;
11553 let validators = &attrs.validators;
11555 if attrs.on_delete.is_some() && attrs.fk.is_none() && attrs.o2o.is_none() && fk_inner.is_none()
11556 {
11557 return Err(syn::Error::new_spanned(
11558 field,
11559 "`#[rustango(on_delete = \"…\")]` requires a `ForeignKey<T>` field \
11560 or `fk = \"<table>\"` / `o2o = \"<table>\"` on the same field — it \
11561 has no meaning on a non-FK column.",
11562 ));
11563 }
11564 if attrs.on_delete.as_deref() == Some("set_null") && !nullable {
11565 return Err(syn::Error::new_spanned(
11566 field,
11567 "`on_delete = \"set_null\"` needs a nullable column: use \
11568 `Option<ForeignKey<T>>` or `Option<…>` (spelled out; a type \
11569 alias for `Option` is not seen).",
11570 ));
11571 }
11572 let fk_on_delete = match attrs.on_delete.as_deref() {
11573 None => quote!(::core::option::Option::None),
11574 Some(action) => {
11575 let variant = match action {
11576 "cascade" => quote!(Cascade),
11577 "restrict" => quote!(Restrict),
11578 "set_null" => quote!(SetNull),
11579 "set_default" => quote!(SetDefault),
11580 "no_action" => quote!(NoAction),
11581 other => unreachable!("on_delete `{other}` should have been rejected at parse"),
11583 };
11584 quote!(::core::option::Option::Some(
11585 #root::core::OnDeleteAction::#variant
11586 ))
11587 }
11588 };
11589 let auto_now_flag = attrs.auto_now;
11590 let cast = match cast_type(&field.ty) {
11591 Some(ty) => quote!(::core::option::Option::Some(<#ty>::FIELD_CAST)),
11592 None => quote!(::core::option::Option::None),
11593 };
11594 let sealed_guard = match (attrs.secret, cast_type(&field.ty)) {
11596 (Some(false), Some(ty)) => quote! {
11597 const _: () = ::core::assert!(
11598 !::core::matches!(<#ty>::FIELD_CAST, #root::core::FieldCast::Sealed),
11599 "`#[rustango(secret = false)]` on a sealed cast: it is always a credential",
11600 );
11601 },
11602 _ => quote!(),
11603 };
11604 let schema = quote! {{
11605 #sealed_guard
11606 #root::core::FieldSchema::from_parts(#root::core::FieldSchemaParts {
11607 name: #name,
11608 column: #column_lit,
11609 ty: #field_type_tokens,
11610 nullable: #nullable,
11611 primary_key: #primary_key,
11612 relation: #relation,
11613 max_length: #max_length,
11614 min: #min,
11615 max: #max,
11616 default: #default,
11617 auto: #auto,
11618 auto_now: #auto_now_flag,
11619 unique: #unique,
11620 generated_as: #generated_as,
11621 help_text: #help_text,
11622 choices: #choices,
11623 db_comment: #db_comment,
11624 verbose_name: #verbose_name,
11625 editable: #editable,
11626 blank: #blank,
11627 case_insensitive: #case_insensitive,
11628 fk_on_delete: #fk_on_delete,
11629 validators: &[ #( #root::core::__validator_names::#validators ),* ],
11630 cast: #cast,
11631 secret: #secret,
11632 })
11633 }};
11634
11635 let decimal_column = kind == DetectedKind::Decimal && !detected_auto && fk_inner.is_none();
11637 let (from_row_init, from_aliased_row_init) = if decimal_column {
11638 (
11639 quote! { #ident: #root::sql::try_get_flat(row, #column_lit)? },
11640 quote! {
11641 #ident: #root::sql::try_get_flat(
11642 row,
11643 #root::core::joins::joined_label(prefix, #column_lit).as_str(),
11644 )?
11645 },
11646 )
11647 } else {
11648 (
11649 quote! { #ident: #root::sql::sqlx::Row::try_get(row, #column_lit)? },
11650 quote! {
11651 #ident: #root::sql::sqlx::Row::try_get(
11652 row,
11653 #root::core::joins::joined_label(prefix, #column_lit).as_str(),
11654 )?
11655 },
11656 )
11657 };
11658 let uuid_column = kind == DetectedKind::Uuid && !detected_auto && fk_inner.is_none();
11661 let (from_row_init_my, from_aliased_row_init_my) = if uuid_column {
11662 (
11663 quote! { #ident: #root::sql::try_get_flat_my(row, #column_lit)? },
11664 quote! {
11665 #ident: #root::sql::try_get_flat_my(
11666 row,
11667 #root::core::joins::joined_label(prefix, #column_lit).as_str(),
11668 )?
11669 },
11670 )
11671 } else {
11672 (from_row_init.clone(), from_aliased_row_init.clone())
11673 };
11674
11675 Ok(FieldInfo {
11676 ident,
11677 column,
11678 primary_key,
11679 auto,
11680 value_ty: &field.ty,
11681 field_type_tokens,
11682 schema,
11683 from_row_init,
11684 from_aliased_row_init,
11685 from_row_init_my,
11686 from_aliased_row_init_my,
11687 uuid_column,
11688 fk_inner: fk_inner.cloned(),
11689 fk_pk_kind: kind,
11690 nullable,
11691 auto_now: attrs.auto_now,
11692 auto_now_add: attrs.auto_now_add,
11693 soft_delete: attrs.soft_delete,
11694 generated_as: attrs.generated_as.clone(),
11695 default_uuid_v7: attrs.default_uuid_v7,
11696 related_name: attrs.related_name.clone(),
11697 })
11698}
11699
11700fn check_bound_compatibility(
11701 field: &syn::Field,
11702 attrs: &FieldAttrs,
11703 kind: DetectedKind,
11704) -> syn::Result<()> {
11705 if attrs.max_length.is_some() && kind != DetectedKind::String {
11706 return Err(syn::Error::new_spanned(
11707 field,
11708 "`max_length` is only valid on `String` fields (or `Option<String>`)",
11709 ));
11710 }
11711 if attrs.case_insensitive && kind != DetectedKind::String {
11713 return Err(syn::Error::new_spanned(
11714 field,
11715 "`citext` is only valid on `String` fields (or `Option<String>`)",
11716 ));
11717 }
11718 if attrs.vector_dims.is_some() && kind != DetectedKind::Vector {
11719 return Err(syn::Error::new_spanned(
11720 field,
11721 "`vector(dims = …)` is only valid on a pgvector `Vector` field",
11722 ));
11723 }
11724 if attrs.geometry_srid.is_some() && kind != DetectedKind::Geometry {
11725 return Err(syn::Error::new_spanned(
11726 field,
11727 "`geometry(srid = …)` is only valid on a PostGIS `Point` field",
11728 ));
11729 }
11730 if attrs.choices.is_some() && kind != DetectedKind::String {
11731 return Err(syn::Error::new_spanned(
11732 field,
11733 "`choices` is only valid on `String` fields (or `Option<String>`) — \
11734 integer-valued enumerations should be modeled with a Rust enum and \
11735 custom (de)serializer for now",
11736 ));
11737 }
11738 if !attrs.validators.is_empty() && kind != DetectedKind::String {
11740 return Err(syn::Error::new_spanned(
11741 field,
11742 "`validators` is only valid on `String` fields (or `Option<String>`)",
11743 ));
11744 }
11745 if (attrs.min.is_some() || attrs.max.is_some()) && !kind.is_integer() {
11746 return Err(syn::Error::new_spanned(
11747 field,
11748 "`min` / `max` are only valid on integer fields (`i32`, `i64`, optionally Option-wrapped)",
11749 ));
11750 }
11751 if let (Some(min), Some(max)) = (attrs.min, attrs.max) {
11752 if min > max {
11753 return Err(syn::Error::new_spanned(
11754 field,
11755 format!("`min` ({min}) is greater than `max` ({max})"),
11756 ));
11757 }
11758 }
11759 if let Some(default) = &attrs.default {
11760 check_default_expr(default, kind).map_err(|msg| syn::Error::new_spanned(field, msg))?;
11761 }
11762 Ok(())
11763}
11764
11765const DEFAULT_KEYWORDS: &[&str] = &[
11768 "NULL",
11769 "TRUE",
11770 "FALSE",
11771 "CURRENT_TIMESTAMP",
11772 "CURRENT_DATE",
11773 "CURRENT_TIME",
11774 "LOCALTIMESTAMP",
11775 "LOCALTIME",
11776];
11777
11778fn check_default_expr(default: &str, kind: DetectedKind) -> Result<(), String> {
11782 let expr = default.trim();
11783 let is_word = expr.starts_with(|c: char| c.is_ascii_alphabetic() || c == '_')
11784 && expr
11785 .chars()
11786 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == ' ');
11787 let upper = expr.to_ascii_uppercase();
11788 if is_word && ["CURRENT_USER", "SESSION_USER", "USER"].contains(&upper.as_str()) {
11789 return Err(format!(
11790 "`default = \"{expr}\"` works on Postgres only: MySQL refuses it as a default \
11791 and SQLite stores the text `{expr}`; set the value in code instead"
11792 ));
11793 }
11794 if is_word && !DEFAULT_KEYWORDS.contains(&upper.as_str()) {
11795 return Err(format!(
11796 "`default = \"{expr}\"` is raw SQL, so `{expr}` is read as a column name; \
11797 for the text '{expr}' write `default = \"'{expr}'\"`"
11798 ));
11799 }
11800 let numeric = kind.is_integer()
11801 || matches!(
11802 kind,
11803 DetectedKind::F32 | DetectedKind::F64 | DetectedKind::Decimal
11804 );
11805 if numeric {
11806 let unquoted = expr
11807 .strip_prefix('\'')
11808 .and_then(|e| e.strip_suffix('\''))
11809 .unwrap_or(expr);
11810 let is_number = if kind.is_integer() {
11811 unquoted.parse::<i64>().is_ok()
11812 } else {
11813 unquoted.parse::<f64>().is_ok_and(f64::is_finite)
11814 };
11815 let is_literal = is_word || expr.is_empty() || expr.starts_with('\'');
11818 if is_literal && !is_number && upper != "NULL" {
11819 return Err(format!(
11820 "`default = \"{expr}\"` is not a number, but the field is numeric"
11821 ));
11822 }
11823 }
11824 Ok(())
11825}
11826
11827fn const_slice(ty: &TokenStream2, items: impl Iterator<Item = TokenStream2>) -> TokenStream2 {
11830 quote!({
11831 const ITEMS: &[#ty] = &[ #(#items),* ];
11832 ITEMS
11833 })
11834}
11835
11836fn optional_u32(value: Option<u32>) -> TokenStream2 {
11837 if let Some(v) = value {
11838 quote!(::core::option::Option::Some(#v))
11839 } else {
11840 quote!(::core::option::Option::None)
11841 }
11842}
11843
11844fn optional_i64(value: Option<i64>) -> TokenStream2 {
11845 if let Some(v) = value {
11846 quote!(::core::option::Option::Some(#v))
11847 } else {
11848 quote!(::core::option::Option::None)
11849 }
11850}
11851
11852fn optional_str(value: Option<&str>) -> TokenStream2 {
11853 if let Some(v) = value {
11854 quote!(::core::option::Option::Some(#v))
11855 } else {
11856 quote!(::core::option::Option::None)
11857 }
11858}
11859
11860fn optional_choices(pairs: Option<&[(String, String)]>) -> TokenStream2 {
11861 let Some(pairs) = pairs else {
11862 return quote!(::core::option::Option::None);
11863 };
11864 let entries = pairs.iter().map(|(v, l)| quote!((#v, #l)));
11865 quote!(::core::option::Option::Some(&[#(#entries),*]))
11866}
11867
11868fn relation_tokens(
11869 field: &syn::Field,
11870 attrs: &FieldAttrs,
11871 fk_inner: Option<&syn::Type>,
11872 table: &str,
11873) -> syn::Result<TokenStream2> {
11874 let root = rustango_root();
11875 if let Some(inner) = fk_inner {
11876 if attrs.fk.is_some() || attrs.o2o.is_some() {
11877 return Err(syn::Error::new_spanned(
11878 field,
11879 "`ForeignKey<T>` already declares the FK target via the type parameter — \
11880 remove the `fk = \"…\"` / `o2o = \"…\"` attribute.",
11881 ));
11882 }
11883 let on = match attrs.on.as_deref() {
11885 Some(on) => quote!(#on),
11886 None => quote! {
11887 match <#inner as #root::core::Model>::SCHEMA.pk_column() {
11888 ::core::option::Option::Some(c) => c,
11889 ::core::option::Option::None => "id",
11890 }
11891 },
11892 };
11893 return Ok(quote! {
11894 ::core::option::Option::Some(#root::core::Relation::fk(
11895 <#inner as #root::core::Model>::SCHEMA.table,
11896 #on,
11897 ))
11898 });
11899 }
11900 match (&attrs.fk, &attrs.o2o) {
11901 (Some(_), Some(_)) => Err(syn::Error::new_spanned(
11902 field,
11903 "`fk` and `o2o` are mutually exclusive",
11904 )),
11905 (Some(to), None) => {
11906 let on = attrs.on.as_deref().unwrap_or("id");
11907 let resolved = if to == "self" { table } else { to };
11913 Ok(quote! {
11914 ::core::option::Option::Some(#root::core::Relation::fk(#resolved, #on))
11915 })
11916 }
11917 (None, Some(to)) => {
11918 let on = attrs.on.as_deref().unwrap_or("id");
11919 let resolved = if to == "self" { table } else { to };
11920 Ok(quote! {
11921 ::core::option::Option::Some(#root::core::Relation::o2o(#resolved, #on))
11922 })
11923 }
11924 (None, None) => {
11925 if attrs.on.is_some() {
11926 return Err(syn::Error::new_spanned(
11927 field,
11928 "`on` requires `fk` or `o2o`",
11929 ));
11930 }
11931 Ok(quote!(::core::option::Option::None))
11932 }
11933 }
11934}
11935
11936#[derive(Clone, Copy, PartialEq, Eq)]
11940enum DetectedKind {
11941 I16,
11942 I32,
11943 I64,
11944 F32,
11945 F64,
11946 Bool,
11947 String,
11948 DateTime,
11949 Date,
11950 Time,
11951 Uuid,
11952 Json,
11953 Decimal,
11954 Binary,
11955 ArrayText,
11957 ArrayInt,
11959 ArrayBigInt,
11961 RangeInt,
11963 RangeBigInt,
11965 RangeNumeric,
11967 RangeDate,
11969 RangeDateTime,
11971 HStore,
11973 Vector,
11977 Geometry,
11981}
11982
11983impl DetectedKind {
11984 fn variant_tokens(self) -> TokenStream2 {
11985 let root = rustango_root();
11986 match self {
11987 Self::I16 => quote!(#root::core::FieldType::I16),
11988 Self::I32 => quote!(#root::core::FieldType::I32),
11989 Self::I64 => quote!(#root::core::FieldType::I64),
11990 Self::F32 => quote!(#root::core::FieldType::F32),
11991 Self::F64 => quote!(#root::core::FieldType::F64),
11992 Self::Bool => quote!(#root::core::FieldType::Bool),
11993 Self::String => quote!(#root::core::FieldType::String),
11994 Self::DateTime => quote!(#root::core::FieldType::DateTime),
11995 Self::Date => quote!(#root::core::FieldType::Date),
11996 Self::Time => quote!(#root::core::FieldType::Time),
11997 Self::Uuid => quote!(#root::core::FieldType::Uuid),
11998 Self::Json => quote!(#root::core::FieldType::Json),
11999 Self::Decimal => quote!(#root::core::FieldType::Decimal),
12000 Self::Binary => quote!(#root::core::FieldType::Binary),
12001 Self::ArrayText => {
12002 quote!(#root::core::FieldType::Array(#root::core::ArrayElem::Text))
12003 }
12004 Self::ArrayInt => {
12005 quote!(#root::core::FieldType::Array(#root::core::ArrayElem::Int))
12006 }
12007 Self::ArrayBigInt => {
12008 quote!(#root::core::FieldType::Array(#root::core::ArrayElem::BigInt))
12009 }
12010 Self::RangeInt => {
12011 quote!(#root::core::FieldType::Range(#root::core::RangeElem::Int))
12012 }
12013 Self::RangeBigInt => {
12014 quote!(#root::core::FieldType::Range(#root::core::RangeElem::BigInt))
12015 }
12016 Self::RangeNumeric => {
12017 quote!(#root::core::FieldType::Range(#root::core::RangeElem::Numeric))
12018 }
12019 Self::RangeDate => {
12020 quote!(#root::core::FieldType::Range(#root::core::RangeElem::Date))
12021 }
12022 Self::RangeDateTime => {
12023 quote!(#root::core::FieldType::Range(#root::core::RangeElem::DateTime))
12024 }
12025 Self::HStore => quote!(#root::core::FieldType::HStore),
12026 Self::Vector => quote!(#root::core::FieldType::Vector(0)),
12029 Self::Geometry => quote!(#root::core::FieldType::Geometry(0)),
12032 }
12033 }
12034
12035 fn is_integer(self) -> bool {
12036 matches!(self, Self::I16 | Self::I32 | Self::I64)
12037 }
12038}
12039
12040#[derive(Clone, Copy)]
12046struct DetectedType<'a> {
12047 kind: DetectedKind,
12048 nullable: bool,
12049 auto: bool,
12050 fk_inner: Option<&'a syn::Type>,
12051}
12052
12053fn auto_inner_type(ty: &syn::Type) -> Option<&syn::Type> {
12058 let Type::Path(TypePath { path, qself: None }) = ty else {
12059 return None;
12060 };
12061 let last = path.segments.last()?;
12062 if last.ident != "Auto" {
12063 return None;
12064 }
12065 let syn::PathArguments::AngleBracketed(args) = &last.arguments else {
12066 return None;
12067 };
12068 args.args.iter().find_map(|a| match a {
12069 syn::GenericArgument::Type(t) => Some(t),
12070 _ => None,
12071 })
12072}
12073
12074fn cast_type(ty: &syn::Type) -> Option<&syn::Type> {
12076 let Type::Path(TypePath { path, qself: None }) = ty else {
12077 return None;
12078 };
12079 let last = path.segments.last()?;
12080 if last.ident == "Cast" {
12081 return Some(ty);
12082 }
12083 if last.ident != "Option" {
12084 return None;
12085 }
12086 let syn::PathArguments::AngleBracketed(args) = &last.arguments else {
12087 return None;
12088 };
12089 match args.args.first()? {
12090 syn::GenericArgument::Type(inner) => cast_type(inner).filter(|_| args.args.len() == 1),
12091 _ => None,
12092 }
12093}
12094
12095fn detect_type(ty: &syn::Type) -> syn::Result<DetectedType<'_>> {
12096 let Type::Path(TypePath { path, qself: None }) = ty else {
12097 return Err(syn::Error::new_spanned(ty, "unsupported field type"));
12098 };
12099 let last = path
12100 .segments
12101 .last()
12102 .ok_or_else(|| syn::Error::new_spanned(ty, "empty type path"))?;
12103
12104 if last.ident == "Option" {
12105 let inner = generic_inner(ty, &last.arguments, "Option")?;
12106 let inner_det = detect_type(inner)?;
12107 if inner_det.nullable {
12108 return Err(syn::Error::new_spanned(
12109 ty,
12110 "nested Option is not supported",
12111 ));
12112 }
12113 if inner_det.auto {
12114 return Err(syn::Error::new_spanned(
12115 ty,
12116 "`Option<Auto<T>>` is not supported — Auto fields are server-assigned and cannot be NULL",
12117 ));
12118 }
12119 return Ok(DetectedType {
12120 nullable: true,
12121 ..inner_det
12122 });
12123 }
12124
12125 if last.ident == "Auto" {
12126 let inner = generic_inner(ty, &last.arguments, "Auto")?;
12127 let inner_det = detect_type(inner)?;
12128 if inner_det.auto {
12129 return Err(syn::Error::new_spanned(ty, "nested Auto is not supported"));
12130 }
12131 if inner_det.nullable {
12132 return Err(syn::Error::new_spanned(
12133 ty,
12134 "`Auto<Option<T>>` is not supported — Auto fields are server-assigned and cannot be NULL",
12135 ));
12136 }
12137 if inner_det.fk_inner.is_some() {
12138 return Err(syn::Error::new_spanned(
12139 ty,
12140 "`Auto<ForeignKey<T>>` is not supported — Auto is for server-assigned PKs, ForeignKey is for parent references",
12141 ));
12142 }
12143 if !matches!(
12144 inner_det.kind,
12145 DetectedKind::I32 | DetectedKind::I64 | DetectedKind::Uuid | DetectedKind::DateTime
12146 ) {
12147 return Err(syn::Error::new_spanned(
12148 ty,
12149 "`Auto<T>` only supports integers (`i32` → SERIAL, `i64` → BIGSERIAL), \
12150 `uuid::Uuid` (DEFAULT gen_random_uuid()), or `chrono::DateTime<chrono::Utc>` \
12151 (DEFAULT now())",
12152 ));
12153 }
12154 return Ok(DetectedType {
12155 auto: true,
12156 ..inner_det
12157 });
12158 }
12159
12160 if last.ident == "ForeignKey" {
12161 let (inner, key_ty) = generic_pair(ty, &last.arguments, "ForeignKey")?;
12162 let kind = match key_ty {
12170 Some(k) => detect_type(k)?.kind,
12171 None => DetectedKind::I64,
12172 };
12173 return Ok(DetectedType {
12174 kind,
12175 nullable: false,
12176 auto: false,
12177 fk_inner: Some(inner),
12178 });
12179 }
12180
12181 let kind = match last.ident.to_string().as_str() {
12182 "i16" => DetectedKind::I16,
12183 "i32" => DetectedKind::I32,
12184 "i64" => DetectedKind::I64,
12185 "f32" => DetectedKind::F32,
12186 "f64" => DetectedKind::F64,
12187 "bool" => DetectedKind::Bool,
12188 "String" => DetectedKind::String,
12189 "DateTime" => DetectedKind::DateTime,
12190 "NaiveDate" => DetectedKind::Date,
12191 "NaiveTime" => DetectedKind::Time,
12192 "Uuid" => DetectedKind::Uuid,
12193 "Value" => DetectedKind::Json,
12194 "Decimal" => DetectedKind::Decimal,
12195 "Vec" => {
12199 let (inner, _) = generic_pair(ty, &last.arguments, "Vec")?;
12200 if let Type::Path(TypePath { path, qself: None }) = inner {
12201 if let Some(seg) = path.segments.last() {
12202 if seg.ident == "u8" && seg.arguments.is_empty() {
12203 return Ok(DetectedType {
12204 kind: DetectedKind::Binary,
12205 nullable: false,
12206 auto: false,
12207 fk_inner: None,
12208 });
12209 }
12210 }
12211 }
12212 return Err(syn::Error::new_spanned(
12213 ty,
12214 "unsupported `Vec<T>` field — only `Vec<u8>` (→ Binary) is supported; \
12215 for a PostgreSQL array column use `Array<String>` / `Array<i32>` / `Array<i64>`",
12216 ));
12217 }
12218 "Array" => {
12221 let (inner, _) = generic_pair(ty, &last.arguments, "Array")?;
12222 let elem = match inner {
12223 Type::Path(TypePath { path, qself: None }) => {
12224 path.segments.last().map(|s| s.ident.to_string())
12225 }
12226 _ => None,
12227 };
12228 let kind = match elem.as_deref() {
12229 Some("String") => DetectedKind::ArrayText,
12230 Some("i32") => DetectedKind::ArrayInt,
12231 Some("i64") => DetectedKind::ArrayBigInt,
12232 _ => {
12233 return Err(syn::Error::new_spanned(
12234 ty,
12235 "unsupported `Array<T>` element — only `Array<String>` (→ text[]), \
12236 `Array<i32>` (→ integer[]), and `Array<i64>` (→ bigint[]) are supported (#341)",
12237 ));
12238 }
12239 };
12240 return Ok(DetectedType {
12241 kind,
12242 nullable: false,
12243 auto: false,
12244 fk_inner: None,
12245 });
12246 }
12247 "Range" => {
12251 let (inner, _) = generic_pair(ty, &last.arguments, "Range")?;
12252 let elem = match inner {
12253 Type::Path(TypePath { path, qself: None }) => {
12254 path.segments.last().map(|s| s.ident.to_string())
12255 }
12256 _ => None,
12257 };
12258 let kind = match elem.as_deref() {
12259 Some("i32") => DetectedKind::RangeInt,
12260 Some("i64") => DetectedKind::RangeBigInt,
12261 Some("Decimal") => DetectedKind::RangeNumeric,
12262 Some("NaiveDate") => DetectedKind::RangeDate,
12263 Some("DateTime") => DetectedKind::RangeDateTime,
12264 _ => {
12265 return Err(syn::Error::new_spanned(
12266 ty,
12267 "unsupported `Range<T>` element — only `Range<i32>` (→ int4range), \
12268 `Range<i64>` (→ int8range), `Range<Decimal>` (→ numrange), \
12269 `Range<NaiveDate>` (→ daterange), and `Range<DateTime<Utc>>` \
12270 (→ tstzrange) are supported (#343)",
12271 ));
12272 }
12273 };
12274 return Ok(DetectedType {
12275 kind,
12276 nullable: false,
12277 auto: false,
12278 fk_inner: None,
12279 });
12280 }
12281 "Cast" => {
12286 return Ok(DetectedType {
12287 kind: DetectedKind::String,
12288 nullable: false,
12289 auto: false,
12290 fk_inner: None,
12291 });
12292 }
12293 "HStore" => {
12296 return Ok(DetectedType {
12297 kind: DetectedKind::HStore,
12298 nullable: false,
12299 auto: false,
12300 fk_inner: None,
12301 });
12302 }
12303 "Vector" => {
12306 return Ok(DetectedType {
12307 kind: DetectedKind::Vector,
12308 nullable: false,
12309 auto: false,
12310 fk_inner: None,
12311 });
12312 }
12313 "Point" => {
12316 return Ok(DetectedType {
12317 kind: DetectedKind::Geometry,
12318 nullable: false,
12319 auto: false,
12320 fk_inner: None,
12321 });
12322 }
12323 other => {
12324 return Err(syn::Error::new_spanned(
12325 ty,
12326 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)"),
12327 ));
12328 }
12329 };
12330 Ok(DetectedType {
12331 kind,
12332 nullable: false,
12333 auto: false,
12334 fk_inner: None,
12335 })
12336}
12337
12338fn generic_inner<'a>(
12339 ty: &'a Type,
12340 arguments: &'a PathArguments,
12341 wrapper: &str,
12342) -> syn::Result<&'a Type> {
12343 let PathArguments::AngleBracketed(args) = arguments else {
12344 return Err(syn::Error::new_spanned(
12345 ty,
12346 format!("{wrapper} requires a generic argument"),
12347 ));
12348 };
12349 args.args
12350 .iter()
12351 .find_map(|a| match a {
12352 GenericArgument::Type(t) => Some(t),
12353 _ => None,
12354 })
12355 .ok_or_else(|| {
12356 syn::Error::new_spanned(ty, format!("{wrapper}<T> requires a type argument"))
12357 })
12358}
12359
12360fn generic_pair<'a>(
12364 ty: &'a Type,
12365 arguments: &'a PathArguments,
12366 wrapper: &str,
12367) -> syn::Result<(&'a Type, Option<&'a Type>)> {
12368 let PathArguments::AngleBracketed(args) = arguments else {
12369 return Err(syn::Error::new_spanned(
12370 ty,
12371 format!("{wrapper} requires a generic argument"),
12372 ));
12373 };
12374 let mut types = args.args.iter().filter_map(|a| match a {
12375 GenericArgument::Type(t) => Some(t),
12376 _ => None,
12377 });
12378 let first = types.next().ok_or_else(|| {
12379 syn::Error::new_spanned(ty, format!("{wrapper}<T> requires a type argument"))
12380 })?;
12381 let second = types.next();
12382 Ok((first, second))
12383}
12384
12385fn to_snake_case(s: &str) -> String {
12386 let mut out = String::with_capacity(s.len() + 4);
12387 for (i, ch) in s.chars().enumerate() {
12388 if ch.is_ascii_uppercase() {
12389 if i > 0 {
12390 out.push('_');
12391 }
12392 out.push(ch.to_ascii_lowercase());
12393 } else {
12394 out.push(ch);
12395 }
12396 }
12397 out
12398}
12399
12400#[derive(Default)]
12406struct FormFieldAttrs {
12407 min: Option<i64>,
12408 max: Option<i64>,
12409 min_length: Option<u32>,
12410 max_length: Option<u32>,
12411 clean: Option<syn::Ident>,
12418}
12419
12420#[derive(Default)]
12423struct FormContainerAttrs {
12424 validate: Option<syn::Ident>,
12430}
12431
12432#[derive(Clone, Copy)]
12434enum FormFieldKind {
12435 String,
12436 I16,
12437 I32,
12438 I64,
12439 F32,
12440 F64,
12441 Bool,
12442}
12443
12444impl FormFieldKind {
12445 fn parse_method(self) -> &'static str {
12446 match self {
12447 Self::I16 => "i16",
12448 Self::I32 => "i32",
12449 Self::I64 => "i64",
12450 Self::F32 => "f32",
12451 Self::F64 => "f64",
12452 Self::String | Self::Bool => "",
12455 }
12456 }
12457}
12458
12459fn form_local(name: &str) -> syn::Ident {
12461 syn::Ident::new(name, proc_macro2::Span::mixed_site())
12462}
12463
12464fn expand_form(input: &DeriveInput) -> syn::Result<TokenStream2> {
12465 let root = rustango_root();
12466 let (errs, cleaned, data_param) = (
12467 form_local("__errors"),
12468 form_local("__cleaned"),
12469 form_local("data"),
12470 );
12471 let struct_name = &input.ident;
12472
12473 let Data::Struct(data) = &input.data else {
12474 return Err(syn::Error::new_spanned(
12475 struct_name,
12476 "Form can only be derived on structs",
12477 ));
12478 };
12479 let Fields::Named(named) = &data.fields else {
12480 return Err(syn::Error::new_spanned(
12481 struct_name,
12482 "Form requires a struct with named fields",
12483 ));
12484 };
12485
12486 let container = parse_form_container_attrs(input)?;
12488 let post_field_clean: Vec<TokenStream2> = Vec::new();
12489 let _ = post_field_clean;
12490
12491 let mut field_blocks: Vec<TokenStream2> = Vec::with_capacity(named.named.len());
12492 let mut field_idents: Vec<&syn::Ident> = Vec::with_capacity(named.named.len());
12493
12494 for field in &named.named {
12495 let ident = field
12496 .ident
12497 .as_ref()
12498 .ok_or_else(|| syn::Error::new(field.span(), "tuple structs are not supported"))?;
12499 let attrs = parse_form_field_attrs(field)?;
12500 let (kind, nullable) = detect_form_field(&field.ty, field.span())?;
12501 let is_string = matches!(kind, FormFieldKind::String);
12502 let is_number = !is_string && !matches!(kind, FormFieldKind::Bool);
12503 if !is_string && (attrs.min_length.is_some() || attrs.max_length.is_some()) {
12504 return Err(syn::Error::new(
12505 field.span(),
12506 "`min_length` / `max_length` need a String field",
12507 ));
12508 }
12509 if !is_number && (attrs.min.is_some() || attrs.max.is_some()) {
12510 return Err(syn::Error::new(
12511 field.span(),
12512 "`min` / `max` need a numeric field",
12513 ));
12514 }
12515
12516 let name_lit = ident_name(ident);
12517 let parse_block = render_form_field_parse(ident, &name_lit, kind, nullable, &attrs);
12518 let clean_block = if let Some(clean_fn) = &attrs.clean {
12524 quote! {
12525 if #errs.fields().get(#name_lit).is_none() {
12526 match Self::#clean_fn(&#ident) {
12527 ::core::result::Result::Ok(#cleaned) => { #ident = #cleaned; }
12528 ::core::result::Result::Err(__msg) => {
12529 #errs.add(#name_lit, __msg);
12530 }
12531 }
12532 }
12533 }
12534 } else {
12535 quote! {}
12536 };
12537 field_blocks.push(quote! {
12538 #parse_block
12539 #clean_block
12540 });
12541 field_idents.push(ident);
12542 }
12543
12544 let cross_field_call = if let Some(validate_fn) = &container.validate {
12549 quote! {
12550 if #errs.is_empty() {
12551 let __candidate = Self { #( #field_idents ),* };
12552 if let ::core::result::Result::Err(__other) = Self::#validate_fn(&__candidate) {
12553 #errs.merge(__other);
12554 }
12555 if !#errs.is_empty() {
12556 return ::core::result::Result::Err(#errs);
12557 }
12558 return ::core::result::Result::Ok(__candidate);
12559 }
12560 }
12561 } else {
12562 quote! {}
12563 };
12564
12565 Ok(quote! {
12566 impl #root::forms::Form for #struct_name {
12567 fn parse(
12568 #data_param: &::std::collections::HashMap<::std::string::String, ::std::string::String>,
12569 ) -> ::core::result::Result<Self, #root::forms::FormErrors> {
12570 let mut #errs = #root::forms::FormErrors::default();
12571 #( #field_blocks )*
12572 #cross_field_call
12573 if !#errs.is_empty() {
12574 return ::core::result::Result::Err(#errs);
12575 }
12576 ::core::result::Result::Ok(Self {
12577 #( #field_idents ),*
12578 })
12579 }
12580 }
12581 })
12582}
12583
12584fn parse_form_container_attrs(input: &DeriveInput) -> syn::Result<FormContainerAttrs> {
12585 let mut out = FormContainerAttrs::default();
12586 for attr in &input.attrs {
12587 if !attr.path().is_ident("form") {
12588 continue;
12589 }
12590 attr.parse_nested_meta(|meta| {
12591 if meta.path.is_ident("validate") {
12592 let s: LitStr = meta.value()?.parse()?;
12593 out.validate = Some(syn::Ident::new(&s.value(), s.span()));
12594 return Ok(());
12595 }
12596 Err(meta.error("unknown form container attribute (supported: `validate`)"))
12597 })?;
12598 }
12599 Ok(out)
12600}
12601
12602fn parse_form_field_attrs(field: &syn::Field) -> syn::Result<FormFieldAttrs> {
12603 let mut out = FormFieldAttrs::default();
12604 for attr in &field.attrs {
12605 if !attr.path().is_ident("form") {
12606 continue;
12607 }
12608 attr.parse_nested_meta(|meta| {
12609 if meta.path.is_ident("min") {
12610 let lit: syn::LitInt = meta.value()?.parse()?;
12611 out.min = Some(lit.base10_parse::<i64>()?);
12612 return Ok(());
12613 }
12614 if meta.path.is_ident("max") {
12615 let lit: syn::LitInt = meta.value()?.parse()?;
12616 out.max = Some(lit.base10_parse::<i64>()?);
12617 return Ok(());
12618 }
12619 if meta.path.is_ident("min_length") {
12620 let lit: syn::LitInt = meta.value()?.parse()?;
12621 out.min_length = Some(lit.base10_parse::<u32>()?);
12622 return Ok(());
12623 }
12624 if meta.path.is_ident("max_length") {
12625 let lit: syn::LitInt = meta.value()?.parse()?;
12626 out.max_length = Some(lit.base10_parse::<u32>()?);
12627 return Ok(());
12628 }
12629 if meta.path.is_ident("clean") {
12630 let s: LitStr = meta.value()?.parse()?;
12631 out.clean = Some(syn::Ident::new(&s.value(), s.span()));
12632 return Ok(());
12633 }
12634 Err(meta.error(
12635 "unknown form field attribute (supported: `min`, `max`, `min_length`, `max_length`, `clean`)",
12636 ))
12637 })?;
12638 }
12639 Ok(out)
12640}
12641
12642fn detect_form_field(ty: &Type, span: proc_macro2::Span) -> syn::Result<(FormFieldKind, bool)> {
12643 let Type::Path(TypePath { path, qself: None }) = ty else {
12644 return Err(syn::Error::new(
12645 span,
12646 "Form field must be a simple typed path (e.g. `String`, `i32`, `Option<String>`)",
12647 ));
12648 };
12649 let last = path
12650 .segments
12651 .last()
12652 .ok_or_else(|| syn::Error::new(span, "empty type path"))?;
12653
12654 if last.ident == "Option" {
12655 let inner = generic_inner(ty, &last.arguments, "Option")?;
12656 let (kind, nested) = detect_form_field(inner, span)?;
12657 if nested {
12658 return Err(syn::Error::new(
12659 span,
12660 "nested Option in Form fields is not supported",
12661 ));
12662 }
12663 return Ok((kind, true));
12664 }
12665
12666 let kind = match last.ident.to_string().as_str() {
12667 "String" => FormFieldKind::String,
12668 "i16" => FormFieldKind::I16,
12669 "i32" => FormFieldKind::I32,
12670 "i64" => FormFieldKind::I64,
12671 "f32" => FormFieldKind::F32,
12672 "f64" => FormFieldKind::F64,
12673 "bool" => FormFieldKind::Bool,
12674 other => {
12675 return Err(syn::Error::new(
12676 span,
12677 format!(
12678 "Form field type `{other}` is not supported in v0.8 — use String / \
12679 i16 / i32 / i64 / f32 / f64 / bool, optionally wrapped in Option<…>"
12680 ),
12681 ));
12682 }
12683 };
12684 Ok((kind, false))
12685}
12686
12687#[allow(clippy::too_many_lines)]
12688fn render_form_field_parse(
12689 ident: &syn::Ident,
12690 name_lit: &str,
12691 kind: FormFieldKind,
12692 nullable: bool,
12693 attrs: &FormFieldAttrs,
12694) -> TokenStream2 {
12695 let (errs, data_param) = (form_local("__errors"), form_local("data"));
12696 let lookup = quote! {
12698 let __raw: ::core::option::Option<&::std::string::String> = #data_param.get(#name_lit);
12699 };
12700
12701 let root = rustango_root();
12702 let checkbox = quote! {
12704 match #root::forms::checkbox(__s) {
12705 ::core::option::Option::Some(__b) => __b,
12706 ::core::option::Option::None => {
12707 #errs.add(#name_lit, "Enter true or false.");
12708 false
12709 }
12710 }
12711 };
12712 let parsed_value = match kind {
12713 FormFieldKind::Bool if nullable => quote! {
12715 let __v: ::core::option::Option<bool> = match __raw {
12716 ::core::option::Option::None => ::core::option::Option::None,
12717 ::core::option::Option::Some(__s) if __s.is_empty() => {
12718 ::core::option::Option::None
12719 }
12720 ::core::option::Option::Some(__s) => ::core::option::Option::Some(#checkbox),
12721 };
12722 },
12723 FormFieldKind::Bool => quote! {
12724 let __v: bool = match __raw {
12725 ::core::option::Option::None => false,
12726 ::core::option::Option::Some(__s) => #checkbox,
12727 };
12728 },
12729 FormFieldKind::String => {
12730 if nullable {
12731 quote! {
12732 let __v: ::core::option::Option<::std::string::String> = match __raw {
12733 ::core::option::Option::None => ::core::option::Option::None,
12734 ::core::option::Option::Some(__s) if __s.is_empty() => {
12735 ::core::option::Option::None
12736 }
12737 ::core::option::Option::Some(__s) => {
12738 ::core::option::Option::Some(::core::clone::Clone::clone(__s))
12739 }
12740 };
12741 }
12742 } else {
12743 quote! {
12744 let __v: ::std::string::String = match __raw {
12745 ::core::option::Option::Some(__s) if !__s.is_empty() => {
12746 ::core::clone::Clone::clone(__s)
12747 }
12748 _ => {
12749 #errs.add(#name_lit, "This field is required.");
12750 ::std::string::String::new()
12751 }
12752 };
12753 }
12754 }
12755 }
12756 FormFieldKind::I16
12757 | FormFieldKind::I32
12758 | FormFieldKind::I64
12759 | FormFieldKind::F32
12760 | FormFieldKind::F64 => {
12761 let parse_ty = syn::Ident::new(kind.parse_method(), proc_macro2::Span::call_site());
12762 let ty_lit = kind.parse_method();
12763 let default_val = match kind {
12764 FormFieldKind::I16 => quote! { 0i16 },
12765 FormFieldKind::I32 => quote! { 0i32 },
12766 FormFieldKind::I64 => quote! { 0i64 },
12767 FormFieldKind::F32 => quote! { 0f32 },
12768 FormFieldKind::F64 => quote! { 0f64 },
12769 _ => quote! { Default::default() },
12770 };
12771 let finite = if matches!(kind, FormFieldKind::F32 | FormFieldKind::F64) {
12773 quote! {
12774 .and_then(|__n| if __n.is_finite() {
12775 ::core::result::Result::Ok(__n)
12776 } else {
12777 ::core::result::Result::Err(::std::string::String::from("not a finite number"))
12778 })
12779 }
12780 } else {
12781 quote! {}
12782 };
12783 if nullable {
12784 quote! {
12785 let __v: ::core::option::Option<#parse_ty> = match __raw {
12786 ::core::option::Option::None => ::core::option::Option::None,
12787 ::core::option::Option::Some(__s) if __s.is_empty() => {
12788 ::core::option::Option::None
12789 }
12790 ::core::option::Option::Some(__s) => {
12791 match __s.parse::<#parse_ty>().map_err(|__e| ::std::string::ToString::to_string(&__e)) #finite {
12792 ::core::result::Result::Ok(__n) => {
12793 ::core::option::Option::Some(__n)
12794 }
12795 ::core::result::Result::Err(__e) => {
12796 #errs.add(
12797 #name_lit,
12798 ::std::format!("Enter a valid {} value: {}", #ty_lit, __e),
12799 );
12800 ::core::option::Option::None
12801 }
12802 }
12803 }
12804 };
12805 }
12806 } else {
12807 quote! {
12808 let __v: #parse_ty = match __raw {
12809 ::core::option::Option::Some(__s) if !__s.is_empty() => {
12810 match __s.parse::<#parse_ty>().map_err(|__e| ::std::string::ToString::to_string(&__e)) #finite {
12811 ::core::result::Result::Ok(__n) => __n,
12812 ::core::result::Result::Err(__e) => {
12813 #errs.add(
12814 #name_lit,
12815 ::std::format!("Enter a valid {} value: {}", #ty_lit, __e),
12816 );
12817 #default_val
12818 }
12819 }
12820 }
12821 _ => {
12822 #errs.add(#name_lit, "This field is required.");
12823 #default_val
12824 }
12825 };
12826 }
12827 }
12828 }
12829 };
12830
12831 let validators = render_form_validators(name_lit, kind, nullable, attrs);
12832
12833 quote! {
12834 let mut #ident = {
12838 #lookup
12839 #parsed_value
12840 #validators
12841 __v
12842 };
12843 }
12844}
12845
12846fn render_form_validators(
12847 name_lit: &str,
12848 kind: FormFieldKind,
12849 nullable: bool,
12850 attrs: &FormFieldAttrs,
12851) -> TokenStream2 {
12852 let errs = form_local("__errors");
12853 let mut checks: Vec<TokenStream2> = Vec::new();
12854
12855 let val_ref = if nullable {
12856 quote! { __v.as_ref() }
12857 } else {
12858 quote! { ::core::option::Option::Some(&__v) }
12859 };
12860
12861 let is_string = matches!(kind, FormFieldKind::String);
12862 let is_numeric = matches!(
12863 kind,
12864 FormFieldKind::I16
12865 | FormFieldKind::I32
12866 | FormFieldKind::I64
12867 | FormFieldKind::F32
12868 | FormFieldKind::F64
12869 );
12870
12871 if is_string {
12872 if let Some(min_len) = attrs.min_length {
12873 let min_len_usize = min_len as usize;
12874 checks.push(quote! {
12875 if let ::core::option::Option::Some(__s) = #val_ref {
12876 if __s.chars().count() < #min_len_usize {
12877 #errs.add(
12878 #name_lit,
12879 ::std::format!("Ensure this value has at least {} characters.", #min_len_usize),
12880 );
12881 }
12882 }
12883 });
12884 }
12885 if let Some(max_len) = attrs.max_length {
12886 let max_len_usize = max_len as usize;
12887 checks.push(quote! {
12888 if let ::core::option::Option::Some(__s) = #val_ref {
12889 if __s.chars().count() > #max_len_usize {
12890 #errs.add(
12891 #name_lit,
12892 ::std::format!("Ensure this value has at most {} characters.", #max_len_usize),
12893 );
12894 }
12895 }
12896 });
12897 }
12898 }
12899
12900 if is_numeric {
12901 if let Some(min) = attrs.min {
12902 checks.push(quote! {
12903 if let ::core::option::Option::Some(__n) = #val_ref {
12904 if (*__n as f64) < (#min as f64) {
12905 #errs.add(
12906 #name_lit,
12907 ::std::format!("Ensure this value is greater than or equal to {}.", #min),
12908 );
12909 }
12910 }
12911 });
12912 }
12913 if let Some(max) = attrs.max {
12914 checks.push(quote! {
12915 if let ::core::option::Option::Some(__n) = #val_ref {
12916 if (*__n as f64) > (#max as f64) {
12917 #errs.add(
12918 #name_lit,
12919 ::std::format!("Ensure this value is less than or equal to {}.", #max),
12920 );
12921 }
12922 }
12923 });
12924 }
12925 }
12926
12927 quote! { #( #checks )* }
12928}
12929
12930struct ViewSetAttrs {
12935 model: syn::Path,
12936 fields: Vec<String>,
12937 filter_fields: Vec<String>,
12938 search_fields: Vec<String>,
12939 ordering: Vec<(String, bool)>,
12941 page_size: Option<usize>,
12942 read_only: bool,
12943 allow_anonymous: bool,
12944 perms: ViewSetPermsAttrs,
12945 serializer: Option<syn::Path>,
12950}
12951
12952#[derive(Default)]
12953struct ViewSetPermsAttrs {
12954 list: Vec<String>,
12955 retrieve: Vec<String>,
12956 create: Vec<String>,
12957 update: Vec<String>,
12958 destroy: Vec<String>,
12959}
12960
12961fn expand_viewset(input: &DeriveInput) -> syn::Result<TokenStream2> {
12962 let root = rustango_root();
12963 let struct_name = &input.ident;
12964
12965 match &input.data {
12967 Data::Struct(s) => match &s.fields {
12968 Fields::Unit | Fields::Named(_) => {}
12969 Fields::Unnamed(_) => {
12970 return Err(syn::Error::new_spanned(
12971 struct_name,
12972 "ViewSet can only be derived on a unit struct or an empty named struct",
12973 ));
12974 }
12975 },
12976 _ => {
12977 return Err(syn::Error::new_spanned(
12978 struct_name,
12979 "ViewSet can only be derived on a struct",
12980 ));
12981 }
12982 }
12983
12984 let attrs = parse_viewset_attrs(input)?;
12985 let model_path = &attrs.model;
12986
12987 let field_lits = attrs.fields.iter().map(|f| f.as_str());
12988
12989 let filter_fields_call = if attrs.filter_fields.is_empty() {
12990 quote!()
12991 } else {
12992 let lits = attrs.filter_fields.iter().map(|f| f.as_str());
12993 quote!(.filter_fields(&[ #(#lits),* ]))
12994 };
12995
12996 let search_fields_call = if attrs.search_fields.is_empty() {
12997 quote!()
12998 } else {
12999 let lits = attrs.search_fields.iter().map(|f| f.as_str());
13000 quote!(.search_fields(&[ #(#lits),* ]))
13001 };
13002
13003 let ordering_call = if attrs.ordering.is_empty() {
13004 quote!()
13005 } else {
13006 let pairs = attrs.ordering.iter().map(|(f, desc)| {
13007 let f = f.as_str();
13008 quote!((#f, #desc))
13009 });
13010 quote!(.ordering(&[ #(#pairs),* ]))
13011 };
13012
13013 let page_size_call = if let Some(n) = attrs.page_size {
13014 quote!(.page_size(#n))
13015 } else {
13016 quote!()
13017 };
13018
13019 let read_only_call = if attrs.read_only {
13020 quote!(.read_only())
13021 } else {
13022 quote!()
13023 };
13024 let allow_anonymous_call = if attrs.allow_anonymous {
13025 quote!(.allow_anonymous())
13026 } else {
13027 quote!()
13028 };
13029
13030 let serializer_call = if let Some(ref ser) = attrs.serializer {
13034 quote!(.serializer::<#ser>())
13035 } else {
13036 quote!()
13037 };
13038
13039 let perms = &attrs.perms;
13040 let perms_call = if perms.list.is_empty()
13041 && perms.retrieve.is_empty()
13042 && perms.create.is_empty()
13043 && perms.update.is_empty()
13044 && perms.destroy.is_empty()
13045 {
13046 quote!()
13047 } else {
13048 let list_lits = perms.list.iter().map(|s| s.as_str());
13049 let retrieve_lits = perms.retrieve.iter().map(|s| s.as_str());
13050 let create_lits = perms.create.iter().map(|s| s.as_str());
13051 let update_lits = perms.update.iter().map(|s| s.as_str());
13052 let destroy_lits = perms.destroy.iter().map(|s| s.as_str());
13053 quote! {
13054 .permissions(#root::viewset::ViewSetPerms {
13055 list: ::std::vec![ #(#list_lits.to_owned()),* ],
13056 retrieve: ::std::vec![ #(#retrieve_lits.to_owned()),* ],
13057 create: ::std::vec![ #(#create_lits.to_owned()),* ],
13058 update: ::std::vec![ #(#update_lits.to_owned()),* ],
13059 destroy: ::std::vec![ #(#destroy_lits.to_owned()),* ],
13060 })
13061 }
13062 };
13063
13064 Ok(quote! {
13065 impl #struct_name {
13066 pub fn router(
13075 prefix: &str,
13076 pool: impl ::core::convert::Into<#root::sql::Pool>,
13077 ) -> #root::__axum::Router {
13078 #root::viewset::ViewSet::for_model(
13079 <#model_path as #root::core::Model>::SCHEMA,
13080 &[ #(#field_lits),* ],
13081 )
13082 #filter_fields_call
13083 #search_fields_call
13084 #ordering_call
13085 #page_size_call
13086 #perms_call
13087 #read_only_call
13088 #allow_anonymous_call
13089 #serializer_call
13090 .router(prefix, pool.into())
13091 }
13092 }
13093 })
13094}
13095
13096fn parse_viewset_attrs(input: &DeriveInput) -> syn::Result<ViewSetAttrs> {
13097 let mut model: Option<syn::Path> = None;
13098 let mut fields: Option<Vec<String>> = None;
13099 let mut filter_fields: Vec<String> = Vec::new();
13100 let mut search_fields: Vec<String> = Vec::new();
13101 let mut ordering: Vec<(String, bool)> = Vec::new();
13102 let mut page_size: Option<usize> = None;
13103 let mut read_only = false;
13104 let mut allow_anonymous = false;
13105 let mut perms = ViewSetPermsAttrs::default();
13106 let mut serializer: Option<syn::Path> = None;
13107
13108 for attr in &input.attrs {
13109 if !attr.path().is_ident("viewset") {
13110 continue;
13111 }
13112 attr.parse_nested_meta(|meta| {
13113 if meta.path.is_ident("model") {
13114 let path: syn::Path = meta.value()?.parse()?;
13115 model = Some(path);
13116 return Ok(());
13117 }
13118 if meta.path.is_ident("serializer") {
13119 let path: syn::Path = meta.value()?.parse()?;
13120 serializer = Some(path);
13121 return Ok(());
13122 }
13123 if meta.path.is_ident("fields") {
13124 let s: LitStr = meta.value()?.parse()?;
13125 fields = Some(split_field_list(&s.value()));
13126 return Ok(());
13127 }
13128 if meta.path.is_ident("filter_fields") {
13129 let s: LitStr = meta.value()?.parse()?;
13130 filter_fields = split_field_list(&s.value());
13131 return Ok(());
13132 }
13133 if meta.path.is_ident("search_fields") {
13134 let s: LitStr = meta.value()?.parse()?;
13135 search_fields = split_field_list(&s.value());
13136 return Ok(());
13137 }
13138 if meta.path.is_ident("ordering") {
13139 let s: LitStr = meta.value()?.parse()?;
13140 ordering = parse_ordering_list(&s.value());
13141 return Ok(());
13142 }
13143 if meta.path.is_ident("page_size") {
13144 let lit: syn::LitInt = meta.value()?.parse()?;
13145 page_size = Some(lit.base10_parse::<usize>()?);
13146 return Ok(());
13147 }
13148 if meta.path.is_ident("read_only") {
13149 read_only = true;
13150 return Ok(());
13151 }
13152 if meta.path.is_ident("allow_anonymous") {
13153 allow_anonymous = true;
13154 return Ok(());
13155 }
13156 if meta.path.is_ident("permissions") {
13157 meta.parse_nested_meta(|inner| {
13158 let parse_codenames = |inner: &syn::meta::ParseNestedMeta| -> syn::Result<Vec<String>> {
13159 let s: LitStr = inner.value()?.parse()?;
13160 Ok(split_field_list(&s.value()))
13161 };
13162 if inner.path.is_ident("list") {
13163 perms.list = parse_codenames(&inner)?;
13164 } else if inner.path.is_ident("retrieve") {
13165 perms.retrieve = parse_codenames(&inner)?;
13166 } else if inner.path.is_ident("create") {
13167 perms.create = parse_codenames(&inner)?;
13168 } else if inner.path.is_ident("update") {
13169 perms.update = parse_codenames(&inner)?;
13170 } else if inner.path.is_ident("destroy") {
13171 perms.destroy = parse_codenames(&inner)?;
13172 } else {
13173 return Err(inner.error(
13174 "unknown permissions key (supported: list, retrieve, create, update, destroy)",
13175 ));
13176 }
13177 Ok(())
13178 })?;
13179 return Ok(());
13180 }
13181 Err(meta.error(
13182 "unknown viewset attribute (supported: model, fields, filter_fields, \
13183 search_fields, ordering, page_size, read_only, allow_anonymous, serializer, \
13184 permissions(...))",
13185 ))
13186 })?;
13187 }
13188
13189 let model = model.ok_or_else(|| {
13190 syn::Error::new_spanned(&input.ident, "`#[viewset(model = SomeModel)]` is required")
13191 })?;
13192 let fields = fields.ok_or_else(|| {
13194 syn::Error::new_spanned(
13195 &input.ident,
13196 "`#[viewset(fields = \"id, title\")]` is required: the fields the API renders and writes",
13197 )
13198 })?;
13199
13200 Ok(ViewSetAttrs {
13201 model,
13202 fields,
13203 filter_fields,
13204 search_fields,
13205 ordering,
13206 page_size,
13207 read_only,
13208 allow_anonymous,
13209 perms,
13210 serializer,
13211 })
13212}
13213
13214struct SerializerContainerAttrs {
13217 model: syn::Path,
13218 cross_validate: Option<syn::Ident>,
13226}
13227
13228#[derive(Default)]
13229struct SerializerFieldAttrs {
13230 read_only: bool,
13231 write_only: bool,
13232 source: Option<String>,
13233 skip: bool,
13234 method: Option<String>,
13238 validate: Option<String>,
13243 nested: bool,
13253 nested_strict: bool,
13258 many: Option<syn::Type>,
13267 slug: Option<String>,
13278 max_length: Option<u64>,
13282 min_length: Option<u64>,
13285 min: Option<i64>,
13288 max: Option<i64>,
13291}
13292
13293fn parse_serializer_container_attrs(input: &DeriveInput) -> syn::Result<SerializerContainerAttrs> {
13294 let mut model: Option<syn::Path> = None;
13295 let mut cross_validate: Option<syn::Ident> = None;
13296 for attr in &input.attrs {
13297 if !attr.path().is_ident("serializer") {
13298 continue;
13299 }
13300 attr.parse_nested_meta(|meta| {
13301 if meta.path.is_ident("model") {
13302 let _eq: syn::Token![=] = meta.input.parse()?;
13303 model = Some(meta.input.parse()?);
13304 return Ok(());
13305 }
13306 if meta.path.is_ident("validate") {
13307 let s: LitStr = meta.value()?.parse()?;
13312 cross_validate = Some(syn::Ident::new(&s.value(), s.span()));
13313 return Ok(());
13314 }
13315 Err(meta.error(
13316 "unknown serializer container attribute \
13317 (supported: `model`, `validate`)",
13318 ))
13319 })?;
13320 }
13321 let model = model.ok_or_else(|| {
13322 syn::Error::new_spanned(
13323 &input.ident,
13324 "`#[serializer(model = SomeModel)]` is required",
13325 )
13326 })?;
13327 Ok(SerializerContainerAttrs {
13328 model,
13329 cross_validate,
13330 })
13331}
13332
13333#[derive(Default)]
13336struct SerdeWireAttrs {
13337 rename_ser: Option<String>,
13338 rename_de: Option<String>,
13339 skip_ser: bool,
13340 skip_de: bool,
13341 skip_ser_if: Option<syn::ExprPath>,
13342}
13343
13344#[derive(Clone, Copy)]
13346enum SerdeRenameRule {
13347 Lower,
13348 Upper,
13349 Pascal,
13350 Camel,
13351 ScreamingSnake,
13352 Kebab,
13353 ScreamingKebab,
13354}
13355
13356impl SerdeRenameRule {
13357 fn parse(s: &LitStr) -> syn::Result<Self> {
13358 Ok(match s.value().as_str() {
13359 "lowercase" | "snake_case" => Self::Lower,
13360 "UPPERCASE" => Self::Upper,
13361 "PascalCase" => Self::Pascal,
13362 "camelCase" => Self::Camel,
13363 "SCREAMING_SNAKE_CASE" => Self::ScreamingSnake,
13364 "kebab-case" => Self::Kebab,
13365 "SCREAMING-KEBAB-CASE" => Self::ScreamingKebab,
13366 _ => {
13367 return Err(syn::Error::new_spanned(
13368 s,
13369 "unknown serde `rename_all` rule",
13370 ))
13371 }
13372 })
13373 }
13374
13375 fn apply(self, field: &str) -> String {
13377 match self {
13378 Self::Lower => field.to_owned(),
13379 Self::Upper | Self::ScreamingSnake => field.to_ascii_uppercase(),
13380 Self::Pascal | Self::Camel => {
13381 let mut out = String::new();
13382 let mut cap = matches!(self, Self::Pascal);
13383 for ch in field.chars() {
13384 if ch == '_' {
13385 cap = !out.is_empty() || matches!(self, Self::Pascal);
13386 } else if cap {
13387 out.push(ch.to_ascii_uppercase());
13388 cap = false;
13389 } else {
13390 out.push(ch);
13391 }
13392 }
13393 out
13394 }
13395 Self::Kebab => field.replace('_', "-"),
13396 Self::ScreamingKebab => field.to_ascii_uppercase().replace('_', "-"),
13397 }
13398 }
13399}
13400
13401fn skip_serde_meta_value(meta: &syn::meta::ParseNestedMeta) -> syn::Result<()> {
13403 if meta.input.peek(syn::Token![=]) {
13404 let _: syn::Expr = meta.value()?.parse()?;
13405 } else if meta.input.peek(syn::token::Paren) {
13406 let _: proc_macro2::Group = meta.input.parse()?;
13407 }
13408 Ok(())
13409}
13410
13411fn parse_serde_rename(
13413 meta: &syn::meta::ParseNestedMeta,
13414 mut set: impl FnMut(bool, LitStr) -> syn::Result<()>,
13415) -> syn::Result<()> {
13416 if meta.input.peek(syn::Token![=]) {
13417 let s: LitStr = meta.value()?.parse()?;
13418 set(true, s.clone())?;
13419 return set(false, s);
13420 }
13421 meta.parse_nested_meta(|inner| {
13422 let is_ser = inner.path.is_ident("serialize");
13423 if !is_ser && !inner.path.is_ident("deserialize") {
13424 return Err(inner.error("expected `serialize` or `deserialize`"));
13425 }
13426 set(is_ser, inner.value()?.parse()?)
13427 })
13428}
13429
13430fn parse_serde_container_rules(
13432 input: &DeriveInput,
13433) -> syn::Result<(Option<SerdeRenameRule>, Option<SerdeRenameRule>)> {
13434 let (mut ser, mut de) = (None, None);
13435 for attr in input.attrs.iter().filter(|a| a.path().is_ident("serde")) {
13436 attr.parse_nested_meta(|meta| {
13437 if meta.path.is_ident("rename_all") {
13438 return parse_serde_rename(&meta, |is_ser, s| {
13439 let rule = Some(SerdeRenameRule::parse(&s)?);
13440 if is_ser {
13441 ser = rule;
13442 } else {
13443 de = rule;
13444 }
13445 Ok(())
13446 });
13447 }
13448 skip_serde_meta_value(&meta)
13449 })?;
13450 }
13451 Ok((ser, de))
13452}
13453
13454fn parse_serde_field_attrs(field: &syn::Field) -> syn::Result<SerdeWireAttrs> {
13455 let mut out = SerdeWireAttrs::default();
13456 for attr in field.attrs.iter().filter(|a| a.path().is_ident("serde")) {
13457 attr.parse_nested_meta(|meta| {
13458 if meta.path.is_ident("rename") {
13459 return parse_serde_rename(&meta, |is_ser, s| {
13460 if is_ser {
13461 out.rename_ser = Some(s.value());
13462 } else {
13463 out.rename_de = Some(s.value());
13464 }
13465 Ok(())
13466 });
13467 }
13468 if meta.path.is_ident("skip") {
13469 out.skip_ser = true;
13470 out.skip_de = true;
13471 return Ok(());
13472 }
13473 if meta.path.is_ident("skip_serializing") {
13474 out.skip_ser = true;
13475 return Ok(());
13476 }
13477 if meta.path.is_ident("skip_deserializing") {
13478 out.skip_de = true;
13479 return Ok(());
13480 }
13481 if meta.path.is_ident("skip_serializing_if") {
13482 let s: LitStr = meta.value()?.parse()?;
13483 out.skip_ser_if = Some(s.parse()?);
13484 return Ok(());
13485 }
13486 skip_serde_meta_value(&meta)
13487 })?;
13488 }
13489 Ok(out)
13490}
13491
13492fn parse_serializer_field_attrs(field: &syn::Field) -> syn::Result<SerializerFieldAttrs> {
13493 let mut out = SerializerFieldAttrs::default();
13494 for attr in &field.attrs {
13495 if !attr.path().is_ident("serializer") {
13496 continue;
13497 }
13498 attr.parse_nested_meta(|meta| {
13499 if meta.path.is_ident("read_only") {
13500 out.read_only = true;
13501 return Ok(());
13502 }
13503 if meta.path.is_ident("write_only") {
13504 out.write_only = true;
13505 return Ok(());
13506 }
13507 if meta.path.is_ident("skip") {
13508 out.skip = true;
13509 return Ok(());
13510 }
13511 if meta.path.is_ident("source") {
13512 let s: LitStr = meta.value()?.parse()?;
13513 out.source = Some(s.value());
13514 return Ok(());
13515 }
13516 if meta.path.is_ident("method") {
13517 let s: LitStr = meta.value()?.parse()?;
13518 out.method = Some(s.value());
13519 return Ok(());
13520 }
13521 if meta.path.is_ident("validate") {
13522 let s: LitStr = meta.value()?.parse()?;
13523 out.validate = Some(s.value());
13524 return Ok(());
13525 }
13526 if meta.path.is_ident("many") {
13527 let _eq: syn::Token![=] = meta.input.parse()?;
13528 out.many = Some(meta.input.parse()?);
13529 return Ok(());
13530 }
13531 if meta.path.is_ident("nested") {
13532 out.nested = true;
13533 if meta.input.peek(syn::token::Paren) {
13536 meta.parse_nested_meta(|inner| {
13537 if inner.path.is_ident("strict") {
13538 out.nested_strict = true;
13539 return Ok(());
13540 }
13541 Err(inner.error("unknown nested sub-attribute (supported: `strict`)"))
13542 })?;
13543 }
13544 return Ok(());
13545 }
13546 if meta.path.is_ident("slug") {
13547 let s: LitStr = meta.value()?.parse()?;
13548 out.slug = Some(s.value());
13549 return Ok(());
13550 }
13551 if meta.path.is_ident("max_length") {
13552 let lit: syn::LitInt = meta.value()?.parse()?;
13553 out.max_length = Some(lit.base10_parse::<u64>()?);
13554 return Ok(());
13555 }
13556 if meta.path.is_ident("min_length") {
13557 let lit: syn::LitInt = meta.value()?.parse()?;
13558 out.min_length = Some(lit.base10_parse::<u64>()?);
13559 return Ok(());
13560 }
13561 if meta.path.is_ident("min") {
13562 let lit: syn::LitInt = meta.value()?.parse()?;
13563 out.min = Some(lit.base10_parse::<i64>()?);
13564 return Ok(());
13565 }
13566 if meta.path.is_ident("max") {
13567 let lit: syn::LitInt = meta.value()?.parse()?;
13568 out.max = Some(lit.base10_parse::<i64>()?);
13569 return Ok(());
13570 }
13571 Err(meta.error(
13572 "unknown serializer field attribute (supported: \
13573 `read_only`, `write_only`, `source`, `skip`, `method`, \
13574 `validate`, `nested`, `many`, `slug`, `max_length`, \
13575 `min_length`, `min`, `max`)",
13576 ))
13577 })?;
13578 }
13579 if out.read_only && out.write_only {
13581 return Err(syn::Error::new_spanned(
13582 field,
13583 "a field cannot be both `read_only` and `write_only`",
13584 ));
13585 }
13586 if out.method.is_some() && out.source.is_some() {
13587 return Err(syn::Error::new_spanned(
13588 field,
13589 "`method` and `source` are mutually exclusive — `method` computes \
13590 the value from a method, `source` reads it from a different model field",
13591 ));
13592 }
13593 if out.slug.is_some() && (out.method.is_some() || out.nested || out.many.is_some()) {
13594 return Err(syn::Error::new_spanned(
13595 field,
13596 "`slug` is mutually exclusive with `method`, `nested`, and `many` \
13597 — pick one strategy for populating the field",
13598 ));
13599 }
13600 Ok(out)
13601}
13602
13603fn expand_serializer(input: &DeriveInput) -> syn::Result<TokenStream2> {
13604 let root = rustango_root();
13605 let struct_name = &input.ident;
13606 let struct_name_lit = struct_name.to_string();
13607
13608 let Data::Struct(data) = &input.data else {
13609 return Err(syn::Error::new_spanned(
13610 struct_name,
13611 "Serializer can only be derived on structs",
13612 ));
13613 };
13614 let Fields::Named(named) = &data.fields else {
13615 return Err(syn::Error::new_spanned(
13616 struct_name,
13617 "Serializer requires a struct with named fields",
13618 ));
13619 };
13620
13621 let container = parse_serializer_container_attrs(input)?;
13622 let model_path = &container.model;
13623
13624 #[allow(dead_code)]
13628 struct FieldInfo {
13629 ident: syn::Ident,
13630 ty: syn::Type,
13631 attrs: SerializerFieldAttrs,
13632 serde: SerdeWireAttrs,
13633 ser_name: String,
13635 de_name: String,
13636 }
13637 let (ser_rule, de_rule) = parse_serde_container_rules(input)?;
13638 let mut fields_info: Vec<FieldInfo> = Vec::new();
13639 for field in &named.named {
13640 let ident = field.ident.clone().expect("named field has ident");
13641 let attrs = parse_serializer_field_attrs(field)?;
13642 let serde = parse_serde_field_attrs(field)?;
13643 let name = ident_name(&ident);
13644 let wire = |rename: &Option<String>, rule: Option<SerdeRenameRule>| {
13645 rename
13646 .clone()
13647 .unwrap_or_else(|| rule.map_or_else(|| name.clone(), |r| r.apply(&name)))
13648 };
13649 fields_info.push(FieldInfo {
13650 ser_name: wire(&serde.rename_ser, ser_rule),
13651 de_name: wire(&serde.rename_de, de_rule),
13652 ident,
13653 ty: field.ty.clone(),
13654 attrs,
13655 serde,
13656 });
13657 }
13658
13659 let from_model_fields = fields_info.iter().map(|fi| {
13661 let ident = &fi.ident;
13662 if let Some(_inner) = &fi.attrs.many {
13663 quote! { #ident: ::std::vec::Vec::new() }
13667 } else if let Some(method) = &fi.attrs.method {
13668 let method_ident = syn::Ident::new(method, ident.span());
13672 quote! { #ident: Self::#method_ident(model) }
13673 } else if let Some(slug_field) = &fi.attrs.slug {
13674 let src_name = fi
13683 .attrs
13684 .source
13685 .as_deref()
13686 .unwrap_or(&ident_name(&fi.ident))
13687 .to_owned();
13688 let src_ident = ident_from_name(&src_name, ident.span());
13689 let slug_ident = syn::Ident::new(slug_field, ident.span());
13690 quote! {
13691 #ident: #root::serializer::LoadedRelation::render(
13692 &model.#src_ident,
13693 |__loaded| ::core::clone::Clone::clone(&__loaded.#slug_ident),
13694 ::core::default::Default::default,
13695 )
13696 }
13697 } else if fi.attrs.nested {
13698 let src_name = fi.attrs.source.as_deref().unwrap_or(&ident_name(&fi.ident)).to_owned();
13716 let src_ident = ident_from_name(&src_name, ident.span());
13717 let unloaded = if fi.attrs.nested_strict {
13718 let panic_msg = format!(
13719 "nested(strict) serializer for `{ident}` requires `model.{src_name}` to be loaded — \
13720 call .get(&pool).await? or .select_related(\"{src_name}\") on the model first",
13721 );
13722 quote! { || ::core::panic!("{}", #panic_msg) }
13723 } else {
13724 quote! { ::core::default::Default::default }
13725 };
13726 quote! {
13727 #ident: #root::serializer::LoadedRelation::render(
13728 &model.#src_ident,
13729 |__loaded| #root::serializer::ModelSerializer::from_model(__loaded),
13730 #unloaded,
13731 )
13732 }
13733 } else if fi.attrs.write_only || fi.attrs.skip {
13734 quote! { #ident: ::core::default::Default::default() }
13736 } else if let Some(src) = &fi.attrs.source {
13737 let src_ident = ident_from_name(src, ident.span());
13738 quote! { #ident: ::core::clone::Clone::clone(&model.#src_ident) }
13739 } else {
13740 quote! { #ident: ::core::clone::Clone::clone(&model.#ident) }
13741 }
13742 });
13743
13744 let is_writable = |fi: &&FieldInfo| {
13747 !fi.attrs.read_only
13748 && !fi.attrs.skip
13749 && !fi.serde.skip_de
13750 && fi.attrs.method.is_none()
13751 && !fi.attrs.nested
13752 && fi.attrs.many.is_none()
13753 && fi.attrs.slug.is_none()
13754 };
13755
13756 let opt_usize = |v: Option<u64>| match v {
13764 Some(n) => {
13765 let n = n as usize;
13766 quote!(::core::option::Option::Some(#n))
13767 }
13768 None => quote!(::core::option::Option::None),
13769 };
13770 #[cfg(feature = "openapi")]
13771 let opt_u64 = |v: Option<u64>| match v {
13772 Some(n) => quote!(::core::option::Option::Some(#n)),
13773 None => quote!(::core::option::Option::None),
13774 };
13775 let opt_i64 = |v: Option<i64>| match v {
13776 Some(n) => quote!(::core::option::Option::Some(#n)),
13777 None => quote!(::core::option::Option::None),
13778 };
13779 let constraint_blocks: Vec<_> = fields_info
13780 .iter()
13781 .filter(is_writable)
13782 .map(|fi| {
13783 let ident = &fi.ident;
13784 let fname = &fi.de_name;
13785 let mname = fi
13786 .attrs
13787 .source
13788 .clone()
13789 .unwrap_or_else(|| ident_name(&fi.ident));
13790 let attr_max_len = opt_usize(fi.attrs.max_length);
13791 let attr_min_len = opt_usize(fi.attrs.min_length);
13792 let attr_min = opt_i64(fi.attrs.min);
13793 let attr_max = opt_i64(fi.attrs.max);
13794 let ty = &fi.ty;
13796 let len_check = (fi.attrs.max_length.is_some() || fi.attrs.min_length.is_some())
13797 .then(|| quote! { #root::forms::validators::assert_length_bounded::<#ty>(); });
13798 let num_check = (fi.attrs.min.is_some() || fi.attrs.max.is_some())
13799 .then(|| quote! { #root::forms::validators::assert_number_bounded::<#ty>(); });
13800 quote! {
13801 #len_check
13802 #num_check
13803 if __sent(#fname) {
13804 let __sf = <#model_path as #root::core::Model>::SCHEMA.field(#mname);
13805 let __max_length: ::core::option::Option<usize> = #attr_max_len
13806 .or_else(|| __sf.and_then(|__f| __f.max_length).map(|__n| __n as usize));
13807 let __min_length: ::core::option::Option<usize> = #attr_min_len;
13808 let __min: ::core::option::Option<i64> =
13809 #attr_min.or_else(|| __sf.and_then(|__f| __f.min));
13810 let __max: ::core::option::Option<i64> =
13811 #attr_max.or_else(|| __sf.and_then(|__f| __f.max));
13812 let __choices = __sf.and_then(|__f| __f.choices);
13813 let __v = #root::__serde_json::to_value(&self.#ident)
13814 .unwrap_or(#root::__serde_json::Value::Null);
13815 #root::forms::validators::check_value(
13816 #fname, &__v, __max_length, __min_length, __min, __max, __choices,
13817 &mut __errors,
13818 );
13819 }
13820 }
13821 })
13822 .collect();
13823 let has_constraints = !constraint_blocks.is_empty();
13824
13825 let validator_calls: Vec<_> = fields_info
13829 .iter()
13830 .filter_map(|fi| {
13831 let ident = &fi.ident;
13832 let name_lit = &fi.de_name;
13833 let method = fi.attrs.validate.as_ref()?;
13834 let method_ident = syn::Ident::new(method, ident.span());
13835 Some(quote! {
13836 if __sent(#name_lit) {
13837 if let ::core::result::Result::Err(__e) = Self::#method_ident(&self.#ident) {
13838 __errors.add(#name_lit.to_owned(), __e);
13839 }
13840 }
13841 })
13842 })
13843 .collect();
13844 let cross_validate_call = container.cross_validate.as_ref().map(|method_ident| {
13851 quote! {
13852 if let ::core::result::Result::Err(__cross) = self.#method_ident() {
13855 __errors.merge(__cross);
13856 }
13857 }
13858 });
13859 let has_validators = !validator_calls.is_empty() || container.cross_validate.is_some();
13860 let has_run_validations = has_validators || has_constraints;
13863 let checks = quote! {
13867 let mut __errors = #root::forms::FormErrors::default();
13868 #( #constraint_blocks )*
13869 #( #validator_calls )*
13870 #cross_validate_call
13871 if __errors.is_empty() {
13872 ::core::result::Result::Ok(())
13873 } else {
13874 ::core::result::Result::Err(__errors)
13875 }
13876 };
13877 let validate_body = quote! {
13878 let __sent = |_: &str| true;
13879 #checks
13880 };
13881 let validate_patch_body = quote! {
13883 let __sent = |__n: &str| __obj.is_some_and(|__o| __o.contains_key(__n));
13884 #checks
13885 };
13886 let validate_method = if has_validators {
13892 quote! {
13893 impl #struct_name {
13894 pub fn validate(&self) -> ::core::result::Result<(), #root::forms::FormErrors> {
13900 #validate_body
13901 }
13902 }
13903 }
13904 } else {
13905 quote! {}
13906 };
13907 let trait_validate_override = if has_run_validations {
13912 quote! {
13913 fn validate(&self) -> ::core::result::Result<(), #root::forms::FormErrors> {
13914 #validate_body
13915 }
13916 }
13917 } else {
13918 quote! {}
13919 };
13920
13921 let many_setters: Vec<_> = fields_info
13925 .iter()
13926 .filter_map(|fi| {
13927 let many_ty = fi.attrs.many.as_ref()?;
13928 let ident = &fi.ident;
13929 let setter = syn::Ident::new(&format!("set_{}", ident_name(ident)), ident.span());
13930 Some(quote! {
13931 pub fn #setter(
13936 &mut self,
13937 models: &[<#many_ty as #root::serializer::ModelSerializer>::Model],
13938 ) -> &mut Self {
13939 self.#ident = models.iter()
13940 .map(<#many_ty as #root::serializer::ModelSerializer>::from_model)
13941 .collect();
13942 self
13943 }
13944 })
13945 })
13946 .collect();
13947 let many_setters_impl = if many_setters.is_empty() {
13948 quote! {}
13949 } else {
13950 quote! {
13951 impl #struct_name {
13952 #( #many_setters )*
13953 }
13954 }
13955 };
13956
13957 let output_fields: Vec<_> = fields_info
13959 .iter()
13960 .filter(|fi| !fi.attrs.write_only && !fi.serde.skip_ser)
13961 .collect();
13962 let output_field_count = output_fields.iter().map(|fi| {
13964 let ident = &fi.ident;
13965 if let Some(pred) = &fi.serde.skip_ser_if {
13966 quote! { + if #pred(&self.#ident) { 0 } else { 1 } }
13967 } else {
13968 quote! { + 1 }
13969 }
13970 });
13971 let output_field_count = quote! { 0usize #( #output_field_count )* };
13972 let serialize_fields = output_fields.iter().map(|fi| {
13973 let ident = &fi.ident;
13974 let name_lit = &fi.ser_name;
13975 if let Some(pred) = &fi.serde.skip_ser_if {
13976 quote! {
13977 if #pred(&self.#ident) {
13978 __state.skip_field(#name_lit)?;
13979 } else {
13980 __state.serialize_field(#name_lit, &self.#ident)?;
13981 }
13982 }
13983 } else {
13984 quote! { __state.serialize_field(#name_lit, &self.#ident)?; }
13985 }
13986 });
13987
13988 let writable_lits: Vec<_> = fields_info
14003 .iter()
14004 .filter(is_writable)
14005 .map(|fi| fi.de_name.clone())
14006 .collect();
14007
14008 let writable_source_lits: Vec<String> = fields_info
14013 .iter()
14014 .filter(is_writable)
14015 .map(|fi| {
14016 fi.attrs
14017 .source
14018 .clone()
14019 .unwrap_or_else(|| ident_name(&fi.ident))
14020 })
14021 .collect();
14022
14023 let readable: Vec<&FieldInfo> = fields_info
14027 .iter()
14028 .filter(|fi| {
14029 !fi.attrs.write_only
14030 && !fi.serde.skip_ser
14031 && !fi.attrs.skip
14032 && fi.attrs.method.is_none()
14033 && !fi.attrs.nested
14034 && fi.attrs.many.is_none()
14035 && fi.attrs.slug.is_none()
14036 })
14037 .collect();
14038 let readable_source_lits: Vec<String> = readable
14039 .iter()
14040 .map(|fi| {
14041 fi.attrs
14042 .source
14043 .clone()
14044 .unwrap_or_else(|| ident_name(&fi.ident))
14045 })
14046 .collect();
14047 let readable_lits: Vec<String> = readable.iter().map(|fi| fi.ser_name.clone()).collect();
14049
14050 let from_writable_json_inits: Vec<_> = fields_info
14057 .iter()
14058 .map(|fi| {
14059 let ident = &fi.ident;
14060 let fname = &fi.de_name;
14061 let ty = &fi.ty;
14062 if is_writable(&fi) {
14063 quote! {
14064 #ident: match __obj.and_then(|__o| __o.get(#fname)) {
14065 ::core::option::Option::Some(__v) => {
14066 match #root::__serde_json::from_value::<#ty>(
14067 ::core::clone::Clone::clone(__v),
14068 ) {
14069 ::core::result::Result::Ok(__x) => __x,
14070 ::core::result::Result::Err(__e) => {
14071 __errors.add(#fname.to_owned(), __e.to_string());
14072 ::core::default::Default::default()
14073 }
14074 }
14075 }
14076 ::core::option::Option::None => ::core::default::Default::default(),
14077 }
14078 }
14079 } else {
14080 quote! { #ident: ::core::default::Default::default() }
14081 }
14082 })
14083 .collect();
14084 let patch_overlays: Vec<_> = fields_info
14086 .iter()
14087 .filter(is_writable)
14088 .map(|fi| {
14089 let ident = &fi.ident;
14090 let fname = &fi.de_name;
14091 let ty = &fi.ty;
14092 quote! {
14093 if let ::core::option::Option::Some(__v) = __obj.and_then(|__o| __o.get(#fname)) {
14094 match #root::__serde_json::from_value::<#ty>(::core::clone::Clone::clone(__v)) {
14095 ::core::result::Result::Ok(__x) => __merged.#ident = __x,
14096 ::core::result::Result::Err(__e) => {
14097 __errors.add(#fname.to_owned(), __e.to_string());
14098 }
14099 }
14100 }
14101 }
14102 })
14103 .collect();
14104 let patch_validate_call = if has_run_validations {
14105 quote! { __merged.__rustango_validate_patch(__obj) }
14106 } else {
14107 quote! { let _ = &__merged; ::core::result::Result::Ok(()) }
14108 };
14109 let patch_validate_helper = if has_run_validations {
14110 quote! {
14111 impl #struct_name {
14112 #[doc(hidden)]
14113 fn __rustango_validate_patch(
14114 &self,
14115 __obj: ::core::option::Option<&#root::__serde_json::Map<::std::string::String, #root::__serde_json::Value>>,
14116 ) -> ::core::result::Result<(), #root::forms::FormErrors> {
14117 #validate_patch_body
14118 }
14119 }
14120 }
14121 } else {
14122 quote! {}
14123 };
14124
14125 let openapi_impl = {
14129 #[cfg(feature = "openapi")]
14130 {
14131 let property_calls = output_fields.iter().map(|fi| {
14132 let name_lit = &fi.ser_name;
14133 let ty = &fi.ty;
14134 let nullable_call = if is_option(ty) {
14135 quote! { .nullable() }
14136 } else {
14137 quote! {}
14138 };
14139 let limits_call = is_writable(fi).then(|| {
14141 let mname = fi
14142 .attrs
14143 .source
14144 .clone()
14145 .unwrap_or_else(|| ident_name(&fi.ident));
14146 let (a, b, c, d) = (
14147 opt_u64(fi.attrs.max_length),
14148 opt_u64(fi.attrs.min_length),
14149 opt_i64(fi.attrs.min),
14150 opt_i64(fi.attrs.max),
14151 );
14152 quote! {
14153 .field_limits(
14154 <#model_path as #root::core::Model>::SCHEMA.field(#mname),
14155 #a, #b, #c, #d,
14156 )
14157 }
14158 });
14159 quote! {
14160 .property(
14161 #name_lit,
14162 <#ty as #root::openapi::OpenApiSchema>::openapi_schema()
14163 #limits_call
14164 #nullable_call,
14165 )
14166 }
14167 });
14168 let required_lits: Vec<_> = output_fields
14169 .iter()
14170 .filter(|fi| !is_option(&fi.ty) && fi.serde.skip_ser_if.is_none())
14171 .map(|fi| fi.ser_name.clone())
14172 .collect();
14173 quote! {
14174 impl #root::openapi::OpenApiSchema for #struct_name {
14175 fn openapi_schema() -> #root::openapi::Schema {
14176 #root::openapi::Schema::object()
14177 #( #property_calls )*
14178 .required([ #( #required_lits ),* ])
14179 }
14180 }
14181 }
14182 }
14183 #[cfg(not(feature = "openapi"))]
14184 {
14185 quote! {}
14186 }
14187 };
14188
14189 let related_tables = fields_info.iter().filter_map(|fi| {
14191 let name = &fi.ser_name;
14192 if let Some(inner) = &fi.attrs.many {
14193 return Some(quote! {
14194 (#name, <<#inner as #root::serializer::ModelSerializer>::Model
14195 as #root::core::Model>::SCHEMA.table)
14196 });
14197 }
14198 if !(fi.attrs.nested || fi.attrs.slug.is_some()) {
14199 return None;
14200 }
14201 let src_name = fi
14202 .attrs
14203 .source
14204 .as_deref()
14205 .unwrap_or(&ident_name(&fi.ident))
14206 .to_owned();
14207 let src_ident = ident_from_name(&src_name, fi.ident.span());
14208 Some(quote! {
14209 (#name, #root::serializer::relation_table(|__m: &#model_path| &__m.#src_ident))
14210 })
14211 });
14212
14213 Ok(quote! {
14214 impl #root::serializer::ModelSerializer for #struct_name {
14215 type Model = #model_path;
14216
14217 fn related_tables() -> ::std::vec::Vec<(&'static str, &'static str)> {
14218 ::std::vec![ #( #related_tables ),* ]
14219 }
14220
14221 fn from_model(model: &Self::Model) -> Self {
14222 Self {
14223 #( #from_model_fields ),*
14224 }
14225 }
14226
14227 fn writable_fields() -> &'static [&'static str] {
14228 &[ #( #writable_lits ),* ]
14229 }
14230
14231 fn writable_source_fields() -> &'static [&'static str] {
14232 &[ #( #writable_source_lits ),* ]
14233 }
14234
14235 fn readable_source_fields() -> &'static [&'static str] {
14236 &[ #( #readable_source_lits ),* ]
14237 }
14238
14239 fn readable_fields() -> &'static [&'static str] {
14240 &[ #( #readable_lits ),* ]
14241 }
14242
14243 fn from_writable_json(
14244 __body: &#root::__serde_json::Value,
14245 ) -> ::core::result::Result<Self, #root::forms::FormErrors> {
14246 let mut __errors = #root::forms::FormErrors::default();
14247 let __obj = __body.as_object();
14248 let __out = Self {
14249 #( #from_writable_json_inits ),*
14250 };
14251 if __errors.is_empty() {
14252 ::core::result::Result::Ok(__out)
14253 } else {
14254 ::core::result::Result::Err(__errors)
14255 }
14256 }
14257
14258 fn validate_patch(
14259 __stored: &Self::Model,
14260 __body: &#root::__serde_json::Value,
14261 ) -> ::core::result::Result<(), #root::forms::FormErrors> {
14262 let mut __errors = #root::forms::FormErrors::default();
14263 let __obj = __body.as_object();
14264 #[allow(unused_mut)]
14265 let mut __merged = <Self as #root::serializer::ModelSerializer>::from_model(__stored);
14266 #( #patch_overlays )*
14267 if !__errors.is_empty() {
14268 return ::core::result::Result::Err(__errors);
14269 }
14270 #patch_validate_call
14271 }
14272
14273 #trait_validate_override
14274 }
14275
14276 #patch_validate_helper
14277
14278 impl #root::__serde::Serialize for #struct_name {
14279 fn serialize<S>(&self, serializer: S)
14280 -> ::core::result::Result<S::Ok, S::Error>
14281 where
14282 S: #root::__serde::Serializer,
14283 {
14284 use #root::__serde::ser::SerializeStruct;
14285 let mut __state = serializer.serialize_struct(
14286 #struct_name_lit,
14287 #output_field_count,
14288 )?;
14289 #( #serialize_fields )*
14290 __state.end()
14291 }
14292 }
14293
14294 #openapi_impl
14295
14296 #validate_method
14297
14298 #many_setters_impl
14299 })
14300}
14301
14302#[cfg_attr(not(feature = "openapi"), allow(dead_code))]
14306fn is_option(ty: &syn::Type) -> bool {
14307 if let syn::Type::Path(p) = ty {
14308 if let Some(last) = p.path.segments.last() {
14309 return last.ident == "Option";
14310 }
14311 }
14312 false
14313}
14314
14315#[cfg(test)]
14316mod main_attr_tests {
14317 use super::*;
14318
14319 fn expand(args: &str) -> String {
14320 let body = quote! { async fn main() { run().await } };
14321 expand_main(args.parse().unwrap(), body)
14322 .expect("expansion failed")
14323 .to_string()
14324 }
14325
14326 #[test]
14330 fn logging_false_emits_no_subscriber() {
14331 let out = expand("logging = false");
14332 assert!(
14333 !out.contains("logging :: Setup"),
14334 "`logging = false` must not install a subscriber:\n{out}",
14335 );
14336 assert!(
14337 out.contains("block_on"),
14338 "the runtime must still be built:\n{out}",
14339 );
14340 }
14341
14342 #[test]
14343 fn default_still_installs_a_subscriber() {
14344 let out = expand("");
14348 assert!(
14349 out.contains("logging :: Setup"),
14350 "the default entrypoint must still install one:\n{out}",
14351 );
14352 assert!(
14355 !out.contains("args ()"),
14356 "the entrypoint must not route logs by argv:\n{out}",
14357 );
14358 }
14359
14360 #[test]
14361 fn logging_false_composes_with_flavor() {
14362 let out = expand("flavor = \"current_thread\", logging = false");
14363 assert!(!out.contains("logging :: Setup"), "{out}");
14364 assert!(out.contains("new_current_thread"), "{out}");
14365 }
14366
14367 #[test]
14369 fn body_runs_on_a_big_stack_thread() {
14370 let out = expand("");
14371 assert!(out.contains("run_on_main_thread"), "{out}");
14372 assert!(out.contains("MAIN_STACK_SIZE"), "{out}");
14373 let out = expand("stack_size = 16777216, logging = false");
14374 assert!(out.contains("16777216usize"), "{out}");
14375 assert!(!out.contains("MAIN_STACK_SIZE"), "{out}");
14376 assert!(
14377 out.contains("thread_stack_size"),
14378 "workers get it too:\n{out}"
14379 );
14380 }
14381
14382 #[test]
14384 fn a_tiny_stack_size_is_refused() {
14385 let body = quote! { async fn main() {} };
14386 let err = expand_main("stack_size = 4096".parse().unwrap(), body)
14387 .expect_err("4 KiB")
14388 .to_string();
14389 assert!(err.contains("at least"), "{err}");
14390 }
14391
14392 #[test]
14394 fn lint_attributes_reach_the_body() {
14395 let body = quote! {
14396 #[allow(unused_variables)]
14397 #[doc = "entry"]
14398 async fn main() { let x = 1; }
14399 };
14400 let out = expand_main(quote!(), body).unwrap().to_string();
14401 assert_eq!(out.matches("allow (unused_variables)").count(), 2, "{out}");
14402 assert_eq!(out.matches("doc = \"entry\"").count(), 1, "{out}");
14403 }
14404
14405 #[test]
14406 fn logging_true_is_the_default_shape() {
14407 assert!(
14408 parse_main_args(&"logging = true".parse().unwrap())
14409 .unwrap()
14410 .logging
14411 );
14412 assert!(
14413 !parse_main_args(&"logging=false".parse().unwrap())
14414 .unwrap()
14415 .logging
14416 );
14417 }
14418}
14419
14420#[cfg(test)]
14421mod main_arg_tests {
14422 use super::*;
14423
14424 fn expand(args: &str) -> syn::Result<TokenStream2> {
14425 let body = quote! { async fn main() { run().await } };
14426 expand_main(args.parse().unwrap(), body)
14427 }
14428
14429 #[test]
14431 fn unknown_or_misspelt_args_are_errors() {
14432 for args in [
14433 "logging = flase",
14434 "multi_thread",
14435 "workers = 4",
14436 r#"flavor = "curent_thread""#,
14437 "worker_threads = 4",
14438 ] {
14439 let err = expand(args).expect_err(args).to_string();
14440 assert!(err.contains("#[rustango::main]"), "{args}: {err}");
14441 }
14442 }
14443
14444 #[test]
14445 fn supported_args_expand() {
14446 for args in [
14447 "",
14448 r#"flavor = "current_thread""#,
14449 r#"flavor = "multi_thread", logging = false"#,
14450 "logging = true",
14451 ] {
14452 assert!(expand(args).is_ok(), "{args}");
14453 }
14454 }
14455}
14456
14457#[cfg(test)]
14458mod embed_migrations_tests {
14459 use super::*;
14460
14461 fn expand(path: &str) -> String {
14462 expand_embed_migrations(quote! { #path })
14463 .expect("expansion failed")
14464 .to_string()
14465 }
14466
14467 #[test]
14468 fn missing_dir_warns() {
14469 let out = expand("./no_such_migrations_dir");
14470 assert!(out.contains("deprecated"), "missing dir must warn:\n{out}");
14471 assert!(out.contains("no_such_migrations_dir"), "{out}");
14472 }
14473
14474 #[test]
14475 fn existing_dir_does_not_warn() {
14476 let out = expand("./src");
14477 assert!(!out.contains("deprecated"), "{out}");
14478 }
14479}
14480
14481#[cfg(test)]
14482mod field_attr_tests {
14483 use super::*;
14484
14485 fn error_for(field: syn::Field) -> Option<String> {
14486 let attrs = parse_field_attrs(&field).unwrap();
14487 let kind = detect_type(&field.ty).unwrap().kind;
14488 check_bound_compatibility(&field, &attrs, kind)
14489 .err()
14490 .map(|e| e.to_string())
14491 }
14492
14493 #[test]
14495 fn type_specific_attrs_reject_other_types() {
14496 let f: syn::FieldsNamed = syn::parse_quote!({
14497 #[rustango(citext)] a: i64,
14498 #[rustango(vector(dims = 3))] b: String,
14499 #[rustango(geometry(srid = 4326))] c: String,
14500 #[rustango(citext)] d: String,
14501 });
14502 let errs: Vec<_> = f.named.into_iter().map(error_for).collect();
14503 assert!(errs[0].as_deref().unwrap().contains("citext"), "{errs:?}");
14504 assert!(errs[1].as_deref().unwrap().contains("vector"), "{errs:?}");
14505 assert!(errs[2].as_deref().unwrap().contains("geometry"), "{errs:?}");
14506 assert_eq!(errs[3], None);
14507 }
14508
14509 #[test]
14511 fn manager_ext_is_refused() {
14512 let input: DeriveInput = syn::parse_quote! {
14513 #[rustango(table = "p", manager(ext = "PostManagerExt"))]
14514 struct Post { id: i64 }
14515 };
14516 let err = parse_container_attrs(&input).err().expect("refused");
14517 assert!(err.to_string().contains("#2132"), "{err}");
14518 }
14519
14520 #[test]
14522 fn set_null_needs_an_optional_foreign_key() {
14523 let input: DeriveInput = syn::parse_quote! {
14524 #[rustango(table = "c")]
14525 struct Child {
14526 #[rustango(primary_key)] id: i64,
14527 #[rustango(on_delete = "set_null")] parent: ForeignKey<Parent>,
14528 }
14529 };
14530 let err = expand(&input).err().expect("refused");
14531 assert!(err.to_string().contains("set_null"), "{err}");
14532 let input: DeriveInput = syn::parse_quote! {
14533 #[rustango(table = "c")]
14534 struct Child {
14535 #[rustango(primary_key)] id: i64,
14536 #[rustango(on_delete = "set_null")] parent: Option<ForeignKey<Parent>>,
14537 }
14538 };
14539 assert!(expand(&input).is_ok());
14540 }
14541
14542 #[test]
14544 fn set_null_needs_a_nullable_fk_column() {
14545 let input: DeriveInput = syn::parse_quote! {
14546 #[rustango(table = "c")]
14547 struct Child {
14548 #[rustango(primary_key)] id: i64,
14549 #[rustango(fk = "p", on_delete = "set_null")] parent_id: i64,
14550 #[rustango(o2o = "q", on_delete = "set_null")] other_id: Option<i64>,
14551 }
14552 };
14553 let err = expand(&input).err().expect("refused");
14554 assert!(err.to_string().contains("set_null"), "{err}");
14555 let input: DeriveInput = syn::parse_quote! {
14556 #[rustango(table = "c")]
14557 struct Child {
14558 #[rustango(primary_key)] id: i64,
14559 #[rustango(fk = "p", on_delete = "set_null")] parent_id: std::option::Option<i64>,
14560 #[rustango(o2o = "q", on_delete = "set_null")] other_id: ::core::option::Option<i64>,
14561 #[rustango(on_delete = "set_null")] parent: core::option::Option<ForeignKey<Parent>>,
14562 }
14563 };
14564 assert!(expand(&input).is_ok());
14565 }
14566
14567 #[test]
14570 fn unknown_index_columns_are_errors() {
14571 let input: DeriveInput = syn::parse_quote! {
14572 #[rustango(
14573 table = "w",
14574 unique_together = "a, nope",
14575 index_together = "a, b_typo",
14576 index("missing"),
14577 get_latest_by = "-when_typo"
14578 )]
14579 struct W { #[rustango(primary_key)] id: i64, a: i64, b: i64 }
14580 };
14581 let err = expand(&input).expect_err("must not compile");
14582 let all: Vec<String> = err.into_iter().map(|e| e.to_string()).collect();
14583 for name in ["nope", "b_typo", "missing", "when_typo"] {
14584 assert!(
14585 all.iter().any(|m| m.contains(&format!("`{name}`"))),
14586 "{name}: {all:?}"
14587 );
14588 }
14589 }
14590
14591 #[test]
14594 fn known_index_columns_compile() {
14595 let input: DeriveInput = syn::parse_quote! {
14596 #[rustango(
14597 table = "w",
14598 unique_together = "a, b_col",
14599 index("lower(a)"),
14600 get_latest_by = "-b"
14601 )]
14602 struct W {
14603 #[rustango(primary_key)] id: i64,
14604 a: i64,
14605 #[rustango(column = "b_col")] b: i64,
14606 }
14607 };
14608 expand(&input).expect("compiles");
14609 }
14610
14611 #[test]
14613 fn reverse_accessor_clash_names_related_name() {
14614 let input: DeriveInput = syn::parse_quote! {
14615 #[rustango(table = "m")]
14616 struct Message {
14617 #[rustango(primary_key)] id: i64,
14618 sender: ForeignKey<User>,
14619 recipient: ForeignKey<User>,
14620 }
14621 };
14622 let err = expand(&input).err().expect("refused");
14623 assert!(err.to_string().contains("related_name"), "{err}");
14624 }
14625
14626 #[test]
14629 fn related_name_plus_skips_the_reverse_accessor() {
14630 let input: DeriveInput = syn::parse_quote! {
14631 #[rustango(table = "m")]
14632 struct Message {
14633 #[rustango(primary_key)] id: i64,
14634 sender: ForeignKey<User>,
14635 #[rustango(related_name = "+")] recipient: ForeignKey<User>,
14636 #[rustango(related_name = "+")] owner: ForeignKey<other::Account>,
14637 }
14638 };
14639 let out = expand(&input).expect("derives").to_string();
14640 assert_eq!(out.matches("message_set_pool").count(), 1, "{out}");
14641 assert!(!out.contains("impl other :: Account {"), "{out}");
14642 }
14643
14644 fn field_error(src: &str) -> Option<String> {
14645 let f: syn::FieldsNamed = syn::parse_str(&format!("{{ {src} }}")).unwrap();
14646 let field = f.named.into_iter().next().unwrap();
14647 match parse_field_attrs(&field) {
14648 Err(e) => Some(e.to_string()),
14649 Ok(_) => error_for(field),
14650 }
14651 }
14652
14653 #[test]
14655 fn flag_attrs_accept_eq_true() {
14656 for key in [
14657 "primary_key",
14658 "unique",
14659 "soft_delete",
14660 "index",
14661 "auto_now",
14662 "auto_now_add",
14663 ] {
14664 let f: syn::FieldsNamed =
14665 syn::parse_str(&format!("{{ #[rustango({key} = true)] a: i64 }}")).unwrap();
14666 let field = f.named.into_iter().next().unwrap();
14667 assert!(parse_field_attrs(&field).is_ok(), "{key} = true");
14668 let err = field_error(&format!("#[rustango({key} = false)] a: i64")).unwrap();
14669 assert!(err.contains(&format!("`{key} = false`")), "{err}");
14670 }
14671 let f: syn::Field =
14672 syn::parse_quote!(#[rustango(primary_key = true, unique = true)] a: i64);
14673 let attrs = parse_field_attrs(&f).unwrap();
14674 assert!(attrs.primary_key && attrs.unique);
14675 }
14676
14677 #[test]
14679 fn unknown_key_is_named_with_a_suggestion() {
14680 let err = field_error("#[rustango(max_lenght = 5)] a: String").unwrap();
14681 assert!(err.contains("`max_lenght`"), "{err}");
14682 assert!(err.contains("did you mean `max_length`"), "{err}");
14683 assert!(err.contains("Supported: column"), "{err}");
14684 let input: DeriveInput = syn::parse_quote! {
14685 #[rustango(tabel = "p")]
14686 struct P { id: i64 }
14687 };
14688 let err = parse_container_attrs(&input).err().unwrap().to_string();
14689 assert!(
14690 err.contains("`tabel`") && err.contains("did you mean `table`"),
14691 "{err}"
14692 );
14693 let err = field_error("#[rustango(zzzzzz)] a: i64").unwrap();
14694 assert!(!err.contains("did you mean"), "{err}");
14695 }
14696
14697 #[test]
14699 fn listed_keys_are_all_known() {
14700 for key in FIELD_ATTR_KEYS {
14701 let err = field_error(&format!("#[rustango({key})] a: i64")).unwrap_or_default();
14702 assert!(!err.contains("unknown rustango field"), "{key}: {err}");
14703 }
14704 for key in CONTAINER_ATTR_KEYS {
14705 let input: DeriveInput =
14706 syn::parse_str(&format!("#[rustango({key})] struct P {{ id: i64 }}")).unwrap();
14707 let err = parse_container_attrs(&input)
14708 .err()
14709 .map(|e| e.to_string())
14710 .unwrap_or_default();
14711 assert!(!err.contains("unknown rustango container"), "{key}: {err}");
14712 }
14713 }
14714
14715 #[test]
14717 fn default_shapes_only_sqlite_takes_are_refused() {
14718 for bad in [
14719 r#"#[rustango(default = "draft")] s: String"#,
14720 r#"#[rustango(default = "draft")] s: Option<String>"#,
14721 r#"#[rustango(default = "abc")] n: i32"#,
14722 r#"#[rustango(default = "'abc'")] n: i64"#,
14723 r#"#[rustango(default = "true")] n: i32"#,
14724 r#"#[rustango(default = "")] n: i32"#,
14725 r#"#[rustango(default = "'x'")] n: f64"#,
14726 ] {
14727 assert!(field_error(bad).is_some(), "{bad} must be refused");
14728 }
14729 let err = field_error(r#"#[rustango(default = "draft")] s: String"#).unwrap();
14730 assert!(err.contains(r#"default = "'draft'""#), "{err}");
14731 let err = field_error(r#"#[rustango(default = "current_user")] s: String"#).unwrap();
14733 assert!(
14734 err.contains("Postgres only") && !err.contains("column"),
14735 "{err}"
14736 );
14737 for good in [
14738 r#"#[rustango(default = "'draft'")] s: String"#,
14739 r#"#[rustango(default = "")] s: String"#,
14740 r#"#[rustango(default = "now()")] s: String"#,
14741 r#"#[rustango(default = "CURRENT_TIMESTAMP")] s: String"#,
14742 r#"#[rustango(default = "NULL")] s: Option<String>"#,
14743 r#"#[rustango(default = "0")] n: i32"#,
14744 r#"#[rustango(default = "-1")] n: i64"#,
14745 r#"#[rustango(default = "'5'")] n: i64"#,
14746 r#"#[rustango(default = "(1 + 2)")] n: i64"#,
14747 r#"#[rustango(default = "1.5")] n: f64"#,
14748 r#"#[rustango(default = "true")] b: bool"#,
14749 ] {
14750 assert_eq!(field_error(good), None, "{good}");
14751 }
14752 }
14753
14754 #[test]
14756 fn raw_idents_derive_plain_names() {
14757 let ident: syn::Ident = syn::parse_quote!(r#type);
14758 assert_eq!(column_type_ident(&ident).to_string(), "type_col");
14759 assert_eq!(ident_from_name("type", ident.span()).to_string(), "r#type");
14760 }
14761}
14762
14763#[cfg(test)]
14764mod model_attr_name_tests {
14765 use super::*;
14766
14767 fn container_error(item: proc_macro2::TokenStream) -> Option<String> {
14768 let input: DeriveInput = syn::parse2(item).unwrap();
14769 parse_container_attrs(&input).err().map(|e| e.to_string())
14770 }
14771
14772 fn field_error(field: proc_macro2::TokenStream) -> Option<String> {
14773 let f: syn::FieldsNamed = syn::parse2(quote!({ #field })).unwrap();
14774 let field = f.named.into_iter().next().unwrap();
14775 parse_field_attrs(&field).err().map(|e| e.to_string())
14776 }
14777
14778 #[test]
14780 fn rgo_is_read_like_rustango() {
14781 let long: DeriveInput = syn::parse_quote! {
14782 #[rustango(table = "t", display = "name")] struct S;
14783 };
14784 let short: DeriveInput = syn::parse_quote! {
14785 #[rgo(table = "t", display = "name")] struct S;
14786 };
14787 let (long, short) = (
14788 parse_container_attrs(&long).unwrap(),
14789 parse_container_attrs(&short).unwrap(),
14790 );
14791 assert_eq!(long.table, short.table);
14792 assert_eq!(short.table.as_deref(), Some("t"));
14793 assert_eq!(long.display.map(|d| d.0), short.display.map(|d| d.0));
14794 }
14795
14796 #[test]
14797 fn mixing_names_on_one_item_or_field_is_refused() {
14798 let err = container_error(quote! {
14799 #[rustango(table = "t")] #[rgo(display = "name")] struct S;
14800 })
14801 .expect("a mix on an item must fail");
14802 assert!(
14803 err.contains("`#[rustango(...)]` and `#[rgo(...)]`"),
14804 "{err}"
14805 );
14806 let err = field_error(quote! { #[rgo(primary_key)] #[rustango(column = "c")] a: i64 })
14807 .expect("a mix on a field must fail");
14808 assert!(
14809 err.contains("`#[rgo(...)]` and `#[rustango(...)]`"),
14810 "{err}"
14811 );
14812 assert_eq!(
14814 container_error(quote! { #[rgo(table = "t")] #[rgo(display = "name")] struct S; }),
14815 None
14816 );
14817 assert_eq!(
14818 field_error(quote! { #[rustango(primary_key)] #[rustango(column = "c")] a: i64 }),
14819 None
14820 );
14821 }
14822
14823 #[test]
14825 fn hints_use_the_written_name() {
14826 let err = container_error(quote! { #[rgo(table = "t", unique_together = "a")] struct S; })
14827 .expect("one column must fail");
14828 assert!(err.contains("#[rgo(unique)]"), "{err}");
14829 let err =
14830 container_error(quote! { #[rustango(table = "t", index_together = "a")] struct S; })
14831 .expect("one column must fail");
14832 assert!(err.contains("#[rustango(index)]"), "{err}");
14833 }
14834}
14835
14836fn decode_text(bytes: Vec<u8>) -> Result<String, String> {
14840 fn utf16(bytes: &[u8], unit: fn([u8; 2]) -> u16) -> Result<String, String> {
14841 if bytes.len() % 2 != 0 {
14842 return Err("odd-length UTF-16".into());
14843 }
14844 let units = bytes.chunks_exact(2).map(|c| unit([c[0], c[1]]));
14845 char::decode_utf16(units)
14846 .collect::<Result<String, _>>()
14847 .map_err(|e| e.to_string())
14848 }
14849 match bytes.as_slice() {
14850 [0xFF, 0xFE, rest @ ..] => utf16(rest, u16::from_le_bytes),
14851 [0xFE, 0xFF, rest @ ..] => utf16(rest, u16::from_be_bytes),
14852 _ => {
14853 let mut bytes = bytes;
14854 if bytes.starts_with(&[0xEF, 0xBB, 0xBF]) {
14855 bytes.drain(..3);
14856 }
14857 String::from_utf8(bytes).map_err(|e| e.to_string())
14858 }
14859 }
14860}
14861
14862#[cfg(test)]
14863mod decode_text_tests {
14864 use super::decode_text;
14865
14866 #[test]
14867 fn a_bom_or_utf16_migration_decodes() {
14868 let text = r#"{"name": "0001_é"}"#;
14869 let mut bom = vec![0xEF, 0xBB, 0xBF];
14870 bom.extend(text.bytes());
14871 let mut le = vec![0xFF, 0xFE];
14872 le.extend(text.encode_utf16().flat_map(u16::to_le_bytes));
14873 let mut be = vec![0xFE, 0xFF];
14874 be.extend(text.encode_utf16().flat_map(u16::to_be_bytes));
14875 for bytes in [text.as_bytes().to_vec(), bom, le, be] {
14876 assert_eq!(decode_text(bytes).unwrap(), text);
14877 }
14878 assert!(decode_text(vec![0xFF, 0xFE, 0x41]).is_err());
14879 }
14880}