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
17#[proc_macro_derive(Model, attributes(rustango))]
19pub fn derive_model(input: TokenStream) -> TokenStream {
20 let input = parse_macro_input!(input as DeriveInput);
21 expand(&input)
22 .unwrap_or_else(syn::Error::into_compile_error)
23 .into()
24}
25
26#[proc_macro_derive(ViewSet, attributes(viewset))]
59pub fn derive_viewset(input: TokenStream) -> TokenStream {
60 let input = parse_macro_input!(input as DeriveInput);
61 expand_viewset(&input)
62 .unwrap_or_else(syn::Error::into_compile_error)
63 .into()
64}
65
66#[proc_macro_derive(Form, attributes(form))]
94pub fn derive_form(input: TokenStream) -> TokenStream {
95 let input = parse_macro_input!(input as DeriveInput);
96 expand_form(&input)
97 .unwrap_or_else(syn::Error::into_compile_error)
98 .into()
99}
100
101#[proc_macro_derive(Serializer, attributes(serializer))]
114pub fn derive_serializer(input: TokenStream) -> TokenStream {
115 let input = parse_macro_input!(input as DeriveInput);
116 expand_serializer(&input)
117 .unwrap_or_else(syn::Error::into_compile_error)
118 .into()
119}
120
121#[proc_macro]
156pub fn embed_migrations(input: TokenStream) -> TokenStream {
157 expand_embed_migrations(input.into())
158 .unwrap_or_else(syn::Error::into_compile_error)
159 .into()
160}
161
162#[proc_macro_attribute]
185pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
186 expand_main(args.into(), item.into())
187 .unwrap_or_else(syn::Error::into_compile_error)
188 .into()
189}
190
191fn expand_main(
192 args: TokenStream2,
193 item: TokenStream2,
194) -> syn::Result<TokenStream2> {
195 let mut input: syn::ItemFn = syn::parse2(item)?;
196 if input.sig.asyncness.is_none() {
197 return Err(syn::Error::new(
198 input.sig.ident.span(),
199 "`#[rustango::main]` must wrap an `async fn`",
200 ));
201 }
202
203 let tokio_attr = if args.is_empty() {
206 quote! { #[::tokio::main] }
207 } else {
208 quote! { #[::tokio::main(#args)] }
209 };
210
211 let body = input.block.clone();
213 input.block = syn::parse2(quote! {{
214 {
215 use ::rustango::__private_runtime::tracing_subscriber::{self, EnvFilter};
216 let _ = tracing_subscriber::fmt()
219 .with_env_filter(
220 EnvFilter::try_from_default_env()
221 .unwrap_or_else(|_| EnvFilter::new("info,sqlx=warn")),
222 )
223 .try_init();
224 }
225 #body
226 }})?;
227
228 Ok(quote! {
229 #tokio_attr
230 #input
231 })
232}
233
234fn expand_embed_migrations(input: TokenStream2) -> syn::Result<TokenStream2> {
235 let path_str = if input.is_empty() {
237 "./migrations".to_string()
238 } else {
239 let lit: LitStr = syn::parse2(input)?;
240 lit.value()
241 };
242
243 let manifest = std::env::var("CARGO_MANIFEST_DIR").map_err(|_| {
244 syn::Error::new(
245 proc_macro2::Span::call_site(),
246 "embed_migrations! must be invoked during a Cargo build (CARGO_MANIFEST_DIR not set)",
247 )
248 })?;
249 let abs = std::path::Path::new(&manifest).join(&path_str);
250
251 let mut entries: Vec<(String, std::path::PathBuf)> = Vec::new();
252 if abs.is_dir() {
253 let read = std::fs::read_dir(&abs).map_err(|e| {
254 syn::Error::new(
255 proc_macro2::Span::call_site(),
256 format!("embed_migrations!: cannot read {}: {e}", abs.display()),
257 )
258 })?;
259 for entry in read.flatten() {
260 let path = entry.path();
261 if !path.is_file() {
262 continue;
263 }
264 if path.extension().and_then(|s| s.to_str()) != Some("json") {
265 continue;
266 }
267 let Some(stem) = path.file_stem().and_then(|s| s.to_str()) else {
268 continue;
269 };
270 entries.push((stem.to_owned(), path));
271 }
272 }
273 entries.sort_by(|a, b| a.0.cmp(&b.0));
274
275 let mut chain_names: Vec<String> = Vec::with_capacity(entries.len());
288 let mut prev_refs: Vec<(String, Option<String>)> = Vec::with_capacity(entries.len());
289 for (stem, path) in &entries {
290 let raw = std::fs::read_to_string(path).map_err(|e| {
291 syn::Error::new(
292 proc_macro2::Span::call_site(),
293 format!(
294 "embed_migrations!: cannot read {} for chain validation: {e}",
295 path.display()
296 ),
297 )
298 })?;
299 let json: serde_json::Value = serde_json::from_str(&raw).map_err(|e| {
300 syn::Error::new(
301 proc_macro2::Span::call_site(),
302 format!(
303 "embed_migrations!: {} is not valid JSON: {e}",
304 path.display()
305 ),
306 )
307 })?;
308 let name = json
309 .get("name")
310 .and_then(|v| v.as_str())
311 .ok_or_else(|| {
312 syn::Error::new(
313 proc_macro2::Span::call_site(),
314 format!(
315 "embed_migrations!: {} is missing the `name` field",
316 path.display()
317 ),
318 )
319 })?
320 .to_owned();
321 if name != *stem {
322 return Err(syn::Error::new(
323 proc_macro2::Span::call_site(),
324 format!(
325 "embed_migrations!: file stem `{stem}` does not match the migration's \
326 `name` field `{name}` — rename the file or fix the JSON",
327 ),
328 ));
329 }
330 let prev = json
331 .get("prev")
332 .and_then(|v| v.as_str())
333 .map(str::to_owned);
334 chain_names.push(name.clone());
335 prev_refs.push((name, prev));
336 }
337
338 let name_set: std::collections::HashSet<&str> =
339 chain_names.iter().map(String::as_str).collect();
340 for (name, prev) in &prev_refs {
341 if let Some(p) = prev {
342 if !name_set.contains(p.as_str()) {
343 return Err(syn::Error::new(
344 proc_macro2::Span::call_site(),
345 format!(
346 "embed_migrations!: broken migration chain — `{name}` declares \
347 prev=`{p}` but no migration with that name exists in {}",
348 abs.display()
349 ),
350 ));
351 }
352 }
353 }
354
355 let pairs: Vec<TokenStream2> = entries
356 .iter()
357 .map(|(name, path)| {
358 let path_lit = path.display().to_string();
359 quote! { (#name, ::core::include_str!(#path_lit)) }
360 })
361 .collect();
362
363 Ok(quote! {
364 {
365 const __RUSTANGO_EMBEDDED: &[(&'static str, &'static str)] = &[#(#pairs),*];
366 __RUSTANGO_EMBEDDED
367 }
368 })
369}
370
371fn expand(input: &DeriveInput) -> syn::Result<TokenStream2> {
372 let struct_name = &input.ident;
373
374 let Data::Struct(data) = &input.data else {
375 return Err(syn::Error::new_spanned(
376 struct_name,
377 "Model can only be derived on structs",
378 ));
379 };
380 let Fields::Named(named) = &data.fields else {
381 return Err(syn::Error::new_spanned(
382 struct_name,
383 "Model requires a struct with named fields",
384 ));
385 };
386
387 let container = parse_container_attrs(input)?;
388 let table = container
389 .table
390 .unwrap_or_else(|| to_snake_case(&struct_name.to_string()));
391 let model_name = struct_name.to_string();
392
393 let collected = collect_fields(named, &table)?;
394
395 if let Some((ref display, span)) = container.display {
397 if !collected.field_names.iter().any(|n| n == display) {
398 return Err(syn::Error::new(
399 span,
400 format!("`display = \"{display}\"` does not match any field on this struct"),
401 ));
402 }
403 }
404 let display = container.display.map(|(name, _)| name);
405 let app_label = container.app.clone();
406
407 if let Some(admin) = &container.admin {
409 for (label, list) in [
410 ("list_display", &admin.list_display),
411 ("search_fields", &admin.search_fields),
412 ("readonly_fields", &admin.readonly_fields),
413 ("list_filter", &admin.list_filter),
414 ] {
415 if let Some((names, span)) = list {
416 for name in names {
417 if !collected.field_names.iter().any(|n| n == name) {
418 return Err(syn::Error::new(
419 *span,
420 format!(
421 "`{label} = \"{name}\"`: \"{name}\" is not a declared field on this struct"
422 ),
423 ));
424 }
425 }
426 }
427 }
428 if let Some((pairs, span)) = &admin.ordering {
429 for (name, _) in pairs {
430 if !collected.field_names.iter().any(|n| n == name) {
431 return Err(syn::Error::new(
432 *span,
433 format!(
434 "`ordering = \"{name}\"`: \"{name}\" is not a declared field on this struct"
435 ),
436 ));
437 }
438 }
439 }
440 if let Some((groups, span)) = &admin.fieldsets {
441 for (_, fields) in groups {
442 for name in fields {
443 if !collected.field_names.iter().any(|n| n == name) {
444 return Err(syn::Error::new(
445 *span,
446 format!(
447 "`fieldsets`: \"{name}\" is not a declared field on this struct"
448 ),
449 ));
450 }
451 }
452 }
453 }
454 }
455 if let Some(audit) = &container.audit {
456 if let Some((names, span)) = &audit.track {
457 for name in names {
458 if !collected.field_names.iter().any(|n| n == name) {
459 return Err(syn::Error::new(
460 *span,
461 format!(
462 "`audit(track = \"{name}\")`: \"{name}\" is not a declared field on this struct"
463 ),
464 ));
465 }
466 }
467 }
468 }
469
470 let audit_track_names: Option<Vec<String>> = container.audit.as_ref().map(|audit| {
473 audit
474 .track
475 .as_ref()
476 .map(|(names, _)| names.clone())
477 .unwrap_or_default()
478 });
479
480 let mut all_indexes: Vec<IndexAttr> = container.indexes;
482 for field in &named.named {
483 let ident = field.ident.as_ref().expect("named");
484 let col = to_snake_case(&ident.to_string()); if let Ok(fa) = parse_field_attrs(field) {
487 if fa.index {
488 let col_name = fa.column.clone().unwrap_or_else(|| col.clone());
489 let auto_name = if fa.index_unique {
490 format!("{table}_{col_name}_uq_idx")
491 } else {
492 format!("{table}_{col_name}_idx")
493 };
494 all_indexes.push(IndexAttr {
495 name: fa.index_name.or(Some(auto_name)),
496 columns: vec![col_name],
497 unique: fa.index_unique,
498 });
499 }
500 }
501 }
502
503 let model_impl = model_impl_tokens(
504 struct_name,
505 &model_name,
506 &table,
507 display.as_deref(),
508 app_label.as_deref(),
509 container.admin.as_ref(),
510 &collected.field_schemas,
511 collected.soft_delete_column.as_deref(),
512 container.permissions,
513 audit_track_names.as_deref(),
514 &container.m2m,
515 &all_indexes,
516 &container.checks,
517 &container.composite_fks,
518 &container.generic_fks,
519 );
520 let module_ident = column_module_ident(struct_name);
521 let column_consts = column_const_tokens(&module_ident, &collected.column_entries);
522 let audited_fields: Option<Vec<&ColumnEntry>> = container.audit.as_ref().map(|audit| {
523 let track_set: Option<std::collections::HashSet<&str>> = audit
524 .track
525 .as_ref()
526 .map(|(names, _)| names.iter().map(String::as_str).collect());
527 collected
528 .column_entries
529 .iter()
530 .filter(|c| {
531 track_set
532 .as_ref()
533 .map_or(true, |s| s.contains(c.name.as_str()))
534 })
535 .collect()
536 });
537 let inherent_impl = inherent_impl_tokens(
538 struct_name,
539 &collected,
540 collected.primary_key.as_ref(),
541 &column_consts,
542 audited_fields.as_deref(),
543 );
544 let column_module = column_module_tokens(&module_ident, struct_name, &collected.column_entries);
545 let from_row_impl = from_row_impl_tokens(struct_name, &collected.from_row_inits);
546 let reverse_helpers = reverse_helper_tokens(struct_name, &collected.fk_relations);
547 let m2m_accessors = m2m_accessor_tokens(struct_name, &container.m2m);
548
549 Ok(quote! {
550 #model_impl
551 #inherent_impl
552 #from_row_impl
553 #column_module
554 #reverse_helpers
555 #m2m_accessors
556
557 ::rustango::core::inventory::submit! {
558 ::rustango::core::ModelEntry {
559 schema: <#struct_name as ::rustango::core::Model>::SCHEMA,
560 module_path: ::core::module_path!(),
565 }
566 }
567 })
568}
569
570fn load_related_impl_tokens(
581 struct_name: &syn::Ident,
582 fk_relations: &[FkRelation],
583) -> TokenStream2 {
584 let arms = fk_relations.iter().map(|rel| {
585 let parent_ty = &rel.parent_type;
586 let fk_col = rel.fk_column.as_str();
587 let field_ident = syn::Ident::new(fk_col, proc_macro2::Span::call_site());
590 let (variant_ident, default_expr) = rel.pk_kind.sqlvalue_match_arm();
591 let assign = if rel.nullable {
592 quote! {
593 self.#field_ident = ::core::option::Option::Some(
594 ::rustango::sql::ForeignKey::loaded(_pk, _parent),
595 );
596 }
597 } else {
598 quote! {
599 self.#field_ident = ::rustango::sql::ForeignKey::loaded(_pk, _parent);
600 }
601 };
602 quote! {
603 #fk_col => {
604 let _parent: #parent_ty = <#parent_ty>::__rustango_from_aliased_row(row, alias)?;
605 let _pk = match <#parent_ty>::__rustango_pk_value(&_parent) {
606 ::rustango::core::SqlValue::#variant_ident(v) => v,
607 _ => #default_expr,
608 };
609 #assign
610 ::core::result::Result::Ok(true)
611 }
612 }
613 });
614 quote! {
615 impl ::rustango::sql::LoadRelated for #struct_name {
616 #[allow(unused_variables)]
617 fn __rustango_load_related(
618 &mut self,
619 row: &::rustango::sql::sqlx::postgres::PgRow,
620 field_name: &str,
621 alias: &str,
622 ) -> ::core::result::Result<bool, ::rustango::sql::sqlx::Error> {
623 match field_name {
624 #( #arms )*
625 _ => ::core::result::Result::Ok(false),
626 }
627 }
628 }
629 }
630}
631
632fn load_related_impl_my_tokens(
640 struct_name: &syn::Ident,
641 fk_relations: &[FkRelation],
642) -> TokenStream2 {
643 let arms = fk_relations.iter().map(|rel| {
644 let parent_ty = &rel.parent_type;
645 let fk_col = rel.fk_column.as_str();
646 let field_ident = syn::Ident::new(fk_col, proc_macro2::Span::call_site());
647 let (variant_ident, default_expr) = rel.pk_kind.sqlvalue_match_arm();
648 let assign = if rel.nullable {
649 quote! {
650 __self.#field_ident = ::core::option::Option::Some(
651 ::rustango::sql::ForeignKey::loaded(_pk, _parent),
652 );
653 }
654 } else {
655 quote! {
656 __self.#field_ident = ::rustango::sql::ForeignKey::loaded(_pk, _parent);
657 }
658 };
659 quote! {
664 #fk_col => {
665 let _parent: #parent_ty =
666 <#parent_ty>::__rustango_from_aliased_my_row(row, alias)?;
667 let _pk = match <#parent_ty>::__rustango_pk_value(&_parent) {
668 ::rustango::core::SqlValue::#variant_ident(v) => v,
669 _ => #default_expr,
670 };
671 #assign
672 ::core::result::Result::Ok(true)
673 }
674 }
675 });
676 quote! {
677 ::rustango::__impl_my_load_related!(#struct_name, |__self, row, field_name, alias| {
678 #( #arms )*
679 });
680 }
681}
682
683fn fk_pk_access_impl_tokens(
691 struct_name: &syn::Ident,
692 fk_relations: &[FkRelation],
693) -> TokenStream2 {
694 let arms = fk_relations.iter().map(|rel| {
695 let fk_col = rel.fk_column.as_str();
696 let field_ident = syn::Ident::new(fk_col, proc_macro2::Span::call_site());
697 if rel.pk_kind == DetectedKind::I64 {
698 if rel.nullable {
704 quote! {
705 #fk_col => self.#field_ident
706 .as_ref()
707 .map(|fk| ::rustango::sql::ForeignKey::pk(fk)),
708 }
709 } else {
710 quote! {
711 #fk_col => ::core::option::Option::Some(self.#field_ident.pk()),
712 }
713 }
714 } else {
715 quote! {
723 #fk_col => ::core::option::Option::None,
724 }
725 }
726 });
727 quote! {
728 impl ::rustango::sql::FkPkAccess for #struct_name {
729 #[allow(unused_variables)]
730 fn __rustango_fk_pk(&self, field_name: &str) -> ::core::option::Option<i64> {
731 match field_name {
732 #( #arms )*
733 _ => ::core::option::Option::None,
734 }
735 }
736 }
737 }
738}
739
740fn reverse_helper_tokens(
746 child_ident: &syn::Ident,
747 fk_relations: &[FkRelation],
748) -> TokenStream2 {
749 if fk_relations.is_empty() {
750 return TokenStream2::new();
751 }
752 let suffix = format!("{}_set", to_snake_case(&child_ident.to_string()));
756 let method_ident = syn::Ident::new(&suffix, child_ident.span());
757 let impls = fk_relations.iter().map(|rel| {
758 let parent_ty = &rel.parent_type;
759 let fk_col = rel.fk_column.as_str();
760 let doc = format!(
761 "Fetch every `{child_ident}` whose `{fk_col}` foreign key points at this row. \
762 Single SQL query — `SELECT … FROM <{child_ident} table> WHERE {fk_col} = $1` — \
763 generated from the FK declaration on `{child_ident}::{fk_col}`. Composes with \
764 further `{child_ident}::objects()` filters via direct queryset use."
765 );
766 quote! {
767 impl #parent_ty {
768 #[doc = #doc]
769 pub async fn #method_ident<'_c, _E>(
774 &self,
775 _executor: _E,
776 ) -> ::core::result::Result<
777 ::std::vec::Vec<#child_ident>,
778 ::rustango::sql::ExecError,
779 >
780 where
781 _E: ::rustango::sql::sqlx::Executor<
782 '_c,
783 Database = ::rustango::sql::sqlx::Postgres,
784 >,
785 {
786 let _pk: ::rustango::core::SqlValue = self.__rustango_pk_value();
787 ::rustango::query::QuerySet::<#child_ident>::new()
788 .filter(#fk_col, ::rustango::core::Op::Eq, _pk)
789 .fetch_on(_executor)
790 .await
791 }
792 }
793 }
794 });
795 quote! { #( #impls )* }
796}
797
798fn m2m_accessor_tokens(struct_name: &syn::Ident, m2m_relations: &[M2MAttr]) -> TokenStream2 {
801 if m2m_relations.is_empty() {
802 return TokenStream2::new();
803 }
804 let methods = m2m_relations.iter().map(|rel| {
805 let method_name = format!("{}_m2m", rel.name);
806 let method_ident = syn::Ident::new(&method_name, struct_name.span());
807 let through = rel.through.as_str();
808 let src_col = rel.src.as_str();
809 let dst_col = rel.dst.as_str();
810 quote! {
811 pub fn #method_ident(&self) -> ::rustango::sql::M2MManager {
812 ::rustango::sql::M2MManager {
813 src_pk: self.__rustango_pk_value(),
814 through: #through,
815 src_col: #src_col,
816 dst_col: #dst_col,
817 }
818 }
819 }
820 });
821 quote! {
822 impl #struct_name {
823 #( #methods )*
824 }
825 }
826}
827
828struct ColumnEntry {
829 ident: syn::Ident,
832 value_ty: Type,
834 name: String,
836 column: String,
838 field_type_tokens: TokenStream2,
840}
841
842struct CollectedFields {
843 field_schemas: Vec<TokenStream2>,
844 from_row_inits: Vec<TokenStream2>,
845 from_aliased_row_inits: Vec<TokenStream2>,
849 insert_columns: Vec<TokenStream2>,
852 insert_values: Vec<TokenStream2>,
855 insert_pushes: Vec<TokenStream2>,
860 returning_cols: Vec<TokenStream2>,
863 auto_assigns: Vec<TokenStream2>,
866 auto_field_idents: Vec<(syn::Ident, String)>,
870 first_auto_value_ty: Option<Type>,
873 bulk_pushes_no_auto: Vec<TokenStream2>,
877 bulk_pushes_all: Vec<TokenStream2>,
881 bulk_columns_no_auto: Vec<TokenStream2>,
884 bulk_columns_all: Vec<TokenStream2>,
887 bulk_auto_uniformity: Vec<TokenStream2>,
891 first_auto_ident: Option<syn::Ident>,
894 has_auto: bool,
896 pk_is_auto: bool,
900 update_assignments: Vec<TokenStream2>,
903 upsert_update_columns: Vec<TokenStream2>,
906 primary_key: Option<(syn::Ident, String)>,
907 column_entries: Vec<ColumnEntry>,
908 field_names: Vec<String>,
911 fk_relations: Vec<FkRelation>,
916 soft_delete_column: Option<String>,
921}
922
923#[derive(Clone)]
924struct FkRelation {
925 parent_type: Type,
928 fk_column: String,
931 pk_kind: DetectedKind,
936 nullable: bool,
941}
942
943fn collect_fields(named: &syn::FieldsNamed, table: &str) -> syn::Result<CollectedFields> {
944 let cap = named.named.len();
945 let mut out = CollectedFields {
946 field_schemas: Vec::with_capacity(cap),
947 from_row_inits: Vec::with_capacity(cap),
948 from_aliased_row_inits: Vec::with_capacity(cap),
949 insert_columns: Vec::with_capacity(cap),
950 insert_values: Vec::with_capacity(cap),
951 insert_pushes: Vec::with_capacity(cap),
952 returning_cols: Vec::new(),
953 auto_assigns: Vec::new(),
954 auto_field_idents: Vec::new(),
955 first_auto_value_ty: None,
956 bulk_pushes_no_auto: Vec::with_capacity(cap),
957 bulk_pushes_all: Vec::with_capacity(cap),
958 bulk_columns_no_auto: Vec::with_capacity(cap),
959 bulk_columns_all: Vec::with_capacity(cap),
960 bulk_auto_uniformity: Vec::new(),
961 first_auto_ident: None,
962 has_auto: false,
963 pk_is_auto: false,
964 update_assignments: Vec::with_capacity(cap),
965 upsert_update_columns: Vec::with_capacity(cap),
966 primary_key: None,
967 column_entries: Vec::with_capacity(cap),
968 field_names: Vec::with_capacity(cap),
969 fk_relations: Vec::new(),
970 soft_delete_column: None,
971 };
972
973 for field in &named.named {
974 let info = process_field(field, table)?;
975 out.field_names.push(info.ident.to_string());
976 out.field_schemas.push(info.schema);
977 out.from_row_inits.push(info.from_row_init);
978 out.from_aliased_row_inits.push(info.from_aliased_row_init);
979 if let Some(parent_ty) = info.fk_inner.clone() {
980 out.fk_relations.push(FkRelation {
981 parent_type: parent_ty,
982 fk_column: info.column.clone(),
983 pk_kind: info.fk_pk_kind,
984 nullable: info.nullable,
985 });
986 }
987 if info.soft_delete {
988 if out.soft_delete_column.is_some() {
989 return Err(syn::Error::new_spanned(
990 field,
991 "only one field may be marked `#[rustango(soft_delete)]`",
992 ));
993 }
994 out.soft_delete_column = Some(info.column.clone());
995 }
996 let column = info.column.as_str();
997 let ident = info.ident;
998 out.insert_columns.push(quote!(#column));
999 out.insert_values.push(quote! {
1000 ::core::convert::Into::<::rustango::core::SqlValue>::into(
1001 ::core::clone::Clone::clone(&self.#ident)
1002 )
1003 });
1004 if info.auto {
1005 out.has_auto = true;
1006 if out.first_auto_ident.is_none() {
1007 out.first_auto_ident = Some(ident.clone());
1008 out.first_auto_value_ty = auto_inner_type(info.value_ty).cloned();
1009 }
1010 out.returning_cols.push(quote!(#column));
1011 out.auto_field_idents
1012 .push((ident.clone(), info.column.clone()));
1013 out.auto_assigns.push(quote! {
1014 self.#ident = ::rustango::sql::try_get_returning(_returning_row, #column)?;
1015 });
1016 out.insert_pushes.push(quote! {
1017 if let ::rustango::sql::Auto::Set(_v) = &self.#ident {
1018 _columns.push(#column);
1019 _values.push(::core::convert::Into::<::rustango::core::SqlValue>::into(
1020 ::core::clone::Clone::clone(_v)
1021 ));
1022 }
1023 });
1024 out.bulk_columns_all.push(quote!(#column));
1027 out.bulk_pushes_all.push(quote! {
1028 _row_vals.push(::core::convert::Into::<::rustango::core::SqlValue>::into(
1029 ::core::clone::Clone::clone(&_row.#ident)
1030 ));
1031 });
1032 let ident_clone = ident.clone();
1036 out.bulk_auto_uniformity.push(quote! {
1037 for _r in rows.iter().skip(1) {
1038 if matches!(_r.#ident_clone, ::rustango::sql::Auto::Unset) != _first_unset {
1039 return ::core::result::Result::Err(
1040 ::rustango::sql::ExecError::Sql(
1041 ::rustango::sql::SqlError::BulkAutoMixed
1042 )
1043 );
1044 }
1045 }
1046 });
1047 } else {
1048 out.insert_pushes.push(quote! {
1049 _columns.push(#column);
1050 _values.push(::core::convert::Into::<::rustango::core::SqlValue>::into(
1051 ::core::clone::Clone::clone(&self.#ident)
1052 ));
1053 });
1054 out.bulk_columns_no_auto.push(quote!(#column));
1056 out.bulk_columns_all.push(quote!(#column));
1057 let push_expr = quote! {
1058 _row_vals.push(::core::convert::Into::<::rustango::core::SqlValue>::into(
1059 ::core::clone::Clone::clone(&_row.#ident)
1060 ));
1061 };
1062 out.bulk_pushes_no_auto.push(push_expr.clone());
1063 out.bulk_pushes_all.push(push_expr);
1064 }
1065 if info.primary_key {
1066 if out.primary_key.is_some() {
1067 return Err(syn::Error::new_spanned(
1068 field,
1069 "only one field may be marked `#[rustango(primary_key)]`",
1070 ));
1071 }
1072 out.primary_key = Some((ident.clone(), info.column.clone()));
1073 if info.auto {
1074 out.pk_is_auto = true;
1075 }
1076 } else if info.auto_now_add {
1077 } else if info.auto_now {
1079 out.update_assignments.push(quote! {
1084 ::rustango::core::Assignment {
1085 column: #column,
1086 value: ::core::convert::Into::<::rustango::core::SqlValue>::into(
1087 ::chrono::Utc::now()
1088 ),
1089 }
1090 });
1091 out.upsert_update_columns.push(quote!(#column));
1092 } else {
1093 out.update_assignments.push(quote! {
1094 ::rustango::core::Assignment {
1095 column: #column,
1096 value: ::core::convert::Into::<::rustango::core::SqlValue>::into(
1097 ::core::clone::Clone::clone(&self.#ident)
1098 ),
1099 }
1100 });
1101 out.upsert_update_columns.push(quote!(#column));
1102 }
1103 out.column_entries.push(ColumnEntry {
1104 ident: ident.clone(),
1105 value_ty: info.value_ty.clone(),
1106 name: ident.to_string(),
1107 column: info.column.clone(),
1108 field_type_tokens: info.field_type_tokens,
1109 });
1110 }
1111 Ok(out)
1112}
1113
1114fn model_impl_tokens(
1115 struct_name: &syn::Ident,
1116 model_name: &str,
1117 table: &str,
1118 display: Option<&str>,
1119 app_label: Option<&str>,
1120 admin: Option<&AdminAttrs>,
1121 field_schemas: &[TokenStream2],
1122 soft_delete_column: Option<&str>,
1123 permissions: bool,
1124 audit_track: Option<&[String]>,
1125 m2m_relations: &[M2MAttr],
1126 indexes: &[IndexAttr],
1127 checks: &[CheckAttr],
1128 composite_fks: &[CompositeFkAttr],
1129 generic_fks: &[GenericFkAttr],
1130) -> TokenStream2 {
1131 let display_tokens = if let Some(name) = display {
1132 quote!(::core::option::Option::Some(#name))
1133 } else {
1134 quote!(::core::option::Option::None)
1135 };
1136 let app_label_tokens = if let Some(name) = app_label {
1137 quote!(::core::option::Option::Some(#name))
1138 } else {
1139 quote!(::core::option::Option::None)
1140 };
1141 let soft_delete_tokens = if let Some(col) = soft_delete_column {
1142 quote!(::core::option::Option::Some(#col))
1143 } else {
1144 quote!(::core::option::Option::None)
1145 };
1146 let audit_track_tokens = match audit_track {
1147 None => quote!(::core::option::Option::None),
1148 Some(names) => {
1149 let lits = names.iter().map(|n| n.as_str());
1150 quote!(::core::option::Option::Some(&[ #(#lits),* ]))
1151 }
1152 };
1153 let admin_tokens = admin_config_tokens(admin);
1154 let indexes_tokens = indexes.iter().map(|idx| {
1155 let name = idx.name.as_deref().unwrap_or("unnamed_index");
1156 let cols: Vec<&str> = idx.columns.iter().map(String::as_str).collect();
1157 let unique = idx.unique;
1158 quote! {
1159 ::rustango::core::IndexSchema {
1160 name: #name,
1161 columns: &[ #(#cols),* ],
1162 unique: #unique,
1163 }
1164 }
1165 });
1166 let checks_tokens = checks.iter().map(|c| {
1167 let name = c.name.as_str();
1168 let expr = c.expr.as_str();
1169 quote! {
1170 ::rustango::core::CheckConstraint {
1171 name: #name,
1172 expr: #expr,
1173 }
1174 }
1175 });
1176 let composite_fk_tokens = composite_fks.iter().map(|rel| {
1177 let name = rel.name.as_str();
1178 let to = rel.to.as_str();
1179 let from_cols: Vec<&str> = rel.from.iter().map(String::as_str).collect();
1180 let on_cols: Vec<&str> = rel.on.iter().map(String::as_str).collect();
1181 quote! {
1182 ::rustango::core::CompositeFkRelation {
1183 name: #name,
1184 to: #to,
1185 from: &[ #(#from_cols),* ],
1186 on: &[ #(#on_cols),* ],
1187 }
1188 }
1189 });
1190 let generic_fk_tokens = generic_fks.iter().map(|rel| {
1191 let name = rel.name.as_str();
1192 let ct_col = rel.ct_column.as_str();
1193 let pk_col = rel.pk_column.as_str();
1194 quote! {
1195 ::rustango::core::GenericRelation {
1196 name: #name,
1197 ct_column: #ct_col,
1198 pk_column: #pk_col,
1199 }
1200 }
1201 });
1202 let m2m_tokens = m2m_relations.iter().map(|rel| {
1203 let name = rel.name.as_str();
1204 let to = rel.to.as_str();
1205 let through = rel.through.as_str();
1206 let src = rel.src.as_str();
1207 let dst = rel.dst.as_str();
1208 quote! {
1209 ::rustango::core::M2MRelation {
1210 name: #name,
1211 to: #to,
1212 through: #through,
1213 src_col: #src,
1214 dst_col: #dst,
1215 }
1216 }
1217 });
1218 quote! {
1219 impl ::rustango::core::Model for #struct_name {
1220 const SCHEMA: &'static ::rustango::core::ModelSchema = &::rustango::core::ModelSchema {
1221 name: #model_name,
1222 table: #table,
1223 fields: &[ #(#field_schemas),* ],
1224 display: #display_tokens,
1225 app_label: #app_label_tokens,
1226 admin: #admin_tokens,
1227 soft_delete_column: #soft_delete_tokens,
1228 permissions: #permissions,
1229 audit_track: #audit_track_tokens,
1230 m2m: &[ #(#m2m_tokens),* ],
1231 indexes: &[ #(#indexes_tokens),* ],
1232 check_constraints: &[ #(#checks_tokens),* ],
1233 composite_relations: &[ #(#composite_fk_tokens),* ],
1234 generic_relations: &[ #(#generic_fk_tokens),* ],
1235 };
1236 }
1237 }
1238}
1239
1240fn admin_config_tokens(admin: Option<&AdminAttrs>) -> TokenStream2 {
1244 let Some(admin) = admin else {
1245 return quote!(::core::option::Option::None);
1246 };
1247
1248 let list_display = admin
1249 .list_display
1250 .as_ref()
1251 .map(|(v, _)| v.as_slice())
1252 .unwrap_or(&[]);
1253 let list_display_lits = list_display.iter().map(|s| s.as_str());
1254
1255 let search_fields = admin
1256 .search_fields
1257 .as_ref()
1258 .map(|(v, _)| v.as_slice())
1259 .unwrap_or(&[]);
1260 let search_fields_lits = search_fields.iter().map(|s| s.as_str());
1261
1262 let readonly_fields = admin
1263 .readonly_fields
1264 .as_ref()
1265 .map(|(v, _)| v.as_slice())
1266 .unwrap_or(&[]);
1267 let readonly_fields_lits = readonly_fields.iter().map(|s| s.as_str());
1268
1269 let list_filter = admin
1270 .list_filter
1271 .as_ref()
1272 .map(|(v, _)| v.as_slice())
1273 .unwrap_or(&[]);
1274 let list_filter_lits = list_filter.iter().map(|s| s.as_str());
1275
1276 let actions = admin
1277 .actions
1278 .as_ref()
1279 .map(|(v, _)| v.as_slice())
1280 .unwrap_or(&[]);
1281 let actions_lits = actions.iter().map(|s| s.as_str());
1282
1283 let fieldsets = admin
1284 .fieldsets
1285 .as_ref()
1286 .map(|(v, _)| v.as_slice())
1287 .unwrap_or(&[]);
1288 let fieldset_tokens = fieldsets.iter().map(|(title, fields)| {
1289 let title = title.as_str();
1290 let field_lits = fields.iter().map(|s| s.as_str());
1291 quote!(::rustango::core::Fieldset {
1292 title: #title,
1293 fields: &[ #( #field_lits ),* ],
1294 })
1295 });
1296
1297 let list_per_page = admin.list_per_page.unwrap_or(0);
1298
1299 let ordering_pairs = admin
1300 .ordering
1301 .as_ref()
1302 .map(|(v, _)| v.as_slice())
1303 .unwrap_or(&[]);
1304 let ordering_tokens = ordering_pairs.iter().map(|(name, desc)| {
1305 let name = name.as_str();
1306 let desc = *desc;
1307 quote!((#name, #desc))
1308 });
1309
1310 quote! {
1311 ::core::option::Option::Some(&::rustango::core::AdminConfig {
1312 list_display: &[ #( #list_display_lits ),* ],
1313 search_fields: &[ #( #search_fields_lits ),* ],
1314 list_per_page: #list_per_page,
1315 ordering: &[ #( #ordering_tokens ),* ],
1316 readonly_fields: &[ #( #readonly_fields_lits ),* ],
1317 list_filter: &[ #( #list_filter_lits ),* ],
1318 actions: &[ #( #actions_lits ),* ],
1319 fieldsets: &[ #( #fieldset_tokens ),* ],
1320 })
1321 }
1322}
1323
1324fn inherent_impl_tokens(
1325 struct_name: &syn::Ident,
1326 fields: &CollectedFields,
1327 primary_key: Option<&(syn::Ident, String)>,
1328 column_consts: &TokenStream2,
1329 audited_fields: Option<&[&ColumnEntry]>,
1330) -> TokenStream2 {
1331 let executor_passes_to_data_write = if audited_fields.is_some() {
1337 quote!(&mut *_executor)
1338 } else {
1339 quote!(_executor)
1340 };
1341 let executor_param = if audited_fields.is_some() {
1342 quote!(_executor: &mut ::rustango::sql::sqlx::PgConnection)
1343 } else {
1344 quote!(_executor: _E)
1345 };
1346 let executor_generics = if audited_fields.is_some() {
1347 quote!()
1348 } else {
1349 quote!(<'_c, _E>)
1350 };
1351 let executor_where = if audited_fields.is_some() {
1352 quote!()
1353 } else {
1354 quote! {
1355 where
1356 _E: ::rustango::sql::sqlx::Executor<'_c, Database = ::rustango::sql::sqlx::Postgres>,
1357 }
1358 };
1359 let pool_to_save_on = if audited_fields.is_some() {
1364 quote! {
1365 let mut _conn = pool.acquire().await?;
1366 self.save_on(&mut *_conn).await
1367 }
1368 } else {
1369 quote!(self.save_on(pool).await)
1370 };
1371 let pool_to_insert_on = if audited_fields.is_some() {
1372 quote! {
1373 let mut _conn = pool.acquire().await?;
1374 self.insert_on(&mut *_conn).await
1375 }
1376 } else {
1377 quote!(self.insert_on(pool).await)
1378 };
1379 let pool_to_delete_on = if audited_fields.is_some() {
1380 quote! {
1381 let mut _conn = pool.acquire().await?;
1382 self.delete_on(&mut *_conn).await
1383 }
1384 } else {
1385 quote!(self.delete_on(pool).await)
1386 };
1387 let pool_to_bulk_insert_on = if audited_fields.is_some() {
1388 quote! {
1389 let mut _conn = pool.acquire().await?;
1390 Self::bulk_insert_on(rows, &mut *_conn).await
1391 }
1392 } else {
1393 quote!(Self::bulk_insert_on(rows, pool).await)
1394 };
1395 let pool_to_upsert_on = if audited_fields.is_some() {
1402 quote! {
1403 let mut _conn = pool.acquire().await?;
1404 self.upsert_on(&mut *_conn).await
1405 }
1406 } else {
1407 quote!(self.upsert_on(pool).await)
1408 };
1409
1410 let pool_insert_method = if audited_fields.is_some() && !fields.has_auto {
1428 quote!()
1437 } else if audited_fields.is_some() && fields.has_auto {
1438 quote!()
1441 } else if fields.has_auto {
1442 let pushes = &fields.insert_pushes;
1443 let returning_cols = &fields.returning_cols;
1444 quote! {
1445 pub async fn insert_pool(
1451 &mut self,
1452 pool: &::rustango::sql::Pool,
1453 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
1454 let mut _columns: ::std::vec::Vec<&'static str> =
1455 ::std::vec::Vec::new();
1456 let mut _values: ::std::vec::Vec<::rustango::core::SqlValue> =
1457 ::std::vec::Vec::new();
1458 #( #pushes )*
1459 let _query = ::rustango::core::InsertQuery {
1460 model: <Self as ::rustango::core::Model>::SCHEMA,
1461 columns: _columns,
1462 values: _values,
1463 returning: ::std::vec![ #( #returning_cols ),* ],
1464 on_conflict: ::core::option::Option::None,
1465 };
1466 let _result = ::rustango::sql::insert_returning_pool(
1467 pool, &_query,
1468 ).await?;
1469 ::rustango::sql::apply_auto_pk_pool(_result, self)
1470 }
1471 }
1472 } else {
1473 let insert_columns = &fields.insert_columns;
1474 let insert_values = &fields.insert_values;
1475 quote! {
1476 pub async fn insert_pool(
1483 &self,
1484 pool: &::rustango::sql::Pool,
1485 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
1486 let _query = ::rustango::core::InsertQuery {
1487 model: <Self as ::rustango::core::Model>::SCHEMA,
1488 columns: ::std::vec![ #( #insert_columns ),* ],
1489 values: ::std::vec![ #( #insert_values ),* ],
1490 returning: ::std::vec::Vec::new(),
1491 on_conflict: ::core::option::Option::None,
1492 };
1493 ::rustango::sql::insert_pool(pool, &_query).await
1494 }
1495 }
1496 };
1497
1498 let audit_pair_tokens: Vec<TokenStream2> = audited_fields
1511 .map(|tracked| {
1512 tracked
1513 .iter()
1514 .map(|c| {
1515 let column_lit = c.column.as_str();
1516 let ident = &c.ident;
1517 quote! {
1518 (
1519 #column_lit,
1520 ::serde_json::to_value(&self.#ident)
1521 .unwrap_or(::serde_json::Value::Null),
1522 )
1523 }
1524 })
1525 .collect()
1526 })
1527 .unwrap_or_default();
1528 let audit_pk_to_string = if let Some((pk_ident, _)) = primary_key {
1529 if fields.pk_is_auto {
1530 quote!(self.#pk_ident.get().map(|v| ::std::format!("{}", v)).unwrap_or_default())
1531 } else {
1532 quote!(::std::format!("{}", &self.#pk_ident))
1533 }
1534 } else {
1535 quote!(::std::string::String::new())
1536 };
1537 let make_op_emit = |op_path: TokenStream2| -> TokenStream2 {
1538 if audited_fields.is_some() {
1539 let pairs = audit_pair_tokens.iter();
1540 let pk_str = audit_pk_to_string.clone();
1541 quote! {
1542 let _audit_entry = ::rustango::audit::PendingEntry {
1543 entity_table: <Self as ::rustango::core::Model>::SCHEMA.table,
1544 entity_pk: #pk_str,
1545 operation: #op_path,
1546 source: ::rustango::audit::current_source(),
1547 changes: ::rustango::audit::snapshot_changes(&[
1548 #( #pairs ),*
1549 ]),
1550 };
1551 ::rustango::audit::emit_one(&mut *_executor, &_audit_entry).await?;
1552 }
1553 } else {
1554 quote!()
1555 }
1556 };
1557 let audit_insert_emit = make_op_emit(quote!(::rustango::audit::AuditOp::Create));
1558 let audit_delete_emit = make_op_emit(quote!(::rustango::audit::AuditOp::Delete));
1559 let audit_softdelete_emit = make_op_emit(quote!(::rustango::audit::AuditOp::SoftDelete));
1560 let audit_restore_emit = make_op_emit(quote!(::rustango::audit::AuditOp::Restore));
1561
1562 let pool_save_method = if let Some((pk_ident, pk_col)) = primary_key {
1578 let pk_column_lit = pk_col.as_str();
1579 let assignments = &fields.update_assignments;
1580 if audited_fields.is_some() {
1581 if fields.pk_is_auto {
1582 quote!()
1586 } else {
1587 let pairs = audit_pair_tokens.iter();
1588 let pk_str = audit_pk_to_string.clone();
1589 quote! {
1590 pub async fn save_pool(
1604 &mut self,
1605 pool: &::rustango::sql::Pool,
1606 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
1607 let _query = ::rustango::core::UpdateQuery {
1608 model: <Self as ::rustango::core::Model>::SCHEMA,
1609 set: ::std::vec![ #( #assignments ),* ],
1610 where_clause: ::rustango::core::WhereExpr::Predicate(
1611 ::rustango::core::Filter {
1612 column: #pk_column_lit,
1613 op: ::rustango::core::Op::Eq,
1614 value: ::core::convert::Into::<::rustango::core::SqlValue>::into(
1615 ::core::clone::Clone::clone(&self.#pk_ident)
1616 ),
1617 }
1618 ),
1619 };
1620 let _audit_entry = ::rustango::audit::PendingEntry {
1621 entity_table: <Self as ::rustango::core::Model>::SCHEMA.table,
1622 entity_pk: #pk_str,
1623 operation: ::rustango::audit::AuditOp::Update,
1624 source: ::rustango::audit::current_source(),
1625 changes: ::rustango::audit::snapshot_changes(&[
1626 #( #pairs ),*
1627 ]),
1628 };
1629 let _ = ::rustango::audit::save_one_with_audit_pool(
1630 pool, &_query, &_audit_entry,
1631 ).await?;
1632 ::core::result::Result::Ok(())
1633 }
1634 }
1635 }
1636 } else {
1637 let dispatch_unset = if fields.pk_is_auto {
1638 quote! {
1639 if matches!(self.#pk_ident, ::rustango::sql::Auto::Unset) {
1640 return self.insert_pool(pool).await;
1641 }
1642 }
1643 } else {
1644 quote!()
1645 };
1646 quote! {
1647 pub async fn save_pool(
1654 &mut self,
1655 pool: &::rustango::sql::Pool,
1656 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
1657 #dispatch_unset
1658 let _query = ::rustango::core::UpdateQuery {
1659 model: <Self as ::rustango::core::Model>::SCHEMA,
1660 set: ::std::vec![ #( #assignments ),* ],
1661 where_clause: ::rustango::core::WhereExpr::Predicate(
1662 ::rustango::core::Filter {
1663 column: #pk_column_lit,
1664 op: ::rustango::core::Op::Eq,
1665 value: ::core::convert::Into::<::rustango::core::SqlValue>::into(
1666 ::core::clone::Clone::clone(&self.#pk_ident)
1667 ),
1668 }
1669 ),
1670 };
1671 let _ = ::rustango::sql::update_pool(pool, &_query).await?;
1672 ::core::result::Result::Ok(())
1673 }
1674 }
1675 }
1676 } else {
1677 quote!()
1678 };
1679
1680 let pool_insert_method = if audited_fields.is_some() {
1687 if let Some(_) = primary_key {
1688 let pushes = if fields.has_auto {
1689 fields.insert_pushes.clone()
1690 } else {
1691 fields
1696 .insert_columns
1697 .iter()
1698 .zip(&fields.insert_values)
1699 .map(|(col, val)| {
1700 quote! {
1701 _columns.push(#col);
1702 _values.push(#val);
1703 }
1704 })
1705 .collect()
1706 };
1707 let returning_cols: Vec<proc_macro2::TokenStream> = if fields.has_auto {
1708 fields.returning_cols.clone()
1709 } else {
1710 primary_key
1717 .map(|(_, col)| {
1718 let lit = col.as_str();
1719 vec![quote!(#lit)]
1720 })
1721 .unwrap_or_default()
1722 };
1723 let pairs = audit_pair_tokens.iter();
1724 let pk_str = audit_pk_to_string.clone();
1725 quote! {
1726 pub async fn insert_pool(
1735 &mut self,
1736 pool: &::rustango::sql::Pool,
1737 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
1738 let mut _columns: ::std::vec::Vec<&'static str> =
1739 ::std::vec::Vec::new();
1740 let mut _values: ::std::vec::Vec<::rustango::core::SqlValue> =
1741 ::std::vec::Vec::new();
1742 #( #pushes )*
1743 let _query = ::rustango::core::InsertQuery {
1744 model: <Self as ::rustango::core::Model>::SCHEMA,
1745 columns: _columns,
1746 values: _values,
1747 returning: ::std::vec![ #( #returning_cols ),* ],
1748 on_conflict: ::core::option::Option::None,
1749 };
1750 let _audit_entry = ::rustango::audit::PendingEntry {
1751 entity_table: <Self as ::rustango::core::Model>::SCHEMA.table,
1752 entity_pk: #pk_str,
1753 operation: ::rustango::audit::AuditOp::Create,
1754 source: ::rustango::audit::current_source(),
1755 changes: ::rustango::audit::snapshot_changes(&[
1756 #( #pairs ),*
1757 ]),
1758 };
1759 let _result = ::rustango::audit::insert_one_with_audit_pool(
1760 pool, &_query, &_audit_entry,
1761 ).await?;
1762 ::rustango::sql::apply_auto_pk_pool(_result, self)
1763 }
1764 }
1765 } else {
1766 quote!()
1767 }
1768 } else {
1769 pool_insert_method
1771 };
1772
1773 let pool_save_method = if let Some(tracked) = audited_fields {
1794 if let Some((pk_ident, pk_col)) = primary_key {
1795 let pk_column_lit = pk_col.as_str();
1796 let after_pairs_pg = audit_pair_tokens.iter().collect::<Vec<_>>();
1800 let pk_str = audit_pk_to_string.clone();
1801 let mk_before_pairs = |getter: proc_macro2::TokenStream| -> Vec<proc_macro2::TokenStream> {
1806 tracked
1807 .iter()
1808 .map(|c| {
1809 let column_lit = c.column.as_str();
1810 let value_ty = &c.value_ty;
1811 quote! {
1812 (
1813 #column_lit,
1814 match #getter::<#value_ty>(
1815 _audit_before_row, #column_lit,
1816 ) {
1817 ::core::result::Result::Ok(v) => {
1818 ::serde_json::to_value(&v)
1819 .unwrap_or(::serde_json::Value::Null)
1820 }
1821 ::core::result::Result::Err(_) => ::serde_json::Value::Null,
1822 },
1823 )
1824 }
1825 })
1826 .collect()
1827 };
1828 let before_pairs_pg: Vec<proc_macro2::TokenStream> =
1829 mk_before_pairs(quote!(::rustango::sql::try_get_returning));
1830 let before_pairs_my: Vec<proc_macro2::TokenStream> =
1831 mk_before_pairs(quote!(::rustango::sql::try_get_returning_my));
1832 let pg_select_cols: String = tracked
1833 .iter()
1834 .map(|c| format!("\"{}\"", c.column.replace('"', "\"\"")))
1835 .collect::<Vec<_>>()
1836 .join(", ");
1837 let my_select_cols: String = tracked
1838 .iter()
1839 .map(|c| format!("`{}`", c.column.replace('`', "``")))
1840 .collect::<Vec<_>>()
1841 .join(", ");
1842 let pk_value_for_bind = if fields.pk_is_auto {
1843 quote!(self.#pk_ident.get().copied().unwrap_or_default())
1844 } else {
1845 quote!(::core::clone::Clone::clone(&self.#pk_ident))
1846 };
1847 let assignments = &fields.update_assignments;
1848 let unset_dispatch = if fields.has_auto {
1849 quote! {
1850 if matches!(self.#pk_ident, ::rustango::sql::Auto::Unset) {
1851 return self.insert_pool(pool).await;
1852 }
1853 }
1854 } else {
1855 quote!()
1856 };
1857 quote! {
1858 pub async fn save_pool(
1872 &mut self,
1873 pool: &::rustango::sql::Pool,
1874 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
1875 #unset_dispatch
1876 let _query = ::rustango::core::UpdateQuery {
1877 model: <Self as ::rustango::core::Model>::SCHEMA,
1878 set: ::std::vec![ #( #assignments ),* ],
1879 where_clause: ::rustango::core::WhereExpr::Predicate(
1880 ::rustango::core::Filter {
1881 column: #pk_column_lit,
1882 op: ::rustango::core::Op::Eq,
1883 value: ::core::convert::Into::<::rustango::core::SqlValue>::into(
1884 ::core::clone::Clone::clone(&self.#pk_ident)
1885 ),
1886 }
1887 ),
1888 };
1889 let _after_pairs: ::std::vec::Vec<(&'static str, ::serde_json::Value)> =
1890 ::std::vec![ #( #after_pairs_pg ),* ];
1891 ::rustango::audit::save_one_with_diff_pool(
1892 pool,
1893 &_query,
1894 #pk_column_lit,
1895 ::core::convert::Into::<::rustango::core::SqlValue>::into(
1896 #pk_value_for_bind,
1897 ),
1898 <Self as ::rustango::core::Model>::SCHEMA.table,
1899 #pk_str,
1900 _after_pairs,
1901 #pg_select_cols,
1902 #my_select_cols,
1903 |_audit_before_row| ::std::vec![ #( #before_pairs_pg ),* ],
1904 |_audit_before_row| ::std::vec![ #( #before_pairs_my ),* ],
1905 ).await
1906 }
1907 }
1908 } else {
1909 quote!()
1910 }
1911 } else {
1912 pool_save_method
1913 };
1914
1915 let pool_delete_method = {
1922 let pk_column_lit = primary_key
1923 .map(|(_, col)| col.as_str())
1924 .unwrap_or("id");
1925 let pk_ident_for_pool = primary_key.map(|(ident, _)| ident);
1926 if let Some(pk_ident) = pk_ident_for_pool {
1927 if audited_fields.is_some() {
1928 let pairs = audit_pair_tokens.iter();
1929 let pk_str = audit_pk_to_string.clone();
1930 quote! {
1931 pub async fn delete_pool(
1938 &self,
1939 pool: &::rustango::sql::Pool,
1940 ) -> ::core::result::Result<u64, ::rustango::sql::ExecError> {
1941 let _query = ::rustango::core::DeleteQuery {
1942 model: <Self as ::rustango::core::Model>::SCHEMA,
1943 where_clause: ::rustango::core::WhereExpr::Predicate(
1944 ::rustango::core::Filter {
1945 column: #pk_column_lit,
1946 op: ::rustango::core::Op::Eq,
1947 value: ::core::convert::Into::<::rustango::core::SqlValue>::into(
1948 ::core::clone::Clone::clone(&self.#pk_ident)
1949 ),
1950 }
1951 ),
1952 };
1953 let _audit_entry = ::rustango::audit::PendingEntry {
1954 entity_table: <Self as ::rustango::core::Model>::SCHEMA.table,
1955 entity_pk: #pk_str,
1956 operation: ::rustango::audit::AuditOp::Delete,
1957 source: ::rustango::audit::current_source(),
1958 changes: ::rustango::audit::snapshot_changes(&[
1959 #( #pairs ),*
1960 ]),
1961 };
1962 ::rustango::audit::delete_one_with_audit_pool(
1963 pool, &_query, &_audit_entry,
1964 ).await
1965 }
1966 }
1967 } else {
1968 quote! {
1969 pub async fn delete_pool(
1976 &self,
1977 pool: &::rustango::sql::Pool,
1978 ) -> ::core::result::Result<u64, ::rustango::sql::ExecError> {
1979 let _query = ::rustango::core::DeleteQuery {
1980 model: <Self as ::rustango::core::Model>::SCHEMA,
1981 where_clause: ::rustango::core::WhereExpr::Predicate(
1982 ::rustango::core::Filter {
1983 column: #pk_column_lit,
1984 op: ::rustango::core::Op::Eq,
1985 value: ::core::convert::Into::<::rustango::core::SqlValue>::into(
1986 ::core::clone::Clone::clone(&self.#pk_ident)
1987 ),
1988 }
1989 ),
1990 };
1991 ::rustango::sql::delete_pool(pool, &_query).await
1992 }
1993 }
1994 }
1995 } else {
1996 quote!()
1997 }
1998 };
1999
2000 let (audit_update_pre, audit_update_post): (TokenStream2, TokenStream2) =
2010 if let Some(tracked) = audited_fields {
2011 if tracked.is_empty() {
2012 (quote!(), quote!())
2013 } else {
2014 let select_cols: String = tracked
2015 .iter()
2016 .map(|c| format!("\"{}\"", c.column.replace('"', "\"\"")))
2017 .collect::<Vec<_>>()
2018 .join(", ");
2019 let pk_column_for_select = primary_key
2020 .map(|(_, col)| col.clone())
2021 .unwrap_or_default();
2022 let select_cols_lit = select_cols;
2023 let pk_column_lit_for_select = pk_column_for_select;
2024 let pk_value_for_bind = if let Some((pk_ident, _)) = primary_key {
2025 if fields.pk_is_auto {
2026 quote!(self.#pk_ident.get().copied().unwrap_or_default())
2027 } else {
2028 quote!(::core::clone::Clone::clone(&self.#pk_ident))
2029 }
2030 } else {
2031 quote!(0_i64)
2032 };
2033 let before_pairs = tracked.iter().map(|c| {
2034 let column_lit = c.column.as_str();
2035 let value_ty = &c.value_ty;
2036 quote! {
2037 (
2038 #column_lit,
2039 match ::rustango::sql::sqlx::Row::try_get::<#value_ty, _>(
2040 &_audit_before_row, #column_lit,
2041 ) {
2042 ::core::result::Result::Ok(v) => {
2043 ::serde_json::to_value(&v)
2044 .unwrap_or(::serde_json::Value::Null)
2045 }
2046 ::core::result::Result::Err(_) => ::serde_json::Value::Null,
2047 },
2048 )
2049 }
2050 });
2051 let after_pairs = tracked.iter().map(|c| {
2052 let column_lit = c.column.as_str();
2053 let ident = &c.ident;
2054 quote! {
2055 (
2056 #column_lit,
2057 ::serde_json::to_value(&self.#ident)
2058 .unwrap_or(::serde_json::Value::Null),
2059 )
2060 }
2061 });
2062 let pk_str = audit_pk_to_string.clone();
2063 let pre = quote! {
2064 let _audit_select_sql = ::std::format!(
2065 r#"SELECT {} FROM "{}" WHERE "{}" = $1"#,
2066 #select_cols_lit,
2067 <Self as ::rustango::core::Model>::SCHEMA.table,
2068 #pk_column_lit_for_select,
2069 );
2070 let _audit_before_pairs:
2071 ::std::option::Option<::std::vec::Vec<(&'static str, ::serde_json::Value)>> =
2072 match ::rustango::sql::sqlx::query(&_audit_select_sql)
2073 .bind(#pk_value_for_bind)
2074 .fetch_optional(&mut *_executor)
2075 .await
2076 {
2077 ::core::result::Result::Ok(::core::option::Option::Some(_audit_before_row)) => {
2078 ::core::option::Option::Some(::std::vec![ #( #before_pairs ),* ])
2079 }
2080 _ => ::core::option::Option::None,
2081 };
2082 };
2083 let post = quote! {
2084 if let ::core::option::Option::Some(_audit_before) = _audit_before_pairs {
2085 let _audit_after:
2086 ::std::vec::Vec<(&'static str, ::serde_json::Value)> =
2087 ::std::vec![ #( #after_pairs ),* ];
2088 let _audit_entry = ::rustango::audit::PendingEntry {
2089 entity_table: <Self as ::rustango::core::Model>::SCHEMA.table,
2090 entity_pk: #pk_str,
2091 operation: ::rustango::audit::AuditOp::Update,
2092 source: ::rustango::audit::current_source(),
2093 changes: ::rustango::audit::diff_changes(
2094 &_audit_before,
2095 &_audit_after,
2096 ),
2097 };
2098 ::rustango::audit::emit_one(&mut *_executor, &_audit_entry).await?;
2099 }
2100 };
2101 (pre, post)
2102 }
2103 } else {
2104 (quote!(), quote!())
2105 };
2106
2107 let audit_bulk_insert_emit: TokenStream2 = if audited_fields.is_some() {
2111 let row_pk_str = if let Some((pk_ident, _)) = primary_key {
2112 if fields.pk_is_auto {
2113 quote!(_row.#pk_ident.get().map(|v| ::std::format!("{}", v)).unwrap_or_default())
2114 } else {
2115 quote!(::std::format!("{}", &_row.#pk_ident))
2116 }
2117 } else {
2118 quote!(::std::string::String::new())
2119 };
2120 let row_pairs = audited_fields
2121 .unwrap_or(&[])
2122 .iter()
2123 .map(|c| {
2124 let column_lit = c.column.as_str();
2125 let ident = &c.ident;
2126 quote! {
2127 (
2128 #column_lit,
2129 ::serde_json::to_value(&_row.#ident)
2130 .unwrap_or(::serde_json::Value::Null),
2131 )
2132 }
2133 });
2134 quote! {
2135 let _audit_source = ::rustango::audit::current_source();
2136 let mut _audit_entries:
2137 ::std::vec::Vec<::rustango::audit::PendingEntry> =
2138 ::std::vec::Vec::with_capacity(rows.len());
2139 for _row in rows.iter() {
2140 _audit_entries.push(::rustango::audit::PendingEntry {
2141 entity_table: <Self as ::rustango::core::Model>::SCHEMA.table,
2142 entity_pk: #row_pk_str,
2143 operation: ::rustango::audit::AuditOp::Create,
2144 source: _audit_source.clone(),
2145 changes: ::rustango::audit::snapshot_changes(&[
2146 #( #row_pairs ),*
2147 ]),
2148 });
2149 }
2150 ::rustango::audit::emit_many(&mut *_executor, &_audit_entries).await?;
2151 }
2152 } else {
2153 quote!()
2154 };
2155
2156 let save_method = if fields.pk_is_auto {
2157 let (pk_ident, pk_column) = primary_key
2158 .expect("pk_is_auto implies primary_key is Some");
2159 let pk_column_lit = pk_column.as_str();
2160 let assignments = &fields.update_assignments;
2161 let upsert_cols = &fields.upsert_update_columns;
2162 let upsert_pushes = &fields.insert_pushes;
2163 let upsert_returning = &fields.returning_cols;
2164 let upsert_auto_assigns = &fields.auto_assigns;
2165 let conflict_clause = if fields.upsert_update_columns.is_empty() {
2166 quote!(::rustango::core::ConflictClause::DoNothing)
2167 } else {
2168 quote!(::rustango::core::ConflictClause::DoUpdate {
2169 target: ::std::vec![#pk_column_lit],
2170 update_columns: ::std::vec![ #( #upsert_cols ),* ],
2171 })
2172 };
2173 Some(quote! {
2174 pub async fn save(
2192 &mut self,
2193 pool: &::rustango::sql::sqlx::PgPool,
2194 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
2195 #pool_to_save_on
2196 }
2197
2198 pub async fn save_on #executor_generics (
2209 &mut self,
2210 #executor_param,
2211 ) -> ::core::result::Result<(), ::rustango::sql::ExecError>
2212 #executor_where
2213 {
2214 if matches!(self.#pk_ident, ::rustango::sql::Auto::Unset) {
2215 return self.insert_on(#executor_passes_to_data_write).await;
2216 }
2217 #audit_update_pre
2218 let _query = ::rustango::core::UpdateQuery {
2219 model: <Self as ::rustango::core::Model>::SCHEMA,
2220 set: ::std::vec![ #( #assignments ),* ],
2221 where_clause: ::rustango::core::WhereExpr::Predicate(
2222 ::rustango::core::Filter {
2223 column: #pk_column_lit,
2224 op: ::rustango::core::Op::Eq,
2225 value: ::core::convert::Into::<::rustango::core::SqlValue>::into(
2226 ::core::clone::Clone::clone(&self.#pk_ident)
2227 ),
2228 }
2229 ),
2230 };
2231 let _ = ::rustango::sql::update_on(
2232 #executor_passes_to_data_write,
2233 &_query,
2234 ).await?;
2235 #audit_update_post
2236 ::core::result::Result::Ok(())
2237 }
2238
2239 pub async fn save_on_with #executor_generics (
2250 &mut self,
2251 #executor_param,
2252 source: ::rustango::audit::AuditSource,
2253 ) -> ::core::result::Result<(), ::rustango::sql::ExecError>
2254 #executor_where
2255 {
2256 ::rustango::audit::with_source(source, self.save_on(_executor)).await
2257 }
2258
2259 pub async fn upsert(
2269 &mut self,
2270 pool: &::rustango::sql::sqlx::PgPool,
2271 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
2272 #pool_to_upsert_on
2273 }
2274
2275 pub async fn upsert_on #executor_generics (
2281 &mut self,
2282 #executor_param,
2283 ) -> ::core::result::Result<(), ::rustango::sql::ExecError>
2284 #executor_where
2285 {
2286 let mut _columns: ::std::vec::Vec<&'static str> =
2287 ::std::vec::Vec::new();
2288 let mut _values: ::std::vec::Vec<::rustango::core::SqlValue> =
2289 ::std::vec::Vec::new();
2290 #( #upsert_pushes )*
2291 let query = ::rustango::core::InsertQuery {
2292 model: <Self as ::rustango::core::Model>::SCHEMA,
2293 columns: _columns,
2294 values: _values,
2295 returning: ::std::vec![ #( #upsert_returning ),* ],
2296 on_conflict: ::core::option::Option::Some(#conflict_clause),
2297 };
2298 let _returning_row_v = ::rustango::sql::insert_returning_on(
2299 #executor_passes_to_data_write,
2300 &query,
2301 ).await?;
2302 let _returning_row = &_returning_row_v;
2303 #( #upsert_auto_assigns )*
2304 ::core::result::Result::Ok(())
2305 }
2306 })
2307 } else {
2308 None
2309 };
2310
2311 let pk_methods = primary_key.map(|(pk_ident, pk_column)| {
2312 let pk_column_lit = pk_column.as_str();
2313 let soft_delete_methods = if let Some(col) = fields.soft_delete_column.as_deref() {
2320 let col_lit = col;
2321 quote! {
2322 pub async fn soft_delete_on #executor_generics (
2332 &self,
2333 #executor_param,
2334 ) -> ::core::result::Result<u64, ::rustango::sql::ExecError>
2335 #executor_where
2336 {
2337 let _query = ::rustango::core::UpdateQuery {
2338 model: <Self as ::rustango::core::Model>::SCHEMA,
2339 set: ::std::vec![
2340 ::rustango::core::Assignment {
2341 column: #col_lit,
2342 value: ::core::convert::Into::<::rustango::core::SqlValue>::into(
2343 ::chrono::Utc::now()
2344 ),
2345 },
2346 ],
2347 where_clause: ::rustango::core::WhereExpr::Predicate(
2348 ::rustango::core::Filter {
2349 column: #pk_column_lit,
2350 op: ::rustango::core::Op::Eq,
2351 value: ::core::convert::Into::<::rustango::core::SqlValue>::into(
2352 ::core::clone::Clone::clone(&self.#pk_ident)
2353 ),
2354 }
2355 ),
2356 };
2357 let _affected = ::rustango::sql::update_on(
2358 #executor_passes_to_data_write,
2359 &_query,
2360 ).await?;
2361 #audit_softdelete_emit
2362 ::core::result::Result::Ok(_affected)
2363 }
2364
2365 pub async fn restore_on #executor_generics (
2372 &self,
2373 #executor_param,
2374 ) -> ::core::result::Result<u64, ::rustango::sql::ExecError>
2375 #executor_where
2376 {
2377 let _query = ::rustango::core::UpdateQuery {
2378 model: <Self as ::rustango::core::Model>::SCHEMA,
2379 set: ::std::vec![
2380 ::rustango::core::Assignment {
2381 column: #col_lit,
2382 value: ::rustango::core::SqlValue::Null,
2383 },
2384 ],
2385 where_clause: ::rustango::core::WhereExpr::Predicate(
2386 ::rustango::core::Filter {
2387 column: #pk_column_lit,
2388 op: ::rustango::core::Op::Eq,
2389 value: ::core::convert::Into::<::rustango::core::SqlValue>::into(
2390 ::core::clone::Clone::clone(&self.#pk_ident)
2391 ),
2392 }
2393 ),
2394 };
2395 let _affected = ::rustango::sql::update_on(
2396 #executor_passes_to_data_write,
2397 &_query,
2398 ).await?;
2399 #audit_restore_emit
2400 ::core::result::Result::Ok(_affected)
2401 }
2402 }
2403 } else {
2404 quote!()
2405 };
2406 quote! {
2407 pub async fn delete(
2415 &self,
2416 pool: &::rustango::sql::sqlx::PgPool,
2417 ) -> ::core::result::Result<u64, ::rustango::sql::ExecError> {
2418 #pool_to_delete_on
2419 }
2420
2421 pub async fn delete_on #executor_generics (
2428 &self,
2429 #executor_param,
2430 ) -> ::core::result::Result<u64, ::rustango::sql::ExecError>
2431 #executor_where
2432 {
2433 let query = ::rustango::core::DeleteQuery {
2434 model: <Self as ::rustango::core::Model>::SCHEMA,
2435 where_clause: ::rustango::core::WhereExpr::Predicate(
2436 ::rustango::core::Filter {
2437 column: #pk_column_lit,
2438 op: ::rustango::core::Op::Eq,
2439 value: ::core::convert::Into::<::rustango::core::SqlValue>::into(
2440 ::core::clone::Clone::clone(&self.#pk_ident)
2441 ),
2442 }
2443 ),
2444 };
2445 let _affected = ::rustango::sql::delete_on(
2446 #executor_passes_to_data_write,
2447 &query,
2448 ).await?;
2449 #audit_delete_emit
2450 ::core::result::Result::Ok(_affected)
2451 }
2452
2453 pub async fn delete_on_with #executor_generics (
2459 &self,
2460 #executor_param,
2461 source: ::rustango::audit::AuditSource,
2462 ) -> ::core::result::Result<u64, ::rustango::sql::ExecError>
2463 #executor_where
2464 {
2465 ::rustango::audit::with_source(source, self.delete_on(_executor)).await
2466 }
2467 #pool_delete_method
2468 #pool_insert_method
2469 #pool_save_method
2470 #soft_delete_methods
2471 }
2472 });
2473
2474 let insert_method = if fields.has_auto {
2475 let pushes = &fields.insert_pushes;
2476 let returning_cols = &fields.returning_cols;
2477 let auto_assigns = &fields.auto_assigns;
2478 quote! {
2479 pub async fn insert(
2488 &mut self,
2489 pool: &::rustango::sql::sqlx::PgPool,
2490 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
2491 #pool_to_insert_on
2492 }
2493
2494 pub async fn insert_on #executor_generics (
2500 &mut self,
2501 #executor_param,
2502 ) -> ::core::result::Result<(), ::rustango::sql::ExecError>
2503 #executor_where
2504 {
2505 let mut _columns: ::std::vec::Vec<&'static str> =
2506 ::std::vec::Vec::new();
2507 let mut _values: ::std::vec::Vec<::rustango::core::SqlValue> =
2508 ::std::vec::Vec::new();
2509 #( #pushes )*
2510 let query = ::rustango::core::InsertQuery {
2511 model: <Self as ::rustango::core::Model>::SCHEMA,
2512 columns: _columns,
2513 values: _values,
2514 returning: ::std::vec![ #( #returning_cols ),* ],
2515 on_conflict: ::core::option::Option::None,
2516 };
2517 let _returning_row_v = ::rustango::sql::insert_returning_on(
2518 #executor_passes_to_data_write,
2519 &query,
2520 ).await?;
2521 let _returning_row = &_returning_row_v;
2522 #( #auto_assigns )*
2523 #audit_insert_emit
2524 ::core::result::Result::Ok(())
2525 }
2526
2527 pub async fn insert_on_with #executor_generics (
2533 &mut self,
2534 #executor_param,
2535 source: ::rustango::audit::AuditSource,
2536 ) -> ::core::result::Result<(), ::rustango::sql::ExecError>
2537 #executor_where
2538 {
2539 ::rustango::audit::with_source(source, self.insert_on(_executor)).await
2540 }
2541 }
2542 } else {
2543 let insert_columns = &fields.insert_columns;
2544 let insert_values = &fields.insert_values;
2545 quote! {
2546 pub async fn insert(
2552 &self,
2553 pool: &::rustango::sql::sqlx::PgPool,
2554 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
2555 self.insert_on(pool).await
2556 }
2557
2558 pub async fn insert_on<'_c, _E>(
2564 &self,
2565 _executor: _E,
2566 ) -> ::core::result::Result<(), ::rustango::sql::ExecError>
2567 where
2568 _E: ::rustango::sql::sqlx::Executor<'_c, Database = ::rustango::sql::sqlx::Postgres>,
2569 {
2570 let query = ::rustango::core::InsertQuery {
2571 model: <Self as ::rustango::core::Model>::SCHEMA,
2572 columns: ::std::vec![ #( #insert_columns ),* ],
2573 values: ::std::vec![ #( #insert_values ),* ],
2574 returning: ::std::vec::Vec::new(),
2575 on_conflict: ::core::option::Option::None,
2576 };
2577 ::rustango::sql::insert_on(_executor, &query).await
2578 }
2579 }
2580 };
2581
2582 let bulk_insert_method = if fields.has_auto {
2583 let cols_no_auto = &fields.bulk_columns_no_auto;
2584 let cols_all = &fields.bulk_columns_all;
2585 let pushes_no_auto = &fields.bulk_pushes_no_auto;
2586 let pushes_all = &fields.bulk_pushes_all;
2587 let returning_cols = &fields.returning_cols;
2588 let auto_assigns_for_row = bulk_auto_assigns_for_row(fields);
2589 let uniformity = &fields.bulk_auto_uniformity;
2590 let first_auto_ident = fields
2591 .first_auto_ident
2592 .as_ref()
2593 .expect("has_auto implies first_auto_ident is Some");
2594 quote! {
2595 pub async fn bulk_insert(
2609 rows: &mut [Self],
2610 pool: &::rustango::sql::sqlx::PgPool,
2611 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
2612 #pool_to_bulk_insert_on
2613 }
2614
2615 pub async fn bulk_insert_on #executor_generics (
2621 rows: &mut [Self],
2622 #executor_param,
2623 ) -> ::core::result::Result<(), ::rustango::sql::ExecError>
2624 #executor_where
2625 {
2626 if rows.is_empty() {
2627 return ::core::result::Result::Ok(());
2628 }
2629 let _first_unset = matches!(
2630 rows[0].#first_auto_ident,
2631 ::rustango::sql::Auto::Unset
2632 );
2633 #( #uniformity )*
2634
2635 let mut _all_rows: ::std::vec::Vec<
2636 ::std::vec::Vec<::rustango::core::SqlValue>,
2637 > = ::std::vec::Vec::with_capacity(rows.len());
2638 let _columns: ::std::vec::Vec<&'static str> = if _first_unset {
2639 for _row in rows.iter() {
2640 let mut _row_vals: ::std::vec::Vec<::rustango::core::SqlValue> =
2641 ::std::vec::Vec::new();
2642 #( #pushes_no_auto )*
2643 _all_rows.push(_row_vals);
2644 }
2645 ::std::vec![ #( #cols_no_auto ),* ]
2646 } else {
2647 for _row in rows.iter() {
2648 let mut _row_vals: ::std::vec::Vec<::rustango::core::SqlValue> =
2649 ::std::vec::Vec::new();
2650 #( #pushes_all )*
2651 _all_rows.push(_row_vals);
2652 }
2653 ::std::vec![ #( #cols_all ),* ]
2654 };
2655
2656 let _query = ::rustango::core::BulkInsertQuery {
2657 model: <Self as ::rustango::core::Model>::SCHEMA,
2658 columns: _columns,
2659 rows: _all_rows,
2660 returning: ::std::vec![ #( #returning_cols ),* ],
2661 on_conflict: ::core::option::Option::None,
2662 };
2663 let _returned = ::rustango::sql::bulk_insert_on(
2664 #executor_passes_to_data_write,
2665 &_query,
2666 ).await?;
2667 if _returned.len() != rows.len() {
2668 return ::core::result::Result::Err(
2669 ::rustango::sql::ExecError::Sql(
2670 ::rustango::sql::SqlError::BulkInsertReturningMismatch {
2671 expected: rows.len(),
2672 actual: _returned.len(),
2673 }
2674 )
2675 );
2676 }
2677 for (_returning_row, _row_mut) in _returned.iter().zip(rows.iter_mut()) {
2678 #auto_assigns_for_row
2679 }
2680 #audit_bulk_insert_emit
2681 ::core::result::Result::Ok(())
2682 }
2683 }
2684 } else {
2685 let cols_all = &fields.bulk_columns_all;
2686 let pushes_all = &fields.bulk_pushes_all;
2687 quote! {
2688 pub async fn bulk_insert(
2698 rows: &[Self],
2699 pool: &::rustango::sql::sqlx::PgPool,
2700 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
2701 Self::bulk_insert_on(rows, pool).await
2702 }
2703
2704 pub async fn bulk_insert_on<'_c, _E>(
2710 rows: &[Self],
2711 _executor: _E,
2712 ) -> ::core::result::Result<(), ::rustango::sql::ExecError>
2713 where
2714 _E: ::rustango::sql::sqlx::Executor<'_c, Database = ::rustango::sql::sqlx::Postgres>,
2715 {
2716 if rows.is_empty() {
2717 return ::core::result::Result::Ok(());
2718 }
2719 let mut _all_rows: ::std::vec::Vec<
2720 ::std::vec::Vec<::rustango::core::SqlValue>,
2721 > = ::std::vec::Vec::with_capacity(rows.len());
2722 for _row in rows.iter() {
2723 let mut _row_vals: ::std::vec::Vec<::rustango::core::SqlValue> =
2724 ::std::vec::Vec::new();
2725 #( #pushes_all )*
2726 _all_rows.push(_row_vals);
2727 }
2728 let _query = ::rustango::core::BulkInsertQuery {
2729 model: <Self as ::rustango::core::Model>::SCHEMA,
2730 columns: ::std::vec![ #( #cols_all ),* ],
2731 rows: _all_rows,
2732 returning: ::std::vec::Vec::new(),
2733 on_conflict: ::core::option::Option::None,
2734 };
2735 let _ = ::rustango::sql::bulk_insert_on(_executor, &_query).await?;
2736 ::core::result::Result::Ok(())
2737 }
2738 }
2739 };
2740
2741 let pk_value_helper = primary_key.map(|(pk_ident, _)| {
2742 quote! {
2743 #[doc(hidden)]
2748 pub fn __rustango_pk_value(&self) -> ::rustango::core::SqlValue {
2749 ::core::convert::Into::<::rustango::core::SqlValue>::into(
2750 ::core::clone::Clone::clone(&self.#pk_ident)
2751 )
2752 }
2753 }
2754 });
2755
2756 let has_pk_value_impl = primary_key.map(|(pk_ident, _)| {
2757 quote! {
2758 impl ::rustango::sql::HasPkValue for #struct_name {
2759 fn __rustango_pk_value_impl(&self) -> ::rustango::core::SqlValue {
2760 ::core::convert::Into::<::rustango::core::SqlValue>::into(
2761 ::core::clone::Clone::clone(&self.#pk_ident)
2762 )
2763 }
2764 }
2765 }
2766 });
2767
2768 let fk_pk_access_impl = fk_pk_access_impl_tokens(struct_name, &fields.fk_relations);
2769
2770 let assign_auto_pk_pool_impl = {
2776 let auto_assigns = &fields.auto_assigns;
2777 let mysql_body = if let Some(first) = fields.first_auto_ident.as_ref() {
2778 let value_ty = fields
2796 .first_auto_value_ty
2797 .as_ref()
2798 .expect("first_auto_value_ty set whenever first_auto_ident is");
2799 quote! {
2800 let _converted = <#value_ty as ::rustango::sql::MysqlAutoIdSet>
2801 ::rustango_from_mysql_auto_id(_id)?;
2802 self.#first = ::rustango::sql::Auto::Set(_converted);
2803 ::core::result::Result::Ok(())
2804 }
2805 } else {
2806 quote! {
2807 let _ = _id;
2808 ::core::result::Result::Ok(())
2809 }
2810 };
2811 quote! {
2812 impl ::rustango::sql::AssignAutoPkPool for #struct_name {
2813 fn __rustango_assign_from_pg_row(
2814 &mut self,
2815 _returning_row: &::rustango::sql::PgReturningRow,
2816 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
2817 #( #auto_assigns )*
2818 ::core::result::Result::Ok(())
2819 }
2820 fn __rustango_assign_from_mysql_id(
2821 &mut self,
2822 _id: i64,
2823 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
2824 #mysql_body
2825 }
2826 }
2827 }
2828 };
2829
2830 let from_aliased_row_inits = &fields.from_aliased_row_inits;
2831 let aliased_row_helper = quote! {
2832 #[doc(hidden)]
2838 pub fn __rustango_from_aliased_row(
2839 row: &::rustango::sql::sqlx::postgres::PgRow,
2840 prefix: &str,
2841 ) -> ::core::result::Result<Self, ::rustango::sql::sqlx::Error> {
2842 ::core::result::Result::Ok(Self {
2843 #( #from_aliased_row_inits ),*
2844 })
2845 }
2846 };
2847 let aliased_row_helper_my = quote! {
2850 ::rustango::__impl_my_aliased_row_decoder!(#struct_name, |row, prefix| {
2851 #( #from_aliased_row_inits ),*
2852 });
2853 };
2854
2855 let load_related_impl =
2856 load_related_impl_tokens(struct_name, &fields.fk_relations);
2857 let load_related_impl_my =
2858 load_related_impl_my_tokens(struct_name, &fields.fk_relations);
2859
2860 quote! {
2861 impl #struct_name {
2862 #[must_use]
2864 pub fn objects() -> ::rustango::query::QuerySet<#struct_name> {
2865 ::rustango::query::QuerySet::new()
2866 }
2867
2868 #insert_method
2869
2870 #bulk_insert_method
2871
2872 #save_method
2873
2874 #pk_methods
2875
2876 #pk_value_helper
2877
2878 #aliased_row_helper
2879
2880 #column_consts
2881 }
2882
2883 #aliased_row_helper_my
2884
2885 #load_related_impl
2886
2887 #load_related_impl_my
2888
2889 #has_pk_value_impl
2890
2891 #fk_pk_access_impl
2892
2893 #assign_auto_pk_pool_impl
2894 }
2895}
2896
2897fn bulk_auto_assigns_for_row(fields: &CollectedFields) -> TokenStream2 {
2901 let lines = fields.auto_field_idents.iter().map(|(ident, column)| {
2902 let col_lit = column.as_str();
2903 quote! {
2904 _row_mut.#ident = ::rustango::sql::sqlx::Row::try_get(
2905 _returning_row,
2906 #col_lit,
2907 )?;
2908 }
2909 });
2910 quote! { #( #lines )* }
2911}
2912
2913fn column_const_tokens(module_ident: &syn::Ident, entries: &[ColumnEntry]) -> TokenStream2 {
2915 let lines = entries.iter().map(|e| {
2916 let ident = &e.ident;
2917 let col_ty = column_type_ident(ident);
2918 quote! {
2919 #[allow(non_upper_case_globals)]
2920 pub const #ident: #module_ident::#col_ty = #module_ident::#col_ty;
2921 }
2922 });
2923 quote! { #(#lines)* }
2924}
2925
2926fn column_module_tokens(
2929 module_ident: &syn::Ident,
2930 struct_name: &syn::Ident,
2931 entries: &[ColumnEntry],
2932) -> TokenStream2 {
2933 let items = entries.iter().map(|e| {
2934 let col_ty = column_type_ident(&e.ident);
2935 let value_ty = &e.value_ty;
2936 let name = &e.name;
2937 let column = &e.column;
2938 let field_type_tokens = &e.field_type_tokens;
2939 quote! {
2940 #[derive(::core::clone::Clone, ::core::marker::Copy)]
2941 pub struct #col_ty;
2942
2943 impl ::rustango::core::Column for #col_ty {
2944 type Model = super::#struct_name;
2945 type Value = #value_ty;
2946 const NAME: &'static str = #name;
2947 const COLUMN: &'static str = #column;
2948 const FIELD_TYPE: ::rustango::core::FieldType = #field_type_tokens;
2949 }
2950 }
2951 });
2952 quote! {
2953 #[doc(hidden)]
2954 #[allow(non_camel_case_types, non_snake_case)]
2955 pub mod #module_ident {
2956 #[allow(unused_imports)]
2961 use super::*;
2962 #(#items)*
2963 }
2964 }
2965}
2966
2967fn column_type_ident(field_ident: &syn::Ident) -> syn::Ident {
2968 syn::Ident::new(&format!("{field_ident}_col"), field_ident.span())
2969}
2970
2971fn column_module_ident(struct_name: &syn::Ident) -> syn::Ident {
2972 syn::Ident::new(
2973 &format!("__rustango_cols_{struct_name}"),
2974 struct_name.span(),
2975 )
2976}
2977
2978fn from_row_impl_tokens(struct_name: &syn::Ident, from_row_inits: &[TokenStream2]) -> TokenStream2 {
2979 quote! {
2990 impl<'r> ::rustango::sql::sqlx::FromRow<'r, ::rustango::sql::sqlx::postgres::PgRow>
2991 for #struct_name
2992 {
2993 fn from_row(
2994 row: &'r ::rustango::sql::sqlx::postgres::PgRow,
2995 ) -> ::core::result::Result<Self, ::rustango::sql::sqlx::Error> {
2996 ::core::result::Result::Ok(Self {
2997 #( #from_row_inits ),*
2998 })
2999 }
3000 }
3001
3002 ::rustango::__impl_my_from_row!(#struct_name, |row| {
3003 #( #from_row_inits ),*
3004 });
3005 }
3006}
3007
3008struct ContainerAttrs {
3009 table: Option<String>,
3010 display: Option<(String, proc_macro2::Span)>,
3011 app: Option<String>,
3018 admin: Option<AdminAttrs>,
3023 audit: Option<AuditAttrs>,
3029 permissions: bool,
3033 m2m: Vec<M2MAttr>,
3037 indexes: Vec<IndexAttr>,
3043 checks: Vec<CheckAttr>,
3046 composite_fks: Vec<CompositeFkAttr>,
3050 generic_fks: Vec<GenericFkAttr>,
3054}
3055
3056struct IndexAttr {
3058 name: Option<String>,
3060 columns: Vec<String>,
3062 unique: bool,
3064}
3065
3066struct CheckAttr {
3068 name: String,
3069 expr: String,
3070}
3071
3072struct CompositeFkAttr {
3078 name: String,
3080 to: String,
3082 from: Vec<String>,
3084 on: Vec<String>,
3086}
3087
3088struct GenericFkAttr {
3093 name: String,
3095 ct_column: String,
3097 pk_column: String,
3099}
3100
3101struct M2MAttr {
3103 name: String,
3105 to: String,
3107 through: String,
3109 src: String,
3111 dst: String,
3113}
3114
3115#[derive(Default)]
3121struct AuditAttrs {
3122 track: Option<(Vec<String>, proc_macro2::Span)>,
3126}
3127
3128#[derive(Default)]
3133struct AdminAttrs {
3134 list_display: Option<(Vec<String>, proc_macro2::Span)>,
3135 search_fields: Option<(Vec<String>, proc_macro2::Span)>,
3136 list_per_page: Option<usize>,
3137 ordering: Option<(Vec<(String, bool)>, proc_macro2::Span)>,
3138 readonly_fields: Option<(Vec<String>, proc_macro2::Span)>,
3139 list_filter: Option<(Vec<String>, proc_macro2::Span)>,
3140 actions: Option<(Vec<String>, proc_macro2::Span)>,
3143 fieldsets: Option<(Vec<(String, Vec<String>)>, proc_macro2::Span)>,
3147}
3148
3149fn parse_container_attrs(input: &DeriveInput) -> syn::Result<ContainerAttrs> {
3150 let mut out = ContainerAttrs {
3151 table: None,
3152 display: None,
3153 app: None,
3154 admin: None,
3155 audit: None,
3156 permissions: false,
3157 m2m: Vec::new(),
3158 indexes: Vec::new(),
3159 checks: Vec::new(),
3160 composite_fks: Vec::new(),
3161 generic_fks: Vec::new(),
3162 };
3163 for attr in &input.attrs {
3164 if !attr.path().is_ident("rustango") {
3165 continue;
3166 }
3167 attr.parse_nested_meta(|meta| {
3168 if meta.path.is_ident("table") {
3169 let s: LitStr = meta.value()?.parse()?;
3170 out.table = Some(s.value());
3171 return Ok(());
3172 }
3173 if meta.path.is_ident("display") {
3174 let s: LitStr = meta.value()?.parse()?;
3175 out.display = Some((s.value(), s.span()));
3176 return Ok(());
3177 }
3178 if meta.path.is_ident("app") {
3179 let s: LitStr = meta.value()?.parse()?;
3180 out.app = Some(s.value());
3181 return Ok(());
3182 }
3183 if meta.path.is_ident("admin") {
3184 let mut admin = AdminAttrs::default();
3185 meta.parse_nested_meta(|inner| {
3186 if inner.path.is_ident("list_display") {
3187 let s: LitStr = inner.value()?.parse()?;
3188 admin.list_display =
3189 Some((split_field_list(&s.value()), s.span()));
3190 return Ok(());
3191 }
3192 if inner.path.is_ident("search_fields") {
3193 let s: LitStr = inner.value()?.parse()?;
3194 admin.search_fields =
3195 Some((split_field_list(&s.value()), s.span()));
3196 return Ok(());
3197 }
3198 if inner.path.is_ident("readonly_fields") {
3199 let s: LitStr = inner.value()?.parse()?;
3200 admin.readonly_fields =
3201 Some((split_field_list(&s.value()), s.span()));
3202 return Ok(());
3203 }
3204 if inner.path.is_ident("list_per_page") {
3205 let lit: syn::LitInt = inner.value()?.parse()?;
3206 admin.list_per_page = Some(lit.base10_parse::<usize>()?);
3207 return Ok(());
3208 }
3209 if inner.path.is_ident("ordering") {
3210 let s: LitStr = inner.value()?.parse()?;
3211 admin.ordering = Some((
3212 parse_ordering_list(&s.value()),
3213 s.span(),
3214 ));
3215 return Ok(());
3216 }
3217 if inner.path.is_ident("list_filter") {
3218 let s: LitStr = inner.value()?.parse()?;
3219 admin.list_filter =
3220 Some((split_field_list(&s.value()), s.span()));
3221 return Ok(());
3222 }
3223 if inner.path.is_ident("actions") {
3224 let s: LitStr = inner.value()?.parse()?;
3225 admin.actions =
3226 Some((split_field_list(&s.value()), s.span()));
3227 return Ok(());
3228 }
3229 if inner.path.is_ident("fieldsets") {
3230 let s: LitStr = inner.value()?.parse()?;
3231 admin.fieldsets =
3232 Some((parse_fieldset_list(&s.value()), s.span()));
3233 return Ok(());
3234 }
3235 Err(inner.error(
3236 "unknown admin attribute (supported: \
3237 `list_display`, `search_fields`, `readonly_fields`, \
3238 `list_filter`, `list_per_page`, `ordering`, `actions`, \
3239 `fieldsets`)",
3240 ))
3241 })?;
3242 out.admin = Some(admin);
3243 return Ok(());
3244 }
3245 if meta.path.is_ident("audit") {
3246 let mut audit = AuditAttrs::default();
3247 meta.parse_nested_meta(|inner| {
3248 if inner.path.is_ident("track") {
3249 let s: LitStr = inner.value()?.parse()?;
3250 audit.track =
3251 Some((split_field_list(&s.value()), s.span()));
3252 return Ok(());
3253 }
3254 Err(inner.error(
3255 "unknown audit attribute (supported: `track`)",
3256 ))
3257 })?;
3258 out.audit = Some(audit);
3259 return Ok(());
3260 }
3261 if meta.path.is_ident("permissions") {
3262 out.permissions = true;
3263 return Ok(());
3264 }
3265 if meta.path.is_ident("unique_together") {
3266 let (columns, name) = parse_together_attr(&meta, "unique_together")?;
3275 out.indexes.push(IndexAttr { name, columns, unique: true });
3276 return Ok(());
3277 }
3278 if meta.path.is_ident("index_together") {
3279 let (columns, name) = parse_together_attr(&meta, "index_together")?;
3285 out.indexes.push(IndexAttr { name, columns, unique: false });
3286 return Ok(());
3287 }
3288 if meta.path.is_ident("index") {
3289 let cols_lit: LitStr = meta.value()?.parse()?;
3297 let columns = split_field_list(&cols_lit.value());
3298 out.indexes.push(IndexAttr { name: None, columns, unique: false });
3299 return Ok(());
3300 }
3301 if meta.path.is_ident("check") {
3302 let mut name: Option<String> = None;
3304 let mut expr: Option<String> = None;
3305 meta.parse_nested_meta(|inner| {
3306 if inner.path.is_ident("name") {
3307 let s: LitStr = inner.value()?.parse()?;
3308 name = Some(s.value());
3309 return Ok(());
3310 }
3311 if inner.path.is_ident("expr") {
3312 let s: LitStr = inner.value()?.parse()?;
3313 expr = Some(s.value());
3314 return Ok(());
3315 }
3316 Err(inner.error("unknown check attribute (supported: `name`, `expr`)"))
3317 })?;
3318 let name = name.ok_or_else(|| meta.error("check requires `name = \"...\"`"))?;
3319 let expr = expr.ok_or_else(|| meta.error("check requires `expr = \"...\"`"))?;
3320 out.checks.push(CheckAttr { name, expr });
3321 return Ok(());
3322 }
3323 if meta.path.is_ident("generic_fk") {
3324 let mut gfk = GenericFkAttr {
3325 name: String::new(),
3326 ct_column: String::new(),
3327 pk_column: String::new(),
3328 };
3329 meta.parse_nested_meta(|inner| {
3330 if inner.path.is_ident("name") {
3331 let s: LitStr = inner.value()?.parse()?;
3332 gfk.name = s.value();
3333 return Ok(());
3334 }
3335 if inner.path.is_ident("ct_column") {
3336 let s: LitStr = inner.value()?.parse()?;
3337 gfk.ct_column = s.value();
3338 return Ok(());
3339 }
3340 if inner.path.is_ident("pk_column") {
3341 let s: LitStr = inner.value()?.parse()?;
3342 gfk.pk_column = s.value();
3343 return Ok(());
3344 }
3345 Err(inner.error(
3346 "unknown generic_fk attribute (supported: `name`, `ct_column`, `pk_column`)",
3347 ))
3348 })?;
3349 if gfk.name.is_empty() {
3350 return Err(meta.error("generic_fk requires `name = \"...\"`"));
3351 }
3352 if gfk.ct_column.is_empty() {
3353 return Err(meta.error("generic_fk requires `ct_column = \"...\"`"));
3354 }
3355 if gfk.pk_column.is_empty() {
3356 return Err(meta.error("generic_fk requires `pk_column = \"...\"`"));
3357 }
3358 out.generic_fks.push(gfk);
3359 return Ok(());
3360 }
3361 if meta.path.is_ident("fk_composite") {
3362 let mut fk = CompositeFkAttr {
3363 name: String::new(),
3364 to: String::new(),
3365 from: Vec::new(),
3366 on: Vec::new(),
3367 };
3368 meta.parse_nested_meta(|inner| {
3369 if inner.path.is_ident("name") {
3370 let s: LitStr = inner.value()?.parse()?;
3371 fk.name = s.value();
3372 return Ok(());
3373 }
3374 if inner.path.is_ident("to") {
3375 let s: LitStr = inner.value()?.parse()?;
3376 fk.to = s.value();
3377 return Ok(());
3378 }
3379 if inner.path.is_ident("on") || inner.path.is_ident("from") {
3382 let value = inner.value()?;
3383 let content;
3384 syn::parenthesized!(content in value);
3385 let lits: syn::punctuated::Punctuated<syn::LitStr, syn::Token![,]> =
3386 content.parse_terminated(
3387 |p| p.parse::<syn::LitStr>(),
3388 syn::Token![,],
3389 )?;
3390 let cols: Vec<String> = lits.iter().map(syn::LitStr::value).collect();
3391 if inner.path.is_ident("on") {
3392 fk.on = cols;
3393 } else {
3394 fk.from = cols;
3395 }
3396 return Ok(());
3397 }
3398 Err(inner.error(
3399 "unknown fk_composite attribute (supported: `name`, `to`, `on`, `from`)",
3400 ))
3401 })?;
3402 if fk.name.is_empty() {
3403 return Err(meta.error("fk_composite requires `name = \"...\"`"));
3404 }
3405 if fk.to.is_empty() {
3406 return Err(meta.error("fk_composite requires `to = \"...\"`"));
3407 }
3408 if fk.from.is_empty() || fk.on.is_empty() {
3409 return Err(meta.error(
3410 "fk_composite requires non-empty `from = (...)` and `on = (...)` tuples",
3411 ));
3412 }
3413 if fk.from.len() != fk.on.len() {
3414 return Err(meta.error(format!(
3415 "fk_composite `from` ({} cols) and `on` ({} cols) must be the same length",
3416 fk.from.len(),
3417 fk.on.len(),
3418 )));
3419 }
3420 out.composite_fks.push(fk);
3421 return Ok(());
3422 }
3423 if meta.path.is_ident("m2m") {
3424 let mut m2m = M2MAttr {
3425 name: String::new(),
3426 to: String::new(),
3427 through: String::new(),
3428 src: String::new(),
3429 dst: String::new(),
3430 };
3431 meta.parse_nested_meta(|inner| {
3432 if inner.path.is_ident("name") {
3433 let s: LitStr = inner.value()?.parse()?;
3434 m2m.name = s.value();
3435 return Ok(());
3436 }
3437 if inner.path.is_ident("to") {
3438 let s: LitStr = inner.value()?.parse()?;
3439 m2m.to = s.value();
3440 return Ok(());
3441 }
3442 if inner.path.is_ident("through") {
3443 let s: LitStr = inner.value()?.parse()?;
3444 m2m.through = s.value();
3445 return Ok(());
3446 }
3447 if inner.path.is_ident("src") {
3448 let s: LitStr = inner.value()?.parse()?;
3449 m2m.src = s.value();
3450 return Ok(());
3451 }
3452 if inner.path.is_ident("dst") {
3453 let s: LitStr = inner.value()?.parse()?;
3454 m2m.dst = s.value();
3455 return Ok(());
3456 }
3457 Err(inner.error("unknown m2m attribute (supported: `name`, `to`, `through`, `src`, `dst`)"))
3458 })?;
3459 if m2m.name.is_empty() {
3460 return Err(meta.error("m2m requires `name = \"...\"`"));
3461 }
3462 if m2m.to.is_empty() {
3463 return Err(meta.error("m2m requires `to = \"...\"`"));
3464 }
3465 if m2m.through.is_empty() {
3466 return Err(meta.error("m2m requires `through = \"...\"`"));
3467 }
3468 if m2m.src.is_empty() {
3469 return Err(meta.error("m2m requires `src = \"...\"`"));
3470 }
3471 if m2m.dst.is_empty() {
3472 return Err(meta.error("m2m requires `dst = \"...\"`"));
3473 }
3474 out.m2m.push(m2m);
3475 return Ok(());
3476 }
3477 Err(meta.error("unknown rustango container attribute"))
3478 })?;
3479 }
3480 Ok(out)
3481}
3482
3483fn split_field_list(raw: &str) -> Vec<String> {
3487 raw.split(',')
3488 .map(str::trim)
3489 .filter(|s| !s.is_empty())
3490 .map(str::to_owned)
3491 .collect()
3492}
3493
3494fn parse_together_attr(
3502 meta: &syn::meta::ParseNestedMeta<'_>,
3503 attr: &str,
3504) -> syn::Result<(Vec<String>, Option<String>)> {
3505 if meta.input.peek(syn::Token![=]) {
3508 let cols_lit: LitStr = meta.value()?.parse()?;
3509 let columns = split_field_list(&cols_lit.value());
3510 check_together_columns(meta, attr, &columns)?;
3511 return Ok((columns, None));
3512 }
3513 let mut columns: Option<Vec<String>> = None;
3514 let mut name: Option<String> = None;
3515 meta.parse_nested_meta(|inner| {
3516 if inner.path.is_ident("columns") {
3517 let s: LitStr = inner.value()?.parse()?;
3518 columns = Some(split_field_list(&s.value()));
3519 return Ok(());
3520 }
3521 if inner.path.is_ident("name") {
3522 let s: LitStr = inner.value()?.parse()?;
3523 name = Some(s.value());
3524 return Ok(());
3525 }
3526 Err(inner.error("unknown sub-attribute (supported: `columns`, `name`)"))
3527 })?;
3528 let columns = columns.ok_or_else(|| meta.error(format!(
3529 "{attr}(...) requires a `columns = \"col1, col2\"` argument",
3530 )))?;
3531 check_together_columns(meta, attr, &columns)?;
3532 Ok((columns, name))
3533}
3534
3535fn check_together_columns(
3536 meta: &syn::meta::ParseNestedMeta<'_>,
3537 attr: &str,
3538 columns: &[String],
3539) -> syn::Result<()> {
3540 if columns.len() < 2 {
3541 let single = if attr == "unique_together" {
3542 "#[rustango(unique)] on the field"
3543 } else {
3544 "#[rustango(index)] on the field"
3545 };
3546 return Err(meta.error(format!(
3547 "{attr} expects two or more columns; for a single-column equivalent use {single}",
3548 )));
3549 }
3550 Ok(())
3551}
3552
3553fn parse_fieldset_list(raw: &str) -> Vec<(String, Vec<String>)> {
3562 raw.split('|')
3563 .map(str::trim)
3564 .filter(|s| !s.is_empty())
3565 .map(|section| {
3566 let (title, rest) = match section.split_once(':') {
3568 Some((title, rest)) if !title.contains(',') => {
3569 (title.trim().to_owned(), rest)
3570 }
3571 _ => (String::new(), section),
3572 };
3573 let fields = split_field_list(rest);
3574 (title, fields)
3575 })
3576 .collect()
3577}
3578
3579fn parse_ordering_list(raw: &str) -> Vec<(String, bool)> {
3582 raw.split(',')
3583 .map(str::trim)
3584 .filter(|s| !s.is_empty())
3585 .map(|spec| {
3586 spec.strip_prefix('-')
3587 .map_or((spec.to_owned(), false), |rest| (rest.trim().to_owned(), true))
3588 })
3589 .collect()
3590}
3591
3592struct FieldAttrs {
3593 column: Option<String>,
3594 primary_key: bool,
3595 fk: Option<String>,
3596 o2o: Option<String>,
3597 on: Option<String>,
3598 max_length: Option<u32>,
3599 min: Option<i64>,
3600 max: Option<i64>,
3601 default: Option<String>,
3602 auto_uuid: bool,
3608 auto_now_add: bool,
3613 auto_now: bool,
3619 soft_delete: bool,
3624 unique: bool,
3627 index: bool,
3631 index_unique: bool,
3632 index_name: Option<String>,
3633}
3634
3635fn parse_field_attrs(field: &syn::Field) -> syn::Result<FieldAttrs> {
3636 let mut out = FieldAttrs {
3637 column: None,
3638 primary_key: false,
3639 fk: None,
3640 o2o: None,
3641 on: None,
3642 max_length: None,
3643 min: None,
3644 max: None,
3645 default: None,
3646 auto_uuid: false,
3647 auto_now_add: false,
3648 auto_now: false,
3649 soft_delete: false,
3650 unique: false,
3651 index: false,
3652 index_unique: false,
3653 index_name: None,
3654 };
3655 for attr in &field.attrs {
3656 if !attr.path().is_ident("rustango") {
3657 continue;
3658 }
3659 attr.parse_nested_meta(|meta| {
3660 if meta.path.is_ident("column") {
3661 let s: LitStr = meta.value()?.parse()?;
3662 out.column = Some(s.value());
3663 return Ok(());
3664 }
3665 if meta.path.is_ident("primary_key") {
3666 out.primary_key = true;
3667 return Ok(());
3668 }
3669 if meta.path.is_ident("fk") {
3670 let s: LitStr = meta.value()?.parse()?;
3671 out.fk = Some(s.value());
3672 return Ok(());
3673 }
3674 if meta.path.is_ident("o2o") {
3675 let s: LitStr = meta.value()?.parse()?;
3676 out.o2o = Some(s.value());
3677 return Ok(());
3678 }
3679 if meta.path.is_ident("on") {
3680 let s: LitStr = meta.value()?.parse()?;
3681 out.on = Some(s.value());
3682 return Ok(());
3683 }
3684 if meta.path.is_ident("max_length") {
3685 let lit: syn::LitInt = meta.value()?.parse()?;
3686 out.max_length = Some(lit.base10_parse::<u32>()?);
3687 return Ok(());
3688 }
3689 if meta.path.is_ident("min") {
3690 out.min = Some(parse_signed_i64(&meta)?);
3691 return Ok(());
3692 }
3693 if meta.path.is_ident("max") {
3694 out.max = Some(parse_signed_i64(&meta)?);
3695 return Ok(());
3696 }
3697 if meta.path.is_ident("default") {
3698 let s: LitStr = meta.value()?.parse()?;
3699 out.default = Some(s.value());
3700 return Ok(());
3701 }
3702 if meta.path.is_ident("auto_uuid") {
3703 out.auto_uuid = true;
3704 out.primary_key = true;
3708 if out.default.is_none() {
3709 out.default = Some("gen_random_uuid()".into());
3710 }
3711 return Ok(());
3712 }
3713 if meta.path.is_ident("auto_now_add") {
3714 out.auto_now_add = true;
3715 if out.default.is_none() {
3716 out.default = Some("now()".into());
3717 }
3718 return Ok(());
3719 }
3720 if meta.path.is_ident("auto_now") {
3721 out.auto_now = true;
3722 if out.default.is_none() {
3723 out.default = Some("now()".into());
3724 }
3725 return Ok(());
3726 }
3727 if meta.path.is_ident("soft_delete") {
3728 out.soft_delete = true;
3729 return Ok(());
3730 }
3731 if meta.path.is_ident("unique") {
3732 out.unique = true;
3733 return Ok(());
3734 }
3735 if meta.path.is_ident("index") {
3736 out.index = true;
3737 if meta.input.peek(syn::token::Paren) {
3739 meta.parse_nested_meta(|inner| {
3740 if inner.path.is_ident("unique") {
3741 out.index_unique = true;
3742 return Ok(());
3743 }
3744 if inner.path.is_ident("name") {
3745 let s: LitStr = inner.value()?.parse()?;
3746 out.index_name = Some(s.value());
3747 return Ok(());
3748 }
3749 Err(inner.error("unknown index sub-attribute (supported: `unique`, `name`)"))
3750 })?;
3751 }
3752 return Ok(());
3753 }
3754 Err(meta.error("unknown rustango field attribute"))
3755 })?;
3756 }
3757 Ok(out)
3758}
3759
3760fn parse_signed_i64(meta: &syn::meta::ParseNestedMeta<'_>) -> syn::Result<i64> {
3762 let expr: syn::Expr = meta.value()?.parse()?;
3763 match expr {
3764 syn::Expr::Lit(syn::ExprLit {
3765 lit: syn::Lit::Int(lit),
3766 ..
3767 }) => lit.base10_parse::<i64>(),
3768 syn::Expr::Unary(syn::ExprUnary {
3769 op: syn::UnOp::Neg(_),
3770 expr,
3771 ..
3772 }) => {
3773 if let syn::Expr::Lit(syn::ExprLit {
3774 lit: syn::Lit::Int(lit),
3775 ..
3776 }) = *expr
3777 {
3778 let v: i64 = lit.base10_parse()?;
3779 Ok(-v)
3780 } else {
3781 Err(syn::Error::new_spanned(expr, "expected integer literal"))
3782 }
3783 }
3784 other => Err(syn::Error::new_spanned(
3785 other,
3786 "expected integer literal (signed)",
3787 )),
3788 }
3789}
3790
3791struct FieldInfo<'a> {
3792 ident: &'a syn::Ident,
3793 column: String,
3794 primary_key: bool,
3795 auto: bool,
3799 value_ty: &'a Type,
3802 field_type_tokens: TokenStream2,
3804 schema: TokenStream2,
3805 from_row_init: TokenStream2,
3806 from_aliased_row_init: TokenStream2,
3812 fk_inner: Option<Type>,
3816 fk_pk_kind: DetectedKind,
3822 nullable: bool,
3830 auto_now: bool,
3836 auto_now_add: bool,
3842 soft_delete: bool,
3847}
3848
3849fn process_field<'a>(field: &'a syn::Field, table: &str) -> syn::Result<FieldInfo<'a>> {
3850 let attrs = parse_field_attrs(field)?;
3851 let ident = field
3852 .ident
3853 .as_ref()
3854 .ok_or_else(|| syn::Error::new(field.span(), "tuple structs are not supported"))?;
3855 let name = ident.to_string();
3856 let column = attrs.column.clone().unwrap_or_else(|| name.clone());
3857 let primary_key = attrs.primary_key;
3858 let DetectedType {
3859 kind,
3860 nullable,
3861 auto: detected_auto,
3862 fk_inner,
3863 } = detect_type(&field.ty)?;
3864 check_bound_compatibility(field, &attrs, kind)?;
3865 let auto = detected_auto;
3866 if attrs.auto_uuid {
3872 if kind != DetectedKind::Uuid {
3873 return Err(syn::Error::new_spanned(
3874 field,
3875 "`#[rustango(auto_uuid)]` requires the field type to be \
3876 `Auto<uuid::Uuid>`",
3877 ));
3878 }
3879 if !detected_auto {
3880 return Err(syn::Error::new_spanned(
3881 field,
3882 "`#[rustango(auto_uuid)]` requires the field type to be \
3883 wrapped in `Auto<...>` so the macro skips the column on \
3884 INSERT and the DB DEFAULT (`gen_random_uuid()`) fires",
3885 ));
3886 }
3887 }
3888 if attrs.auto_now_add || attrs.auto_now {
3889 if kind != DetectedKind::DateTime {
3890 return Err(syn::Error::new_spanned(
3891 field,
3892 "`#[rustango(auto_now_add)]` / `#[rustango(auto_now)]` require \
3893 the field type to be `Auto<chrono::DateTime<chrono::Utc>>`",
3894 ));
3895 }
3896 if !detected_auto {
3897 return Err(syn::Error::new_spanned(
3898 field,
3899 "`#[rustango(auto_now_add)]` / `#[rustango(auto_now)]` require \
3900 the field type to be wrapped in `Auto<...>` so the macro skips \
3901 the column on INSERT and the DB DEFAULT (`now()`) fires",
3902 ));
3903 }
3904 }
3905 if attrs.soft_delete && !(kind == DetectedKind::DateTime && nullable) {
3906 return Err(syn::Error::new_spanned(
3907 field,
3908 "`#[rustango(soft_delete)]` requires the field type to be \
3909 `Option<chrono::DateTime<chrono::Utc>>`",
3910 ));
3911 }
3912 let is_mixin_auto = attrs.auto_uuid || attrs.auto_now_add || attrs.auto_now;
3913 if detected_auto && !primary_key && !is_mixin_auto {
3914 return Err(syn::Error::new_spanned(
3915 field,
3916 "`Auto<T>` is only valid on a `#[rustango(primary_key)]` field, \
3917 or on a field carrying one of `auto_uuid`, `auto_now_add`, or \
3918 `auto_now`",
3919 ));
3920 }
3921 if detected_auto && attrs.default.is_some() && !is_mixin_auto {
3922 return Err(syn::Error::new_spanned(
3923 field,
3924 "`#[rustango(default = \"…\")]` is redundant on an `Auto<T>` field — \
3925 SERIAL / BIGSERIAL already supplies a default sequence.",
3926 ));
3927 }
3928 if fk_inner.is_some() && primary_key {
3929 return Err(syn::Error::new_spanned(
3930 field,
3931 "`ForeignKey<T>` is not allowed on a primary-key field — \
3932 a row's PK is its own identity, not a reference to a parent.",
3933 ));
3934 }
3935 let relation = relation_tokens(field, &attrs, fk_inner, table)?;
3936 let column_lit = column.as_str();
3937 let field_type_tokens = kind.variant_tokens();
3938 let max_length = optional_u32(attrs.max_length);
3939 let min = optional_i64(attrs.min);
3940 let max = optional_i64(attrs.max);
3941 let default = optional_str(attrs.default.as_deref());
3942
3943 let unique = attrs.unique;
3944 let schema = quote! {
3945 ::rustango::core::FieldSchema {
3946 name: #name,
3947 column: #column_lit,
3948 ty: #field_type_tokens,
3949 nullable: #nullable,
3950 primary_key: #primary_key,
3951 relation: #relation,
3952 max_length: #max_length,
3953 min: #min,
3954 max: #max,
3955 default: #default,
3956 auto: #auto,
3957 unique: #unique,
3958 }
3959 };
3960
3961 let from_row_init = quote! {
3962 #ident: ::rustango::sql::sqlx::Row::try_get(row, #column_lit)?
3963 };
3964 let from_aliased_row_init = quote! {
3965 #ident: ::rustango::sql::sqlx::Row::try_get(
3966 row,
3967 ::std::format!("{}__{}", prefix, #column_lit).as_str(),
3968 )?
3969 };
3970
3971 Ok(FieldInfo {
3972 ident,
3973 column,
3974 primary_key,
3975 auto,
3976 value_ty: &field.ty,
3977 field_type_tokens,
3978 schema,
3979 from_row_init,
3980 from_aliased_row_init,
3981 fk_inner: fk_inner.cloned(),
3982 fk_pk_kind: kind,
3983 nullable,
3984 auto_now: attrs.auto_now,
3985 auto_now_add: attrs.auto_now_add,
3986 soft_delete: attrs.soft_delete,
3987 })
3988}
3989
3990fn check_bound_compatibility(
3991 field: &syn::Field,
3992 attrs: &FieldAttrs,
3993 kind: DetectedKind,
3994) -> syn::Result<()> {
3995 if attrs.max_length.is_some() && kind != DetectedKind::String {
3996 return Err(syn::Error::new_spanned(
3997 field,
3998 "`max_length` is only valid on `String` fields (or `Option<String>`)",
3999 ));
4000 }
4001 if (attrs.min.is_some() || attrs.max.is_some()) && !kind.is_integer() {
4002 return Err(syn::Error::new_spanned(
4003 field,
4004 "`min` / `max` are only valid on integer fields (`i32`, `i64`, optionally Option-wrapped)",
4005 ));
4006 }
4007 if let (Some(min), Some(max)) = (attrs.min, attrs.max) {
4008 if min > max {
4009 return Err(syn::Error::new_spanned(
4010 field,
4011 format!("`min` ({min}) is greater than `max` ({max})"),
4012 ));
4013 }
4014 }
4015 Ok(())
4016}
4017
4018fn optional_u32(value: Option<u32>) -> TokenStream2 {
4019 if let Some(v) = value {
4020 quote!(::core::option::Option::Some(#v))
4021 } else {
4022 quote!(::core::option::Option::None)
4023 }
4024}
4025
4026fn optional_i64(value: Option<i64>) -> TokenStream2 {
4027 if let Some(v) = value {
4028 quote!(::core::option::Option::Some(#v))
4029 } else {
4030 quote!(::core::option::Option::None)
4031 }
4032}
4033
4034fn optional_str(value: Option<&str>) -> TokenStream2 {
4035 if let Some(v) = value {
4036 quote!(::core::option::Option::Some(#v))
4037 } else {
4038 quote!(::core::option::Option::None)
4039 }
4040}
4041
4042fn relation_tokens(
4043 field: &syn::Field,
4044 attrs: &FieldAttrs,
4045 fk_inner: Option<&syn::Type>,
4046 table: &str,
4047) -> syn::Result<TokenStream2> {
4048 if let Some(inner) = fk_inner {
4049 if attrs.fk.is_some() || attrs.o2o.is_some() {
4050 return Err(syn::Error::new_spanned(
4051 field,
4052 "`ForeignKey<T>` already declares the FK target via the type parameter — \
4053 remove the `fk = \"…\"` / `o2o = \"…\"` attribute.",
4054 ));
4055 }
4056 let on = attrs.on.as_deref().unwrap_or("id");
4057 return Ok(quote! {
4058 ::core::option::Option::Some(::rustango::core::Relation::Fk {
4059 to: <#inner as ::rustango::core::Model>::SCHEMA.table,
4060 on: #on,
4061 })
4062 });
4063 }
4064 match (&attrs.fk, &attrs.o2o) {
4065 (Some(_), Some(_)) => Err(syn::Error::new_spanned(
4066 field,
4067 "`fk` and `o2o` are mutually exclusive",
4068 )),
4069 (Some(to), None) => {
4070 let on = attrs.on.as_deref().unwrap_or("id");
4071 let resolved = if to == "self" { table } else { to };
4077 Ok(quote! {
4078 ::core::option::Option::Some(::rustango::core::Relation::Fk { to: #resolved, on: #on })
4079 })
4080 }
4081 (None, Some(to)) => {
4082 let on = attrs.on.as_deref().unwrap_or("id");
4083 let resolved = if to == "self" { table } else { to };
4084 Ok(quote! {
4085 ::core::option::Option::Some(::rustango::core::Relation::O2O { to: #resolved, on: #on })
4086 })
4087 }
4088 (None, None) => {
4089 if attrs.on.is_some() {
4090 return Err(syn::Error::new_spanned(
4091 field,
4092 "`on` requires `fk` or `o2o`",
4093 ));
4094 }
4095 Ok(quote!(::core::option::Option::None))
4096 }
4097 }
4098}
4099
4100#[derive(Clone, Copy, PartialEq, Eq)]
4104enum DetectedKind {
4105 I16,
4106 I32,
4107 I64,
4108 F32,
4109 F64,
4110 Bool,
4111 String,
4112 DateTime,
4113 Date,
4114 Uuid,
4115 Json,
4116}
4117
4118impl DetectedKind {
4119 fn variant_tokens(self) -> TokenStream2 {
4120 match self {
4121 Self::I16 => quote!(::rustango::core::FieldType::I16),
4122 Self::I32 => quote!(::rustango::core::FieldType::I32),
4123 Self::I64 => quote!(::rustango::core::FieldType::I64),
4124 Self::F32 => quote!(::rustango::core::FieldType::F32),
4125 Self::F64 => quote!(::rustango::core::FieldType::F64),
4126 Self::Bool => quote!(::rustango::core::FieldType::Bool),
4127 Self::String => quote!(::rustango::core::FieldType::String),
4128 Self::DateTime => quote!(::rustango::core::FieldType::DateTime),
4129 Self::Date => quote!(::rustango::core::FieldType::Date),
4130 Self::Uuid => quote!(::rustango::core::FieldType::Uuid),
4131 Self::Json => quote!(::rustango::core::FieldType::Json),
4132 }
4133 }
4134
4135 fn is_integer(self) -> bool {
4136 matches!(self, Self::I16 | Self::I32 | Self::I64)
4137 }
4138
4139 fn sqlvalue_match_arm(self) -> (TokenStream2, TokenStream2) {
4147 match self {
4148 Self::I16 => (quote!(I16), quote!(0i16)),
4149 Self::I32 => (quote!(I32), quote!(0i32)),
4150 Self::I64 => (quote!(I64), quote!(0i64)),
4151 Self::F32 => (quote!(F32), quote!(0f32)),
4152 Self::F64 => (quote!(F64), quote!(0f64)),
4153 Self::Bool => (quote!(Bool), quote!(false)),
4154 Self::String => (quote!(String), quote!(::std::string::String::new())),
4155 Self::DateTime => (
4156 quote!(DateTime),
4157 quote!(<::chrono::DateTime<::chrono::Utc> as ::std::default::Default>::default()),
4158 ),
4159 Self::Date => (
4160 quote!(Date),
4161 quote!(<::chrono::NaiveDate as ::std::default::Default>::default()),
4162 ),
4163 Self::Uuid => (quote!(Uuid), quote!(::uuid::Uuid::nil())),
4164 Self::Json => (quote!(Json), quote!(::serde_json::Value::Null)),
4165 }
4166 }
4167}
4168
4169#[derive(Clone, Copy)]
4175struct DetectedType<'a> {
4176 kind: DetectedKind,
4177 nullable: bool,
4178 auto: bool,
4179 fk_inner: Option<&'a syn::Type>,
4180}
4181
4182fn auto_inner_type(ty: &syn::Type) -> Option<&syn::Type> {
4187 let Type::Path(TypePath { path, qself: None }) = ty else {
4188 return None;
4189 };
4190 let last = path.segments.last()?;
4191 if last.ident != "Auto" {
4192 return None;
4193 }
4194 let syn::PathArguments::AngleBracketed(args) = &last.arguments else {
4195 return None;
4196 };
4197 args.args.iter().find_map(|a| match a {
4198 syn::GenericArgument::Type(t) => Some(t),
4199 _ => None,
4200 })
4201}
4202
4203fn detect_type(ty: &syn::Type) -> syn::Result<DetectedType<'_>> {
4204 let Type::Path(TypePath { path, qself: None }) = ty else {
4205 return Err(syn::Error::new_spanned(ty, "unsupported field type"));
4206 };
4207 let last = path
4208 .segments
4209 .last()
4210 .ok_or_else(|| syn::Error::new_spanned(ty, "empty type path"))?;
4211
4212 if last.ident == "Option" {
4213 let inner = generic_inner(ty, &last.arguments, "Option")?;
4214 let inner_det = detect_type(inner)?;
4215 if inner_det.nullable {
4216 return Err(syn::Error::new_spanned(
4217 ty,
4218 "nested Option is not supported",
4219 ));
4220 }
4221 if inner_det.auto {
4222 return Err(syn::Error::new_spanned(
4223 ty,
4224 "`Option<Auto<T>>` is not supported — Auto fields are server-assigned and cannot be NULL",
4225 ));
4226 }
4227 return Ok(DetectedType {
4228 nullable: true,
4229 ..inner_det
4230 });
4231 }
4232
4233 if last.ident == "Auto" {
4234 let inner = generic_inner(ty, &last.arguments, "Auto")?;
4235 let inner_det = detect_type(inner)?;
4236 if inner_det.auto {
4237 return Err(syn::Error::new_spanned(
4238 ty,
4239 "nested Auto is not supported",
4240 ));
4241 }
4242 if inner_det.nullable {
4243 return Err(syn::Error::new_spanned(
4244 ty,
4245 "`Auto<Option<T>>` is not supported — Auto fields are server-assigned and cannot be NULL",
4246 ));
4247 }
4248 if inner_det.fk_inner.is_some() {
4249 return Err(syn::Error::new_spanned(
4250 ty,
4251 "`Auto<ForeignKey<T>>` is not supported — Auto is for server-assigned PKs, ForeignKey is for parent references",
4252 ));
4253 }
4254 if !matches!(
4255 inner_det.kind,
4256 DetectedKind::I32 | DetectedKind::I64 | DetectedKind::Uuid | DetectedKind::DateTime
4257 ) {
4258 return Err(syn::Error::new_spanned(
4259 ty,
4260 "`Auto<T>` only supports integers (`i32` → SERIAL, `i64` → BIGSERIAL), \
4261 `uuid::Uuid` (DEFAULT gen_random_uuid()), or `chrono::DateTime<chrono::Utc>` \
4262 (DEFAULT now())",
4263 ));
4264 }
4265 return Ok(DetectedType {
4266 auto: true,
4267 ..inner_det
4268 });
4269 }
4270
4271 if last.ident == "ForeignKey" {
4272 let (inner, key_ty) = generic_pair(ty, &last.arguments, "ForeignKey")?;
4273 let kind = match key_ty {
4281 Some(k) => detect_type(k)?.kind,
4282 None => DetectedKind::I64,
4283 };
4284 return Ok(DetectedType {
4285 kind,
4286 nullable: false,
4287 auto: false,
4288 fk_inner: Some(inner),
4289 });
4290 }
4291
4292 let kind = match last.ident.to_string().as_str() {
4293 "i16" => DetectedKind::I16,
4294 "i32" => DetectedKind::I32,
4295 "i64" => DetectedKind::I64,
4296 "f32" => DetectedKind::F32,
4297 "f64" => DetectedKind::F64,
4298 "bool" => DetectedKind::Bool,
4299 "String" => DetectedKind::String,
4300 "DateTime" => DetectedKind::DateTime,
4301 "NaiveDate" => DetectedKind::Date,
4302 "Uuid" => DetectedKind::Uuid,
4303 "Value" => DetectedKind::Json,
4304 other => {
4305 return Err(syn::Error::new_spanned(
4306 ty,
4307 format!("unsupported field type `{other}`; v0.1 supports i32/i64/f32/f64/bool/String/DateTime/NaiveDate/Uuid/serde_json::Value, optionally wrapped in Option or Auto (Auto only on integers)"),
4308 ));
4309 }
4310 };
4311 Ok(DetectedType {
4312 kind,
4313 nullable: false,
4314 auto: false,
4315 fk_inner: None,
4316 })
4317}
4318
4319fn generic_inner<'a>(
4320 ty: &'a Type,
4321 arguments: &'a PathArguments,
4322 wrapper: &str,
4323) -> syn::Result<&'a Type> {
4324 let PathArguments::AngleBracketed(args) = arguments else {
4325 return Err(syn::Error::new_spanned(
4326 ty,
4327 format!("{wrapper} requires a generic argument"),
4328 ));
4329 };
4330 args.args
4331 .iter()
4332 .find_map(|a| match a {
4333 GenericArgument::Type(t) => Some(t),
4334 _ => None,
4335 })
4336 .ok_or_else(|| {
4337 syn::Error::new_spanned(ty, format!("{wrapper}<T> requires a type argument"))
4338 })
4339}
4340
4341fn generic_pair<'a>(
4345 ty: &'a Type,
4346 arguments: &'a PathArguments,
4347 wrapper: &str,
4348) -> syn::Result<(&'a Type, Option<&'a Type>)> {
4349 let PathArguments::AngleBracketed(args) = arguments else {
4350 return Err(syn::Error::new_spanned(
4351 ty,
4352 format!("{wrapper} requires a generic argument"),
4353 ));
4354 };
4355 let mut types = args.args.iter().filter_map(|a| match a {
4356 GenericArgument::Type(t) => Some(t),
4357 _ => None,
4358 });
4359 let first = types.next().ok_or_else(|| {
4360 syn::Error::new_spanned(ty, format!("{wrapper}<T> requires a type argument"))
4361 })?;
4362 let second = types.next();
4363 Ok((first, second))
4364}
4365
4366fn to_snake_case(s: &str) -> String {
4367 let mut out = String::with_capacity(s.len() + 4);
4368 for (i, ch) in s.chars().enumerate() {
4369 if ch.is_ascii_uppercase() {
4370 if i > 0 {
4371 out.push('_');
4372 }
4373 out.push(ch.to_ascii_lowercase());
4374 } else {
4375 out.push(ch);
4376 }
4377 }
4378 out
4379}
4380
4381#[derive(Default)]
4387struct FormFieldAttrs {
4388 min: Option<i64>,
4389 max: Option<i64>,
4390 min_length: Option<u32>,
4391 max_length: Option<u32>,
4392}
4393
4394#[derive(Clone, Copy)]
4396enum FormFieldKind {
4397 String,
4398 I16,
4399 I32,
4400 I64,
4401 F32,
4402 F64,
4403 Bool,
4404}
4405
4406impl FormFieldKind {
4407 fn parse_method(self) -> &'static str {
4408 match self {
4409 Self::I16 => "i16",
4410 Self::I32 => "i32",
4411 Self::I64 => "i64",
4412 Self::F32 => "f32",
4413 Self::F64 => "f64",
4414 Self::String | Self::Bool => "",
4417 }
4418 }
4419}
4420
4421fn expand_form(input: &DeriveInput) -> syn::Result<TokenStream2> {
4422 let struct_name = &input.ident;
4423
4424 let Data::Struct(data) = &input.data else {
4425 return Err(syn::Error::new_spanned(
4426 struct_name,
4427 "Form can only be derived on structs",
4428 ));
4429 };
4430 let Fields::Named(named) = &data.fields else {
4431 return Err(syn::Error::new_spanned(
4432 struct_name,
4433 "Form requires a struct with named fields",
4434 ));
4435 };
4436
4437 let mut field_blocks: Vec<TokenStream2> = Vec::with_capacity(named.named.len());
4438 let mut field_idents: Vec<&syn::Ident> = Vec::with_capacity(named.named.len());
4439
4440 for field in &named.named {
4441 let ident = field
4442 .ident
4443 .as_ref()
4444 .ok_or_else(|| syn::Error::new(field.span(), "tuple structs are not supported"))?;
4445 let attrs = parse_form_field_attrs(field)?;
4446 let (kind, nullable) = detect_form_field(&field.ty, field.span())?;
4447
4448 let name_lit = ident.to_string();
4449 let parse_block = render_form_field_parse(ident, &name_lit, kind, nullable, &attrs);
4450 field_blocks.push(parse_block);
4451 field_idents.push(ident);
4452 }
4453
4454 Ok(quote! {
4455 impl ::rustango::forms::Form for #struct_name {
4456 fn parse(
4457 data: &::std::collections::HashMap<::std::string::String, ::std::string::String>,
4458 ) -> ::core::result::Result<Self, ::rustango::forms::FormErrors> {
4459 let mut __errors = ::rustango::forms::FormErrors::default();
4460 #( #field_blocks )*
4461 if !__errors.is_empty() {
4462 return ::core::result::Result::Err(__errors);
4463 }
4464 ::core::result::Result::Ok(Self {
4465 #( #field_idents ),*
4466 })
4467 }
4468 }
4469 })
4470}
4471
4472fn parse_form_field_attrs(field: &syn::Field) -> syn::Result<FormFieldAttrs> {
4473 let mut out = FormFieldAttrs::default();
4474 for attr in &field.attrs {
4475 if !attr.path().is_ident("form") {
4476 continue;
4477 }
4478 attr.parse_nested_meta(|meta| {
4479 if meta.path.is_ident("min") {
4480 let lit: syn::LitInt = meta.value()?.parse()?;
4481 out.min = Some(lit.base10_parse::<i64>()?);
4482 return Ok(());
4483 }
4484 if meta.path.is_ident("max") {
4485 let lit: syn::LitInt = meta.value()?.parse()?;
4486 out.max = Some(lit.base10_parse::<i64>()?);
4487 return Ok(());
4488 }
4489 if meta.path.is_ident("min_length") {
4490 let lit: syn::LitInt = meta.value()?.parse()?;
4491 out.min_length = Some(lit.base10_parse::<u32>()?);
4492 return Ok(());
4493 }
4494 if meta.path.is_ident("max_length") {
4495 let lit: syn::LitInt = meta.value()?.parse()?;
4496 out.max_length = Some(lit.base10_parse::<u32>()?);
4497 return Ok(());
4498 }
4499 Err(meta.error(
4500 "unknown form attribute (supported: `min`, `max`, `min_length`, `max_length`)",
4501 ))
4502 })?;
4503 }
4504 Ok(out)
4505}
4506
4507fn detect_form_field(ty: &Type, span: proc_macro2::Span) -> syn::Result<(FormFieldKind, bool)> {
4508 let Type::Path(TypePath { path, qself: None }) = ty else {
4509 return Err(syn::Error::new(
4510 span,
4511 "Form field must be a simple typed path (e.g. `String`, `i32`, `Option<String>`)",
4512 ));
4513 };
4514 let last = path
4515 .segments
4516 .last()
4517 .ok_or_else(|| syn::Error::new(span, "empty type path"))?;
4518
4519 if last.ident == "Option" {
4520 let inner = generic_inner(ty, &last.arguments, "Option")?;
4521 let (kind, nested) = detect_form_field(inner, span)?;
4522 if nested {
4523 return Err(syn::Error::new(
4524 span,
4525 "nested Option in Form fields is not supported",
4526 ));
4527 }
4528 return Ok((kind, true));
4529 }
4530
4531 let kind = match last.ident.to_string().as_str() {
4532 "String" => FormFieldKind::String,
4533 "i16" => FormFieldKind::I16,
4534 "i32" => FormFieldKind::I32,
4535 "i64" => FormFieldKind::I64,
4536 "f32" => FormFieldKind::F32,
4537 "f64" => FormFieldKind::F64,
4538 "bool" => FormFieldKind::Bool,
4539 other => {
4540 return Err(syn::Error::new(
4541 span,
4542 format!(
4543 "Form field type `{other}` is not supported in v0.8 — use String / \
4544 i16 / i32 / i64 / f32 / f64 / bool, optionally wrapped in Option<…>"
4545 ),
4546 ));
4547 }
4548 };
4549 Ok((kind, false))
4550}
4551
4552#[allow(clippy::too_many_lines)]
4553fn render_form_field_parse(
4554 ident: &syn::Ident,
4555 name_lit: &str,
4556 kind: FormFieldKind,
4557 nullable: bool,
4558 attrs: &FormFieldAttrs,
4559) -> TokenStream2 {
4560 let lookup = quote! {
4563 let __raw: ::core::option::Option<&::std::string::String> = data.get(#name_lit);
4564 };
4565
4566 let parsed_value = match kind {
4567 FormFieldKind::Bool => quote! {
4568 let __v: bool = match __raw {
4569 ::core::option::Option::None => false,
4570 ::core::option::Option::Some(__s) => !matches!(
4571 __s.to_ascii_lowercase().as_str(),
4572 "" | "false" | "0" | "off" | "no"
4573 ),
4574 };
4575 },
4576 FormFieldKind::String => {
4577 if nullable {
4578 quote! {
4579 let __v: ::core::option::Option<::std::string::String> = match __raw {
4580 ::core::option::Option::None => ::core::option::Option::None,
4581 ::core::option::Option::Some(__s) if __s.is_empty() => {
4582 ::core::option::Option::None
4583 }
4584 ::core::option::Option::Some(__s) => {
4585 ::core::option::Option::Some(::core::clone::Clone::clone(__s))
4586 }
4587 };
4588 }
4589 } else {
4590 quote! {
4591 let __v: ::std::string::String = match __raw {
4592 ::core::option::Option::Some(__s) if !__s.is_empty() => {
4593 ::core::clone::Clone::clone(__s)
4594 }
4595 _ => {
4596 __errors.add(#name_lit, "This field is required.");
4597 ::std::string::String::new()
4598 }
4599 };
4600 }
4601 }
4602 }
4603 FormFieldKind::I16
4604 | FormFieldKind::I32
4605 | FormFieldKind::I64
4606 | FormFieldKind::F32
4607 | FormFieldKind::F64 => {
4608 let parse_ty = syn::Ident::new(kind.parse_method(), proc_macro2::Span::call_site());
4609 let ty_lit = kind.parse_method();
4610 let default_val = match kind {
4611 FormFieldKind::I16 => quote! { 0i16 },
4612 FormFieldKind::I32 => quote! { 0i32 },
4613 FormFieldKind::I64 => quote! { 0i64 },
4614 FormFieldKind::F32 => quote! { 0f32 },
4615 FormFieldKind::F64 => quote! { 0f64 },
4616 _ => quote! { Default::default() },
4617 };
4618 if nullable {
4619 quote! {
4620 let __v: ::core::option::Option<#parse_ty> = match __raw {
4621 ::core::option::Option::None => ::core::option::Option::None,
4622 ::core::option::Option::Some(__s) if __s.is_empty() => {
4623 ::core::option::Option::None
4624 }
4625 ::core::option::Option::Some(__s) => {
4626 match __s.parse::<#parse_ty>() {
4627 ::core::result::Result::Ok(__n) => {
4628 ::core::option::Option::Some(__n)
4629 }
4630 ::core::result::Result::Err(__e) => {
4631 __errors.add(
4632 #name_lit,
4633 ::std::format!("Enter a valid {} value: {}", #ty_lit, __e),
4634 );
4635 ::core::option::Option::None
4636 }
4637 }
4638 }
4639 };
4640 }
4641 } else {
4642 quote! {
4643 let __v: #parse_ty = match __raw {
4644 ::core::option::Option::Some(__s) if !__s.is_empty() => {
4645 match __s.parse::<#parse_ty>() {
4646 ::core::result::Result::Ok(__n) => __n,
4647 ::core::result::Result::Err(__e) => {
4648 __errors.add(
4649 #name_lit,
4650 ::std::format!("Enter a valid {} value: {}", #ty_lit, __e),
4651 );
4652 #default_val
4653 }
4654 }
4655 }
4656 _ => {
4657 __errors.add(#name_lit, "This field is required.");
4658 #default_val
4659 }
4660 };
4661 }
4662 }
4663 }
4664 };
4665
4666 let validators = render_form_validators(name_lit, kind, nullable, attrs);
4667
4668 quote! {
4669 let #ident = {
4670 #lookup
4671 #parsed_value
4672 #validators
4673 __v
4674 };
4675 }
4676}
4677
4678fn render_form_validators(
4679 name_lit: &str,
4680 kind: FormFieldKind,
4681 nullable: bool,
4682 attrs: &FormFieldAttrs,
4683) -> TokenStream2 {
4684 let mut checks: Vec<TokenStream2> = Vec::new();
4685
4686 let val_ref = if nullable {
4687 quote! { __v.as_ref() }
4688 } else {
4689 quote! { ::core::option::Option::Some(&__v) }
4690 };
4691
4692 let is_string = matches!(kind, FormFieldKind::String);
4693 let is_numeric = matches!(
4694 kind,
4695 FormFieldKind::I16
4696 | FormFieldKind::I32
4697 | FormFieldKind::I64
4698 | FormFieldKind::F32
4699 | FormFieldKind::F64
4700 );
4701
4702 if is_string {
4703 if let Some(min_len) = attrs.min_length {
4704 let min_len_usize = min_len as usize;
4705 checks.push(quote! {
4706 if let ::core::option::Option::Some(__s) = #val_ref {
4707 if __s.len() < #min_len_usize {
4708 __errors.add(
4709 #name_lit,
4710 ::std::format!("Ensure this value has at least {} characters.", #min_len_usize),
4711 );
4712 }
4713 }
4714 });
4715 }
4716 if let Some(max_len) = attrs.max_length {
4717 let max_len_usize = max_len as usize;
4718 checks.push(quote! {
4719 if let ::core::option::Option::Some(__s) = #val_ref {
4720 if __s.len() > #max_len_usize {
4721 __errors.add(
4722 #name_lit,
4723 ::std::format!("Ensure this value has at most {} characters.", #max_len_usize),
4724 );
4725 }
4726 }
4727 });
4728 }
4729 }
4730
4731 if is_numeric {
4732 if let Some(min) = attrs.min {
4733 checks.push(quote! {
4734 if let ::core::option::Option::Some(__n) = #val_ref {
4735 if (*__n as f64) < (#min as f64) {
4736 __errors.add(
4737 #name_lit,
4738 ::std::format!("Ensure this value is greater than or equal to {}.", #min),
4739 );
4740 }
4741 }
4742 });
4743 }
4744 if let Some(max) = attrs.max {
4745 checks.push(quote! {
4746 if let ::core::option::Option::Some(__n) = #val_ref {
4747 if (*__n as f64) > (#max as f64) {
4748 __errors.add(
4749 #name_lit,
4750 ::std::format!("Ensure this value is less than or equal to {}.", #max),
4751 );
4752 }
4753 }
4754 });
4755 }
4756 }
4757
4758 quote! { #( #checks )* }
4759}
4760
4761struct ViewSetAttrs {
4766 model: syn::Path,
4767 fields: Option<Vec<String>>,
4768 filter_fields: Vec<String>,
4769 search_fields: Vec<String>,
4770 ordering: Vec<(String, bool)>,
4772 page_size: Option<usize>,
4773 read_only: bool,
4774 perms: ViewSetPermsAttrs,
4775}
4776
4777#[derive(Default)]
4778struct ViewSetPermsAttrs {
4779 list: Vec<String>,
4780 retrieve: Vec<String>,
4781 create: Vec<String>,
4782 update: Vec<String>,
4783 destroy: Vec<String>,
4784}
4785
4786fn expand_viewset(input: &DeriveInput) -> syn::Result<TokenStream2> {
4787 let struct_name = &input.ident;
4788
4789 match &input.data {
4791 Data::Struct(s) => match &s.fields {
4792 Fields::Unit | Fields::Named(_) => {}
4793 Fields::Unnamed(_) => {
4794 return Err(syn::Error::new_spanned(
4795 struct_name,
4796 "ViewSet can only be derived on a unit struct or an empty named struct",
4797 ));
4798 }
4799 },
4800 _ => {
4801 return Err(syn::Error::new_spanned(
4802 struct_name,
4803 "ViewSet can only be derived on a struct",
4804 ));
4805 }
4806 }
4807
4808 let attrs = parse_viewset_attrs(input)?;
4809 let model_path = &attrs.model;
4810
4811 let fields_call = if let Some(ref fields) = attrs.fields {
4813 let lits = fields.iter().map(|f| f.as_str());
4814 quote!(.fields(&[ #(#lits),* ]))
4815 } else {
4816 quote!()
4817 };
4818
4819 let filter_fields_call = if attrs.filter_fields.is_empty() {
4820 quote!()
4821 } else {
4822 let lits = attrs.filter_fields.iter().map(|f| f.as_str());
4823 quote!(.filter_fields(&[ #(#lits),* ]))
4824 };
4825
4826 let search_fields_call = if attrs.search_fields.is_empty() {
4827 quote!()
4828 } else {
4829 let lits = attrs.search_fields.iter().map(|f| f.as_str());
4830 quote!(.search_fields(&[ #(#lits),* ]))
4831 };
4832
4833 let ordering_call = if attrs.ordering.is_empty() {
4834 quote!()
4835 } else {
4836 let pairs = attrs.ordering.iter().map(|(f, desc)| {
4837 let f = f.as_str();
4838 quote!((#f, #desc))
4839 });
4840 quote!(.ordering(&[ #(#pairs),* ]))
4841 };
4842
4843 let page_size_call = if let Some(n) = attrs.page_size {
4844 quote!(.page_size(#n))
4845 } else {
4846 quote!()
4847 };
4848
4849 let read_only_call = if attrs.read_only {
4850 quote!(.read_only())
4851 } else {
4852 quote!()
4853 };
4854
4855 let perms = &attrs.perms;
4856 let perms_call = if perms.list.is_empty()
4857 && perms.retrieve.is_empty()
4858 && perms.create.is_empty()
4859 && perms.update.is_empty()
4860 && perms.destroy.is_empty()
4861 {
4862 quote!()
4863 } else {
4864 let list_lits = perms.list.iter().map(|s| s.as_str());
4865 let retrieve_lits = perms.retrieve.iter().map(|s| s.as_str());
4866 let create_lits = perms.create.iter().map(|s| s.as_str());
4867 let update_lits = perms.update.iter().map(|s| s.as_str());
4868 let destroy_lits = perms.destroy.iter().map(|s| s.as_str());
4869 quote! {
4870 .permissions(::rustango::viewset::ViewSetPerms {
4871 list: ::std::vec![ #(#list_lits.to_owned()),* ],
4872 retrieve: ::std::vec![ #(#retrieve_lits.to_owned()),* ],
4873 create: ::std::vec![ #(#create_lits.to_owned()),* ],
4874 update: ::std::vec![ #(#update_lits.to_owned()),* ],
4875 destroy: ::std::vec![ #(#destroy_lits.to_owned()),* ],
4876 })
4877 }
4878 };
4879
4880 Ok(quote! {
4881 impl #struct_name {
4882 pub fn router(prefix: &str, pool: ::rustango::sql::sqlx::PgPool) -> ::axum::Router {
4885 ::rustango::viewset::ViewSet::for_model(#model_path::SCHEMA)
4886 #fields_call
4887 #filter_fields_call
4888 #search_fields_call
4889 #ordering_call
4890 #page_size_call
4891 #perms_call
4892 #read_only_call
4893 .router(prefix, pool)
4894 }
4895 }
4896 })
4897}
4898
4899fn parse_viewset_attrs(input: &DeriveInput) -> syn::Result<ViewSetAttrs> {
4900 let mut model: Option<syn::Path> = None;
4901 let mut fields: Option<Vec<String>> = None;
4902 let mut filter_fields: Vec<String> = Vec::new();
4903 let mut search_fields: Vec<String> = Vec::new();
4904 let mut ordering: Vec<(String, bool)> = Vec::new();
4905 let mut page_size: Option<usize> = None;
4906 let mut read_only = false;
4907 let mut perms = ViewSetPermsAttrs::default();
4908
4909 for attr in &input.attrs {
4910 if !attr.path().is_ident("viewset") {
4911 continue;
4912 }
4913 attr.parse_nested_meta(|meta| {
4914 if meta.path.is_ident("model") {
4915 let path: syn::Path = meta.value()?.parse()?;
4916 model = Some(path);
4917 return Ok(());
4918 }
4919 if meta.path.is_ident("fields") {
4920 let s: LitStr = meta.value()?.parse()?;
4921 fields = Some(split_field_list(&s.value()));
4922 return Ok(());
4923 }
4924 if meta.path.is_ident("filter_fields") {
4925 let s: LitStr = meta.value()?.parse()?;
4926 filter_fields = split_field_list(&s.value());
4927 return Ok(());
4928 }
4929 if meta.path.is_ident("search_fields") {
4930 let s: LitStr = meta.value()?.parse()?;
4931 search_fields = split_field_list(&s.value());
4932 return Ok(());
4933 }
4934 if meta.path.is_ident("ordering") {
4935 let s: LitStr = meta.value()?.parse()?;
4936 ordering = parse_ordering_list(&s.value());
4937 return Ok(());
4938 }
4939 if meta.path.is_ident("page_size") {
4940 let lit: syn::LitInt = meta.value()?.parse()?;
4941 page_size = Some(lit.base10_parse::<usize>()?);
4942 return Ok(());
4943 }
4944 if meta.path.is_ident("read_only") {
4945 read_only = true;
4946 return Ok(());
4947 }
4948 if meta.path.is_ident("permissions") {
4949 meta.parse_nested_meta(|inner| {
4950 let parse_codenames = |inner: &syn::meta::ParseNestedMeta| -> syn::Result<Vec<String>> {
4951 let s: LitStr = inner.value()?.parse()?;
4952 Ok(split_field_list(&s.value()))
4953 };
4954 if inner.path.is_ident("list") {
4955 perms.list = parse_codenames(&inner)?;
4956 } else if inner.path.is_ident("retrieve") {
4957 perms.retrieve = parse_codenames(&inner)?;
4958 } else if inner.path.is_ident("create") {
4959 perms.create = parse_codenames(&inner)?;
4960 } else if inner.path.is_ident("update") {
4961 perms.update = parse_codenames(&inner)?;
4962 } else if inner.path.is_ident("destroy") {
4963 perms.destroy = parse_codenames(&inner)?;
4964 } else {
4965 return Err(inner.error(
4966 "unknown permissions key (supported: list, retrieve, create, update, destroy)",
4967 ));
4968 }
4969 Ok(())
4970 })?;
4971 return Ok(());
4972 }
4973 Err(meta.error(
4974 "unknown viewset attribute (supported: model, fields, filter_fields, \
4975 search_fields, ordering, page_size, read_only, permissions(...))",
4976 ))
4977 })?;
4978 }
4979
4980 let model = model.ok_or_else(|| {
4981 syn::Error::new_spanned(
4982 &input.ident,
4983 "`#[viewset(model = SomeModel)]` is required",
4984 )
4985 })?;
4986
4987 Ok(ViewSetAttrs {
4988 model,
4989 fields,
4990 filter_fields,
4991 search_fields,
4992 ordering,
4993 page_size,
4994 read_only,
4995 perms,
4996 })
4997}
4998
4999struct SerializerContainerAttrs {
5002 model: syn::Path,
5003}
5004
5005#[derive(Default)]
5006struct SerializerFieldAttrs {
5007 read_only: bool,
5008 write_only: bool,
5009 source: Option<String>,
5010 skip: bool,
5011 method: Option<String>,
5015 validate: Option<String>,
5020 nested: bool,
5030 nested_strict: bool,
5035 many: Option<syn::Type>,
5044}
5045
5046fn parse_serializer_container_attrs(input: &DeriveInput) -> syn::Result<SerializerContainerAttrs> {
5047 let mut model: Option<syn::Path> = None;
5048 for attr in &input.attrs {
5049 if !attr.path().is_ident("serializer") {
5050 continue;
5051 }
5052 attr.parse_nested_meta(|meta| {
5053 if meta.path.is_ident("model") {
5054 let _eq: syn::Token![=] = meta.input.parse()?;
5055 model = Some(meta.input.parse()?);
5056 return Ok(());
5057 }
5058 Err(meta.error("unknown serializer container attribute (supported: `model`)"))
5059 })?;
5060 }
5061 let model = model.ok_or_else(|| {
5062 syn::Error::new_spanned(
5063 &input.ident,
5064 "`#[serializer(model = SomeModel)]` is required",
5065 )
5066 })?;
5067 Ok(SerializerContainerAttrs { model })
5068}
5069
5070fn parse_serializer_field_attrs(field: &syn::Field) -> syn::Result<SerializerFieldAttrs> {
5071 let mut out = SerializerFieldAttrs::default();
5072 for attr in &field.attrs {
5073 if !attr.path().is_ident("serializer") {
5074 continue;
5075 }
5076 attr.parse_nested_meta(|meta| {
5077 if meta.path.is_ident("read_only") {
5078 out.read_only = true;
5079 return Ok(());
5080 }
5081 if meta.path.is_ident("write_only") {
5082 out.write_only = true;
5083 return Ok(());
5084 }
5085 if meta.path.is_ident("skip") {
5086 out.skip = true;
5087 return Ok(());
5088 }
5089 if meta.path.is_ident("source") {
5090 let s: LitStr = meta.value()?.parse()?;
5091 out.source = Some(s.value());
5092 return Ok(());
5093 }
5094 if meta.path.is_ident("method") {
5095 let s: LitStr = meta.value()?.parse()?;
5096 out.method = Some(s.value());
5097 return Ok(());
5098 }
5099 if meta.path.is_ident("validate") {
5100 let s: LitStr = meta.value()?.parse()?;
5101 out.validate = Some(s.value());
5102 return Ok(());
5103 }
5104 if meta.path.is_ident("many") {
5105 let _eq: syn::Token![=] = meta.input.parse()?;
5106 out.many = Some(meta.input.parse()?);
5107 return Ok(());
5108 }
5109 if meta.path.is_ident("nested") {
5110 out.nested = true;
5111 if meta.input.peek(syn::token::Paren) {
5114 meta.parse_nested_meta(|inner| {
5115 if inner.path.is_ident("strict") {
5116 out.nested_strict = true;
5117 return Ok(());
5118 }
5119 Err(inner.error("unknown nested sub-attribute (supported: `strict`)"))
5120 })?;
5121 }
5122 return Ok(());
5123 }
5124 Err(meta.error(
5125 "unknown serializer field attribute (supported: \
5126 `read_only`, `write_only`, `source`, `skip`, `method`, `validate`, `nested`)",
5127 ))
5128 })?;
5129 }
5130 if out.read_only && out.write_only {
5132 return Err(syn::Error::new_spanned(
5133 field,
5134 "a field cannot be both `read_only` and `write_only`",
5135 ));
5136 }
5137 if out.method.is_some() && out.source.is_some() {
5138 return Err(syn::Error::new_spanned(
5139 field,
5140 "`method` and `source` are mutually exclusive — `method` computes \
5141 the value from a method, `source` reads it from a different model field",
5142 ));
5143 }
5144 Ok(out)
5145}
5146
5147fn expand_serializer(input: &DeriveInput) -> syn::Result<TokenStream2> {
5148 let struct_name = &input.ident;
5149 let struct_name_lit = struct_name.to_string();
5150
5151 let Data::Struct(data) = &input.data else {
5152 return Err(syn::Error::new_spanned(
5153 struct_name,
5154 "Serializer can only be derived on structs",
5155 ));
5156 };
5157 let Fields::Named(named) = &data.fields else {
5158 return Err(syn::Error::new_spanned(
5159 struct_name,
5160 "Serializer requires a struct with named fields",
5161 ));
5162 };
5163
5164 let container = parse_serializer_container_attrs(input)?;
5165 let model_path = &container.model;
5166
5167 #[allow(dead_code)]
5171 struct FieldInfo {
5172 ident: syn::Ident,
5173 ty: syn::Type,
5174 attrs: SerializerFieldAttrs,
5175 }
5176 let mut fields_info: Vec<FieldInfo> = Vec::new();
5177 for field in &named.named {
5178 let ident = field.ident.clone().expect("named field has ident");
5179 let attrs = parse_serializer_field_attrs(field)?;
5180 fields_info.push(FieldInfo {
5181 ident,
5182 ty: field.ty.clone(),
5183 attrs,
5184 });
5185 }
5186
5187 let from_model_fields = fields_info.iter().map(|fi| {
5189 let ident = &fi.ident;
5190 let ty = &fi.ty;
5191 if let Some(_inner) = &fi.attrs.many {
5192 quote! { #ident: ::std::vec::Vec::new() }
5196 } else if let Some(method) = &fi.attrs.method {
5197 let method_ident = syn::Ident::new(method, ident.span());
5201 quote! { #ident: Self::#method_ident(model) }
5202 } else if fi.attrs.nested {
5203 let src_name = fi.attrs.source.as_deref().unwrap_or(&fi.ident.to_string()).to_owned();
5219 let src_ident = syn::Ident::new(&src_name, ident.span());
5220 if fi.attrs.nested_strict {
5221 let panic_msg = format!(
5222 "nested(strict) serializer for `{ident}` requires `model.{src_name}` to be loaded — \
5223 call .get(&pool).await? or .select_related(\"{src_name}\") on the model first",
5224 );
5225 quote! {
5226 #ident: <#ty as ::rustango::serializer::ModelSerializer>::from_model(
5227 model.#src_ident.value().expect(#panic_msg),
5228 )
5229 }
5230 } else {
5231 quote! {
5232 #ident: match model.#src_ident.value() {
5233 ::core::option::Option::Some(__loaded) =>
5234 <#ty as ::rustango::serializer::ModelSerializer>::from_model(__loaded),
5235 ::core::option::Option::None =>
5236 ::core::default::Default::default(),
5237 }
5238 }
5239 }
5240 } else if fi.attrs.write_only || fi.attrs.skip {
5241 quote! { #ident: ::core::default::Default::default() }
5243 } else if let Some(src) = &fi.attrs.source {
5244 let src_ident = syn::Ident::new(src, ident.span());
5245 quote! { #ident: ::core::clone::Clone::clone(&model.#src_ident) }
5246 } else {
5247 quote! { #ident: ::core::clone::Clone::clone(&model.#ident) }
5248 }
5249 });
5250
5251 let validator_calls: Vec<_> = fields_info.iter().filter_map(|fi| {
5255 let ident = &fi.ident;
5256 let name_lit = ident.to_string();
5257 let method = fi.attrs.validate.as_ref()?;
5258 let method_ident = syn::Ident::new(method, ident.span());
5259 Some(quote! {
5260 if let ::core::result::Result::Err(__e) = Self::#method_ident(&self.#ident) {
5261 __errors.add(#name_lit.to_owned(), __e);
5262 }
5263 })
5264 }).collect();
5265 let validate_method = if validator_calls.is_empty() {
5266 quote! {}
5267 } else {
5268 quote! {
5269 impl #struct_name {
5270 pub fn validate(&self) -> ::core::result::Result<(), ::rustango::forms::FormErrors> {
5274 let mut __errors = ::rustango::forms::FormErrors::default();
5275 #( #validator_calls )*
5276 if __errors.is_empty() {
5277 ::core::result::Result::Ok(())
5278 } else {
5279 ::core::result::Result::Err(__errors)
5280 }
5281 }
5282 }
5283 }
5284 };
5285
5286 let many_setters: Vec<_> = fields_info.iter().filter_map(|fi| {
5290 let many_ty = fi.attrs.many.as_ref()?;
5291 let ident = &fi.ident;
5292 let setter = syn::Ident::new(&format!("set_{ident}"), ident.span());
5293 Some(quote! {
5294 pub fn #setter(
5299 &mut self,
5300 models: &[<#many_ty as ::rustango::serializer::ModelSerializer>::Model],
5301 ) -> &mut Self {
5302 self.#ident = models.iter()
5303 .map(<#many_ty as ::rustango::serializer::ModelSerializer>::from_model)
5304 .collect();
5305 self
5306 }
5307 })
5308 }).collect();
5309 let many_setters_impl = if many_setters.is_empty() {
5310 quote! {}
5311 } else {
5312 quote! {
5313 impl #struct_name {
5314 #( #many_setters )*
5315 }
5316 }
5317 };
5318
5319 let output_fields: Vec<_> = fields_info
5321 .iter()
5322 .filter(|fi| !fi.attrs.write_only)
5323 .collect();
5324 let output_field_count = output_fields.len();
5325 let serialize_fields = output_fields.iter().map(|fi| {
5326 let ident = &fi.ident;
5327 let name_lit = ident.to_string();
5328 quote! { __state.serialize_field(#name_lit, &self.#ident)?; }
5329 });
5330
5331 let writable_lits: Vec<_> = fields_info
5333 .iter()
5334 .filter(|fi| !fi.attrs.read_only && !fi.attrs.skip)
5335 .map(|fi| fi.ident.to_string())
5336 .collect();
5337
5338 let openapi_impl = {
5342 #[cfg(feature = "openapi")]
5343 {
5344 let property_calls = output_fields.iter().map(|fi| {
5345 let ident = &fi.ident;
5346 let name_lit = ident.to_string();
5347 let ty = &fi.ty;
5348 let nullable_call = if is_option(ty) {
5349 quote! { .nullable() }
5350 } else {
5351 quote! {}
5352 };
5353 quote! {
5354 .property(
5355 #name_lit,
5356 <#ty as ::rustango::openapi::OpenApiSchema>::openapi_schema()
5357 #nullable_call,
5358 )
5359 }
5360 });
5361 let required_lits: Vec<_> = output_fields
5362 .iter()
5363 .filter(|fi| !is_option(&fi.ty))
5364 .map(|fi| fi.ident.to_string())
5365 .collect();
5366 quote! {
5367 impl ::rustango::openapi::OpenApiSchema for #struct_name {
5368 fn openapi_schema() -> ::rustango::openapi::Schema {
5369 ::rustango::openapi::Schema::object()
5370 #( #property_calls )*
5371 .required([ #( #required_lits ),* ])
5372 }
5373 }
5374 }
5375 }
5376 #[cfg(not(feature = "openapi"))]
5377 {
5378 quote! {}
5379 }
5380 };
5381
5382 Ok(quote! {
5383 impl ::rustango::serializer::ModelSerializer for #struct_name {
5384 type Model = #model_path;
5385
5386 fn from_model(model: &Self::Model) -> Self {
5387 Self {
5388 #( #from_model_fields ),*
5389 }
5390 }
5391
5392 fn writable_fields() -> &'static [&'static str] {
5393 &[ #( #writable_lits ),* ]
5394 }
5395 }
5396
5397 impl ::serde::Serialize for #struct_name {
5398 fn serialize<S>(&self, serializer: S)
5399 -> ::core::result::Result<S::Ok, S::Error>
5400 where
5401 S: ::serde::Serializer,
5402 {
5403 use ::serde::ser::SerializeStruct;
5404 let mut __state = serializer.serialize_struct(
5405 #struct_name_lit,
5406 #output_field_count,
5407 )?;
5408 #( #serialize_fields )*
5409 __state.end()
5410 }
5411 }
5412
5413 #openapi_impl
5414
5415 #validate_method
5416
5417 #many_setters_impl
5418 })
5419}
5420
5421#[cfg_attr(not(feature = "openapi"), allow(dead_code))]
5425fn is_option(ty: &syn::Type) -> bool {
5426 if let syn::Type::Path(p) = ty {
5427 if let Some(last) = p.path.segments.last() {
5428 return last.ident == "Option";
5429 }
5430 }
5431 false
5432}