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 quote! {
591 #fk_col => {
592 let _parent: #parent_ty = <#parent_ty>::__rustango_from_aliased_row(row, alias)?;
593 let _pk = match <#parent_ty>::__rustango_pk_value(&_parent) {
594 ::rustango::core::SqlValue::I64(v) => v,
595 _ => 0i64,
596 };
597 self.#field_ident = ::rustango::sql::ForeignKey::loaded(_pk, _parent);
598 ::core::result::Result::Ok(true)
599 }
600 }
601 });
602 quote! {
603 impl ::rustango::sql::LoadRelated for #struct_name {
604 #[allow(unused_variables)]
605 fn __rustango_load_related(
606 &mut self,
607 row: &::rustango::sql::sqlx::postgres::PgRow,
608 field_name: &str,
609 alias: &str,
610 ) -> ::core::result::Result<bool, ::rustango::sql::sqlx::Error> {
611 match field_name {
612 #( #arms )*
613 _ => ::core::result::Result::Ok(false),
614 }
615 }
616 }
617 }
618}
619
620fn load_related_impl_my_tokens(
628 struct_name: &syn::Ident,
629 fk_relations: &[FkRelation],
630) -> TokenStream2 {
631 let arms = fk_relations.iter().map(|rel| {
632 let parent_ty = &rel.parent_type;
633 let fk_col = rel.fk_column.as_str();
634 let field_ident = syn::Ident::new(fk_col, proc_macro2::Span::call_site());
635 quote! {
640 #fk_col => {
641 let _parent: #parent_ty =
642 <#parent_ty>::__rustango_from_aliased_my_row(row, alias)?;
643 let _pk = match <#parent_ty>::__rustango_pk_value(&_parent) {
644 ::rustango::core::SqlValue::I64(v) => v,
645 _ => 0i64,
646 };
647 __self.#field_ident = ::rustango::sql::ForeignKey::loaded(_pk, _parent);
648 ::core::result::Result::Ok(true)
649 }
650 }
651 });
652 quote! {
653 ::rustango::__impl_my_load_related!(#struct_name, |__self, row, field_name, alias| {
654 #( #arms )*
655 });
656 }
657}
658
659fn fk_pk_access_impl_tokens(
667 struct_name: &syn::Ident,
668 fk_relations: &[FkRelation],
669) -> TokenStream2 {
670 let arms = fk_relations.iter().map(|rel| {
671 let fk_col = rel.fk_column.as_str();
672 let field_ident = syn::Ident::new(fk_col, proc_macro2::Span::call_site());
673 quote! {
674 #fk_col => ::core::option::Option::Some(self.#field_ident.pk()),
675 }
676 });
677 quote! {
678 impl ::rustango::sql::FkPkAccess for #struct_name {
679 #[allow(unused_variables)]
680 fn __rustango_fk_pk(&self, field_name: &str) -> ::core::option::Option<i64> {
681 match field_name {
682 #( #arms )*
683 _ => ::core::option::Option::None,
684 }
685 }
686 }
687 }
688}
689
690fn reverse_helper_tokens(
696 child_ident: &syn::Ident,
697 fk_relations: &[FkRelation],
698) -> TokenStream2 {
699 if fk_relations.is_empty() {
700 return TokenStream2::new();
701 }
702 let suffix = format!("{}_set", to_snake_case(&child_ident.to_string()));
706 let method_ident = syn::Ident::new(&suffix, child_ident.span());
707 let impls = fk_relations.iter().map(|rel| {
708 let parent_ty = &rel.parent_type;
709 let fk_col = rel.fk_column.as_str();
710 let doc = format!(
711 "Fetch every `{child_ident}` whose `{fk_col}` foreign key points at this row. \
712 Single SQL query — `SELECT … FROM <{child_ident} table> WHERE {fk_col} = $1` — \
713 generated from the FK declaration on `{child_ident}::{fk_col}`. Composes with \
714 further `{child_ident}::objects()` filters via direct queryset use."
715 );
716 quote! {
717 impl #parent_ty {
718 #[doc = #doc]
719 pub async fn #method_ident<'_c, _E>(
724 &self,
725 _executor: _E,
726 ) -> ::core::result::Result<
727 ::std::vec::Vec<#child_ident>,
728 ::rustango::sql::ExecError,
729 >
730 where
731 _E: ::rustango::sql::sqlx::Executor<
732 '_c,
733 Database = ::rustango::sql::sqlx::Postgres,
734 >,
735 {
736 let _pk: ::rustango::core::SqlValue = self.__rustango_pk_value();
737 ::rustango::query::QuerySet::<#child_ident>::new()
738 .filter(#fk_col, ::rustango::core::Op::Eq, _pk)
739 .fetch_on(_executor)
740 .await
741 }
742 }
743 }
744 });
745 quote! { #( #impls )* }
746}
747
748fn m2m_accessor_tokens(struct_name: &syn::Ident, m2m_relations: &[M2MAttr]) -> TokenStream2 {
751 if m2m_relations.is_empty() {
752 return TokenStream2::new();
753 }
754 let methods = m2m_relations.iter().map(|rel| {
755 let method_name = format!("{}_m2m", rel.name);
756 let method_ident = syn::Ident::new(&method_name, struct_name.span());
757 let through = rel.through.as_str();
758 let src_col = rel.src.as_str();
759 let dst_col = rel.dst.as_str();
760 quote! {
761 pub fn #method_ident(&self) -> ::rustango::sql::M2MManager {
762 ::rustango::sql::M2MManager {
763 src_pk: self.__rustango_pk_value(),
764 through: #through,
765 src_col: #src_col,
766 dst_col: #dst_col,
767 }
768 }
769 }
770 });
771 quote! {
772 impl #struct_name {
773 #( #methods )*
774 }
775 }
776}
777
778struct ColumnEntry {
779 ident: syn::Ident,
782 value_ty: Type,
784 name: String,
786 column: String,
788 field_type_tokens: TokenStream2,
790}
791
792struct CollectedFields {
793 field_schemas: Vec<TokenStream2>,
794 from_row_inits: Vec<TokenStream2>,
795 from_aliased_row_inits: Vec<TokenStream2>,
799 insert_columns: Vec<TokenStream2>,
802 insert_values: Vec<TokenStream2>,
805 insert_pushes: Vec<TokenStream2>,
810 returning_cols: Vec<TokenStream2>,
813 auto_assigns: Vec<TokenStream2>,
816 auto_field_idents: Vec<(syn::Ident, String)>,
820 first_auto_value_ty: Option<Type>,
823 bulk_pushes_no_auto: Vec<TokenStream2>,
827 bulk_pushes_all: Vec<TokenStream2>,
831 bulk_columns_no_auto: Vec<TokenStream2>,
834 bulk_columns_all: Vec<TokenStream2>,
837 bulk_auto_uniformity: Vec<TokenStream2>,
841 first_auto_ident: Option<syn::Ident>,
844 has_auto: bool,
846 pk_is_auto: bool,
850 update_assignments: Vec<TokenStream2>,
853 upsert_update_columns: Vec<TokenStream2>,
856 primary_key: Option<(syn::Ident, String)>,
857 column_entries: Vec<ColumnEntry>,
858 field_names: Vec<String>,
861 fk_relations: Vec<FkRelation>,
866 soft_delete_column: Option<String>,
871}
872
873#[derive(Clone)]
874struct FkRelation {
875 parent_type: Type,
878 fk_column: String,
881}
882
883fn collect_fields(named: &syn::FieldsNamed, table: &str) -> syn::Result<CollectedFields> {
884 let cap = named.named.len();
885 let mut out = CollectedFields {
886 field_schemas: Vec::with_capacity(cap),
887 from_row_inits: Vec::with_capacity(cap),
888 from_aliased_row_inits: Vec::with_capacity(cap),
889 insert_columns: Vec::with_capacity(cap),
890 insert_values: Vec::with_capacity(cap),
891 insert_pushes: Vec::with_capacity(cap),
892 returning_cols: Vec::new(),
893 auto_assigns: Vec::new(),
894 auto_field_idents: Vec::new(),
895 first_auto_value_ty: None,
896 bulk_pushes_no_auto: Vec::with_capacity(cap),
897 bulk_pushes_all: Vec::with_capacity(cap),
898 bulk_columns_no_auto: Vec::with_capacity(cap),
899 bulk_columns_all: Vec::with_capacity(cap),
900 bulk_auto_uniformity: Vec::new(),
901 first_auto_ident: None,
902 has_auto: false,
903 pk_is_auto: false,
904 update_assignments: Vec::with_capacity(cap),
905 upsert_update_columns: Vec::with_capacity(cap),
906 primary_key: None,
907 column_entries: Vec::with_capacity(cap),
908 field_names: Vec::with_capacity(cap),
909 fk_relations: Vec::new(),
910 soft_delete_column: None,
911 };
912
913 for field in &named.named {
914 let info = process_field(field, table)?;
915 out.field_names.push(info.ident.to_string());
916 out.field_schemas.push(info.schema);
917 out.from_row_inits.push(info.from_row_init);
918 out.from_aliased_row_inits.push(info.from_aliased_row_init);
919 if let Some(parent_ty) = info.fk_inner.clone() {
920 out.fk_relations.push(FkRelation {
921 parent_type: parent_ty,
922 fk_column: info.column.clone(),
923 });
924 }
925 if info.soft_delete {
926 if out.soft_delete_column.is_some() {
927 return Err(syn::Error::new_spanned(
928 field,
929 "only one field may be marked `#[rustango(soft_delete)]`",
930 ));
931 }
932 out.soft_delete_column = Some(info.column.clone());
933 }
934 let column = info.column.as_str();
935 let ident = info.ident;
936 out.insert_columns.push(quote!(#column));
937 out.insert_values.push(quote! {
938 ::core::convert::Into::<::rustango::core::SqlValue>::into(
939 ::core::clone::Clone::clone(&self.#ident)
940 )
941 });
942 if info.auto {
943 out.has_auto = true;
944 if out.first_auto_ident.is_none() {
945 out.first_auto_ident = Some(ident.clone());
946 out.first_auto_value_ty = auto_inner_type(info.value_ty).cloned();
947 }
948 out.returning_cols.push(quote!(#column));
949 out.auto_field_idents
950 .push((ident.clone(), info.column.clone()));
951 out.auto_assigns.push(quote! {
952 self.#ident = ::rustango::sql::try_get_returning(_returning_row, #column)?;
953 });
954 out.insert_pushes.push(quote! {
955 if let ::rustango::sql::Auto::Set(_v) = &self.#ident {
956 _columns.push(#column);
957 _values.push(::core::convert::Into::<::rustango::core::SqlValue>::into(
958 ::core::clone::Clone::clone(_v)
959 ));
960 }
961 });
962 out.bulk_columns_all.push(quote!(#column));
965 out.bulk_pushes_all.push(quote! {
966 _row_vals.push(::core::convert::Into::<::rustango::core::SqlValue>::into(
967 ::core::clone::Clone::clone(&_row.#ident)
968 ));
969 });
970 let ident_clone = ident.clone();
974 out.bulk_auto_uniformity.push(quote! {
975 for _r in rows.iter().skip(1) {
976 if matches!(_r.#ident_clone, ::rustango::sql::Auto::Unset) != _first_unset {
977 return ::core::result::Result::Err(
978 ::rustango::sql::ExecError::Sql(
979 ::rustango::sql::SqlError::BulkAutoMixed
980 )
981 );
982 }
983 }
984 });
985 } else {
986 out.insert_pushes.push(quote! {
987 _columns.push(#column);
988 _values.push(::core::convert::Into::<::rustango::core::SqlValue>::into(
989 ::core::clone::Clone::clone(&self.#ident)
990 ));
991 });
992 out.bulk_columns_no_auto.push(quote!(#column));
994 out.bulk_columns_all.push(quote!(#column));
995 let push_expr = quote! {
996 _row_vals.push(::core::convert::Into::<::rustango::core::SqlValue>::into(
997 ::core::clone::Clone::clone(&_row.#ident)
998 ));
999 };
1000 out.bulk_pushes_no_auto.push(push_expr.clone());
1001 out.bulk_pushes_all.push(push_expr);
1002 }
1003 if info.primary_key {
1004 if out.primary_key.is_some() {
1005 return Err(syn::Error::new_spanned(
1006 field,
1007 "only one field may be marked `#[rustango(primary_key)]`",
1008 ));
1009 }
1010 out.primary_key = Some((ident.clone(), info.column.clone()));
1011 if info.auto {
1012 out.pk_is_auto = true;
1013 }
1014 } else if info.auto_now_add {
1015 } else if info.auto_now {
1017 out.update_assignments.push(quote! {
1022 ::rustango::core::Assignment {
1023 column: #column,
1024 value: ::core::convert::Into::<::rustango::core::SqlValue>::into(
1025 ::chrono::Utc::now()
1026 ),
1027 }
1028 });
1029 out.upsert_update_columns.push(quote!(#column));
1030 } else {
1031 out.update_assignments.push(quote! {
1032 ::rustango::core::Assignment {
1033 column: #column,
1034 value: ::core::convert::Into::<::rustango::core::SqlValue>::into(
1035 ::core::clone::Clone::clone(&self.#ident)
1036 ),
1037 }
1038 });
1039 out.upsert_update_columns.push(quote!(#column));
1040 }
1041 out.column_entries.push(ColumnEntry {
1042 ident: ident.clone(),
1043 value_ty: info.value_ty.clone(),
1044 name: ident.to_string(),
1045 column: info.column.clone(),
1046 field_type_tokens: info.field_type_tokens,
1047 });
1048 }
1049 Ok(out)
1050}
1051
1052fn model_impl_tokens(
1053 struct_name: &syn::Ident,
1054 model_name: &str,
1055 table: &str,
1056 display: Option<&str>,
1057 app_label: Option<&str>,
1058 admin: Option<&AdminAttrs>,
1059 field_schemas: &[TokenStream2],
1060 soft_delete_column: Option<&str>,
1061 permissions: bool,
1062 audit_track: Option<&[String]>,
1063 m2m_relations: &[M2MAttr],
1064 indexes: &[IndexAttr],
1065 checks: &[CheckAttr],
1066 composite_fks: &[CompositeFkAttr],
1067 generic_fks: &[GenericFkAttr],
1068) -> TokenStream2 {
1069 let display_tokens = if let Some(name) = display {
1070 quote!(::core::option::Option::Some(#name))
1071 } else {
1072 quote!(::core::option::Option::None)
1073 };
1074 let app_label_tokens = if let Some(name) = app_label {
1075 quote!(::core::option::Option::Some(#name))
1076 } else {
1077 quote!(::core::option::Option::None)
1078 };
1079 let soft_delete_tokens = if let Some(col) = soft_delete_column {
1080 quote!(::core::option::Option::Some(#col))
1081 } else {
1082 quote!(::core::option::Option::None)
1083 };
1084 let audit_track_tokens = match audit_track {
1085 None => quote!(::core::option::Option::None),
1086 Some(names) => {
1087 let lits = names.iter().map(|n| n.as_str());
1088 quote!(::core::option::Option::Some(&[ #(#lits),* ]))
1089 }
1090 };
1091 let admin_tokens = admin_config_tokens(admin);
1092 let indexes_tokens = indexes.iter().map(|idx| {
1093 let name = idx.name.as_deref().unwrap_or("unnamed_index");
1094 let cols: Vec<&str> = idx.columns.iter().map(String::as_str).collect();
1095 let unique = idx.unique;
1096 quote! {
1097 ::rustango::core::IndexSchema {
1098 name: #name,
1099 columns: &[ #(#cols),* ],
1100 unique: #unique,
1101 }
1102 }
1103 });
1104 let checks_tokens = checks.iter().map(|c| {
1105 let name = c.name.as_str();
1106 let expr = c.expr.as_str();
1107 quote! {
1108 ::rustango::core::CheckConstraint {
1109 name: #name,
1110 expr: #expr,
1111 }
1112 }
1113 });
1114 let composite_fk_tokens = composite_fks.iter().map(|rel| {
1115 let name = rel.name.as_str();
1116 let to = rel.to.as_str();
1117 let from_cols: Vec<&str> = rel.from.iter().map(String::as_str).collect();
1118 let on_cols: Vec<&str> = rel.on.iter().map(String::as_str).collect();
1119 quote! {
1120 ::rustango::core::CompositeFkRelation {
1121 name: #name,
1122 to: #to,
1123 from: &[ #(#from_cols),* ],
1124 on: &[ #(#on_cols),* ],
1125 }
1126 }
1127 });
1128 let generic_fk_tokens = generic_fks.iter().map(|rel| {
1129 let name = rel.name.as_str();
1130 let ct_col = rel.ct_column.as_str();
1131 let pk_col = rel.pk_column.as_str();
1132 quote! {
1133 ::rustango::core::GenericRelation {
1134 name: #name,
1135 ct_column: #ct_col,
1136 pk_column: #pk_col,
1137 }
1138 }
1139 });
1140 let m2m_tokens = m2m_relations.iter().map(|rel| {
1141 let name = rel.name.as_str();
1142 let to = rel.to.as_str();
1143 let through = rel.through.as_str();
1144 let src = rel.src.as_str();
1145 let dst = rel.dst.as_str();
1146 quote! {
1147 ::rustango::core::M2MRelation {
1148 name: #name,
1149 to: #to,
1150 through: #through,
1151 src_col: #src,
1152 dst_col: #dst,
1153 }
1154 }
1155 });
1156 quote! {
1157 impl ::rustango::core::Model for #struct_name {
1158 const SCHEMA: &'static ::rustango::core::ModelSchema = &::rustango::core::ModelSchema {
1159 name: #model_name,
1160 table: #table,
1161 fields: &[ #(#field_schemas),* ],
1162 display: #display_tokens,
1163 app_label: #app_label_tokens,
1164 admin: #admin_tokens,
1165 soft_delete_column: #soft_delete_tokens,
1166 permissions: #permissions,
1167 audit_track: #audit_track_tokens,
1168 m2m: &[ #(#m2m_tokens),* ],
1169 indexes: &[ #(#indexes_tokens),* ],
1170 check_constraints: &[ #(#checks_tokens),* ],
1171 composite_relations: &[ #(#composite_fk_tokens),* ],
1172 generic_relations: &[ #(#generic_fk_tokens),* ],
1173 };
1174 }
1175 }
1176}
1177
1178fn admin_config_tokens(admin: Option<&AdminAttrs>) -> TokenStream2 {
1182 let Some(admin) = admin else {
1183 return quote!(::core::option::Option::None);
1184 };
1185
1186 let list_display = admin
1187 .list_display
1188 .as_ref()
1189 .map(|(v, _)| v.as_slice())
1190 .unwrap_or(&[]);
1191 let list_display_lits = list_display.iter().map(|s| s.as_str());
1192
1193 let search_fields = admin
1194 .search_fields
1195 .as_ref()
1196 .map(|(v, _)| v.as_slice())
1197 .unwrap_or(&[]);
1198 let search_fields_lits = search_fields.iter().map(|s| s.as_str());
1199
1200 let readonly_fields = admin
1201 .readonly_fields
1202 .as_ref()
1203 .map(|(v, _)| v.as_slice())
1204 .unwrap_or(&[]);
1205 let readonly_fields_lits = readonly_fields.iter().map(|s| s.as_str());
1206
1207 let list_filter = admin
1208 .list_filter
1209 .as_ref()
1210 .map(|(v, _)| v.as_slice())
1211 .unwrap_or(&[]);
1212 let list_filter_lits = list_filter.iter().map(|s| s.as_str());
1213
1214 let actions = admin
1215 .actions
1216 .as_ref()
1217 .map(|(v, _)| v.as_slice())
1218 .unwrap_or(&[]);
1219 let actions_lits = actions.iter().map(|s| s.as_str());
1220
1221 let fieldsets = admin
1222 .fieldsets
1223 .as_ref()
1224 .map(|(v, _)| v.as_slice())
1225 .unwrap_or(&[]);
1226 let fieldset_tokens = fieldsets.iter().map(|(title, fields)| {
1227 let title = title.as_str();
1228 let field_lits = fields.iter().map(|s| s.as_str());
1229 quote!(::rustango::core::Fieldset {
1230 title: #title,
1231 fields: &[ #( #field_lits ),* ],
1232 })
1233 });
1234
1235 let list_per_page = admin.list_per_page.unwrap_or(0);
1236
1237 let ordering_pairs = admin
1238 .ordering
1239 .as_ref()
1240 .map(|(v, _)| v.as_slice())
1241 .unwrap_or(&[]);
1242 let ordering_tokens = ordering_pairs.iter().map(|(name, desc)| {
1243 let name = name.as_str();
1244 let desc = *desc;
1245 quote!((#name, #desc))
1246 });
1247
1248 quote! {
1249 ::core::option::Option::Some(&::rustango::core::AdminConfig {
1250 list_display: &[ #( #list_display_lits ),* ],
1251 search_fields: &[ #( #search_fields_lits ),* ],
1252 list_per_page: #list_per_page,
1253 ordering: &[ #( #ordering_tokens ),* ],
1254 readonly_fields: &[ #( #readonly_fields_lits ),* ],
1255 list_filter: &[ #( #list_filter_lits ),* ],
1256 actions: &[ #( #actions_lits ),* ],
1257 fieldsets: &[ #( #fieldset_tokens ),* ],
1258 })
1259 }
1260}
1261
1262fn inherent_impl_tokens(
1263 struct_name: &syn::Ident,
1264 fields: &CollectedFields,
1265 primary_key: Option<&(syn::Ident, String)>,
1266 column_consts: &TokenStream2,
1267 audited_fields: Option<&[&ColumnEntry]>,
1268) -> TokenStream2 {
1269 let executor_passes_to_data_write = if audited_fields.is_some() {
1275 quote!(&mut *_executor)
1276 } else {
1277 quote!(_executor)
1278 };
1279 let executor_param = if audited_fields.is_some() {
1280 quote!(_executor: &mut ::rustango::sql::sqlx::PgConnection)
1281 } else {
1282 quote!(_executor: _E)
1283 };
1284 let executor_generics = if audited_fields.is_some() {
1285 quote!()
1286 } else {
1287 quote!(<'_c, _E>)
1288 };
1289 let executor_where = if audited_fields.is_some() {
1290 quote!()
1291 } else {
1292 quote! {
1293 where
1294 _E: ::rustango::sql::sqlx::Executor<'_c, Database = ::rustango::sql::sqlx::Postgres>,
1295 }
1296 };
1297 let pool_to_save_on = if audited_fields.is_some() {
1302 quote! {
1303 let mut _conn = pool.acquire().await?;
1304 self.save_on(&mut *_conn).await
1305 }
1306 } else {
1307 quote!(self.save_on(pool).await)
1308 };
1309 let pool_to_insert_on = if audited_fields.is_some() {
1310 quote! {
1311 let mut _conn = pool.acquire().await?;
1312 self.insert_on(&mut *_conn).await
1313 }
1314 } else {
1315 quote!(self.insert_on(pool).await)
1316 };
1317 let pool_to_delete_on = if audited_fields.is_some() {
1318 quote! {
1319 let mut _conn = pool.acquire().await?;
1320 self.delete_on(&mut *_conn).await
1321 }
1322 } else {
1323 quote!(self.delete_on(pool).await)
1324 };
1325 let pool_to_bulk_insert_on = if audited_fields.is_some() {
1326 quote! {
1327 let mut _conn = pool.acquire().await?;
1328 Self::bulk_insert_on(rows, &mut *_conn).await
1329 }
1330 } else {
1331 quote!(Self::bulk_insert_on(rows, pool).await)
1332 };
1333 let pool_to_upsert_on = if audited_fields.is_some() {
1340 quote! {
1341 let mut _conn = pool.acquire().await?;
1342 self.upsert_on(&mut *_conn).await
1343 }
1344 } else {
1345 quote!(self.upsert_on(pool).await)
1346 };
1347
1348 let pool_insert_method = if audited_fields.is_some() && !fields.has_auto {
1366 quote!()
1375 } else if audited_fields.is_some() && fields.has_auto {
1376 quote!()
1379 } else if fields.has_auto {
1380 let pushes = &fields.insert_pushes;
1381 let returning_cols = &fields.returning_cols;
1382 quote! {
1383 pub async fn insert_pool(
1389 &mut self,
1390 pool: &::rustango::sql::Pool,
1391 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
1392 let mut _columns: ::std::vec::Vec<&'static str> =
1393 ::std::vec::Vec::new();
1394 let mut _values: ::std::vec::Vec<::rustango::core::SqlValue> =
1395 ::std::vec::Vec::new();
1396 #( #pushes )*
1397 let _query = ::rustango::core::InsertQuery {
1398 model: <Self as ::rustango::core::Model>::SCHEMA,
1399 columns: _columns,
1400 values: _values,
1401 returning: ::std::vec![ #( #returning_cols ),* ],
1402 on_conflict: ::core::option::Option::None,
1403 };
1404 let _result = ::rustango::sql::insert_returning_pool(
1405 pool, &_query,
1406 ).await?;
1407 ::rustango::sql::apply_auto_pk_pool(_result, self)
1408 }
1409 }
1410 } else {
1411 let insert_columns = &fields.insert_columns;
1412 let insert_values = &fields.insert_values;
1413 quote! {
1414 pub async fn insert_pool(
1421 &self,
1422 pool: &::rustango::sql::Pool,
1423 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
1424 let _query = ::rustango::core::InsertQuery {
1425 model: <Self as ::rustango::core::Model>::SCHEMA,
1426 columns: ::std::vec![ #( #insert_columns ),* ],
1427 values: ::std::vec![ #( #insert_values ),* ],
1428 returning: ::std::vec::Vec::new(),
1429 on_conflict: ::core::option::Option::None,
1430 };
1431 ::rustango::sql::insert_pool(pool, &_query).await
1432 }
1433 }
1434 };
1435
1436 let audit_pair_tokens: Vec<TokenStream2> = audited_fields
1449 .map(|tracked| {
1450 tracked
1451 .iter()
1452 .map(|c| {
1453 let column_lit = c.column.as_str();
1454 let ident = &c.ident;
1455 quote! {
1456 (
1457 #column_lit,
1458 ::serde_json::to_value(&self.#ident)
1459 .unwrap_or(::serde_json::Value::Null),
1460 )
1461 }
1462 })
1463 .collect()
1464 })
1465 .unwrap_or_default();
1466 let audit_pk_to_string = if let Some((pk_ident, _)) = primary_key {
1467 if fields.pk_is_auto {
1468 quote!(self.#pk_ident.get().map(|v| ::std::format!("{}", v)).unwrap_or_default())
1469 } else {
1470 quote!(::std::format!("{}", &self.#pk_ident))
1471 }
1472 } else {
1473 quote!(::std::string::String::new())
1474 };
1475 let make_op_emit = |op_path: TokenStream2| -> TokenStream2 {
1476 if audited_fields.is_some() {
1477 let pairs = audit_pair_tokens.iter();
1478 let pk_str = audit_pk_to_string.clone();
1479 quote! {
1480 let _audit_entry = ::rustango::audit::PendingEntry {
1481 entity_table: <Self as ::rustango::core::Model>::SCHEMA.table,
1482 entity_pk: #pk_str,
1483 operation: #op_path,
1484 source: ::rustango::audit::current_source(),
1485 changes: ::rustango::audit::snapshot_changes(&[
1486 #( #pairs ),*
1487 ]),
1488 };
1489 ::rustango::audit::emit_one(&mut *_executor, &_audit_entry).await?;
1490 }
1491 } else {
1492 quote!()
1493 }
1494 };
1495 let audit_insert_emit = make_op_emit(quote!(::rustango::audit::AuditOp::Create));
1496 let audit_delete_emit = make_op_emit(quote!(::rustango::audit::AuditOp::Delete));
1497 let audit_softdelete_emit = make_op_emit(quote!(::rustango::audit::AuditOp::SoftDelete));
1498 let audit_restore_emit = make_op_emit(quote!(::rustango::audit::AuditOp::Restore));
1499
1500 let pool_save_method = if let Some((pk_ident, pk_col)) = primary_key {
1516 let pk_column_lit = pk_col.as_str();
1517 let assignments = &fields.update_assignments;
1518 if audited_fields.is_some() {
1519 if fields.pk_is_auto {
1520 quote!()
1524 } else {
1525 let pairs = audit_pair_tokens.iter();
1526 let pk_str = audit_pk_to_string.clone();
1527 quote! {
1528 pub async fn save_pool(
1542 &mut self,
1543 pool: &::rustango::sql::Pool,
1544 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
1545 let _query = ::rustango::core::UpdateQuery {
1546 model: <Self as ::rustango::core::Model>::SCHEMA,
1547 set: ::std::vec![ #( #assignments ),* ],
1548 where_clause: ::rustango::core::WhereExpr::Predicate(
1549 ::rustango::core::Filter {
1550 column: #pk_column_lit,
1551 op: ::rustango::core::Op::Eq,
1552 value: ::core::convert::Into::<::rustango::core::SqlValue>::into(
1553 ::core::clone::Clone::clone(&self.#pk_ident)
1554 ),
1555 }
1556 ),
1557 };
1558 let _audit_entry = ::rustango::audit::PendingEntry {
1559 entity_table: <Self as ::rustango::core::Model>::SCHEMA.table,
1560 entity_pk: #pk_str,
1561 operation: ::rustango::audit::AuditOp::Update,
1562 source: ::rustango::audit::current_source(),
1563 changes: ::rustango::audit::snapshot_changes(&[
1564 #( #pairs ),*
1565 ]),
1566 };
1567 let _ = ::rustango::audit::save_one_with_audit_pool(
1568 pool, &_query, &_audit_entry,
1569 ).await?;
1570 ::core::result::Result::Ok(())
1571 }
1572 }
1573 }
1574 } else {
1575 let dispatch_unset = if fields.pk_is_auto {
1576 quote! {
1577 if matches!(self.#pk_ident, ::rustango::sql::Auto::Unset) {
1578 return self.insert_pool(pool).await;
1579 }
1580 }
1581 } else {
1582 quote!()
1583 };
1584 quote! {
1585 pub async fn save_pool(
1592 &mut self,
1593 pool: &::rustango::sql::Pool,
1594 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
1595 #dispatch_unset
1596 let _query = ::rustango::core::UpdateQuery {
1597 model: <Self as ::rustango::core::Model>::SCHEMA,
1598 set: ::std::vec![ #( #assignments ),* ],
1599 where_clause: ::rustango::core::WhereExpr::Predicate(
1600 ::rustango::core::Filter {
1601 column: #pk_column_lit,
1602 op: ::rustango::core::Op::Eq,
1603 value: ::core::convert::Into::<::rustango::core::SqlValue>::into(
1604 ::core::clone::Clone::clone(&self.#pk_ident)
1605 ),
1606 }
1607 ),
1608 };
1609 let _ = ::rustango::sql::update_pool(pool, &_query).await?;
1610 ::core::result::Result::Ok(())
1611 }
1612 }
1613 }
1614 } else {
1615 quote!()
1616 };
1617
1618 let pool_insert_method = if audited_fields.is_some() {
1625 if let Some(_) = primary_key {
1626 let pushes = if fields.has_auto {
1627 fields.insert_pushes.clone()
1628 } else {
1629 fields
1634 .insert_columns
1635 .iter()
1636 .zip(&fields.insert_values)
1637 .map(|(col, val)| {
1638 quote! {
1639 _columns.push(#col);
1640 _values.push(#val);
1641 }
1642 })
1643 .collect()
1644 };
1645 let returning_cols: Vec<proc_macro2::TokenStream> = if fields.has_auto {
1646 fields.returning_cols.clone()
1647 } else {
1648 primary_key
1655 .map(|(_, col)| {
1656 let lit = col.as_str();
1657 vec![quote!(#lit)]
1658 })
1659 .unwrap_or_default()
1660 };
1661 let pairs = audit_pair_tokens.iter();
1662 let pk_str = audit_pk_to_string.clone();
1663 quote! {
1664 pub async fn insert_pool(
1673 &mut self,
1674 pool: &::rustango::sql::Pool,
1675 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
1676 let mut _columns: ::std::vec::Vec<&'static str> =
1677 ::std::vec::Vec::new();
1678 let mut _values: ::std::vec::Vec<::rustango::core::SqlValue> =
1679 ::std::vec::Vec::new();
1680 #( #pushes )*
1681 let _query = ::rustango::core::InsertQuery {
1682 model: <Self as ::rustango::core::Model>::SCHEMA,
1683 columns: _columns,
1684 values: _values,
1685 returning: ::std::vec![ #( #returning_cols ),* ],
1686 on_conflict: ::core::option::Option::None,
1687 };
1688 let _audit_entry = ::rustango::audit::PendingEntry {
1689 entity_table: <Self as ::rustango::core::Model>::SCHEMA.table,
1690 entity_pk: #pk_str,
1691 operation: ::rustango::audit::AuditOp::Create,
1692 source: ::rustango::audit::current_source(),
1693 changes: ::rustango::audit::snapshot_changes(&[
1694 #( #pairs ),*
1695 ]),
1696 };
1697 let _result = ::rustango::audit::insert_one_with_audit_pool(
1698 pool, &_query, &_audit_entry,
1699 ).await?;
1700 ::rustango::sql::apply_auto_pk_pool(_result, self)
1701 }
1702 }
1703 } else {
1704 quote!()
1705 }
1706 } else {
1707 pool_insert_method
1709 };
1710
1711 let pool_save_method = if let Some(tracked) = audited_fields {
1732 if let Some((pk_ident, pk_col)) = primary_key {
1733 let pk_column_lit = pk_col.as_str();
1734 let after_pairs_pg = audit_pair_tokens.iter().collect::<Vec<_>>();
1738 let pk_str = audit_pk_to_string.clone();
1739 let mk_before_pairs = |getter: proc_macro2::TokenStream| -> Vec<proc_macro2::TokenStream> {
1744 tracked
1745 .iter()
1746 .map(|c| {
1747 let column_lit = c.column.as_str();
1748 let value_ty = &c.value_ty;
1749 quote! {
1750 (
1751 #column_lit,
1752 match #getter::<#value_ty>(
1753 _audit_before_row, #column_lit,
1754 ) {
1755 ::core::result::Result::Ok(v) => {
1756 ::serde_json::to_value(&v)
1757 .unwrap_or(::serde_json::Value::Null)
1758 }
1759 ::core::result::Result::Err(_) => ::serde_json::Value::Null,
1760 },
1761 )
1762 }
1763 })
1764 .collect()
1765 };
1766 let before_pairs_pg: Vec<proc_macro2::TokenStream> =
1767 mk_before_pairs(quote!(::rustango::sql::try_get_returning));
1768 let before_pairs_my: Vec<proc_macro2::TokenStream> =
1769 mk_before_pairs(quote!(::rustango::sql::try_get_returning_my));
1770 let pg_select_cols: String = tracked
1771 .iter()
1772 .map(|c| format!("\"{}\"", c.column.replace('"', "\"\"")))
1773 .collect::<Vec<_>>()
1774 .join(", ");
1775 let my_select_cols: String = tracked
1776 .iter()
1777 .map(|c| format!("`{}`", c.column.replace('`', "``")))
1778 .collect::<Vec<_>>()
1779 .join(", ");
1780 let pk_value_for_bind = if fields.pk_is_auto {
1781 quote!(self.#pk_ident.get().copied().unwrap_or_default())
1782 } else {
1783 quote!(::core::clone::Clone::clone(&self.#pk_ident))
1784 };
1785 let assignments = &fields.update_assignments;
1786 let unset_dispatch = if fields.has_auto {
1787 quote! {
1788 if matches!(self.#pk_ident, ::rustango::sql::Auto::Unset) {
1789 return self.insert_pool(pool).await;
1790 }
1791 }
1792 } else {
1793 quote!()
1794 };
1795 quote! {
1796 pub async fn save_pool(
1810 &mut self,
1811 pool: &::rustango::sql::Pool,
1812 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
1813 #unset_dispatch
1814 let _query = ::rustango::core::UpdateQuery {
1815 model: <Self as ::rustango::core::Model>::SCHEMA,
1816 set: ::std::vec![ #( #assignments ),* ],
1817 where_clause: ::rustango::core::WhereExpr::Predicate(
1818 ::rustango::core::Filter {
1819 column: #pk_column_lit,
1820 op: ::rustango::core::Op::Eq,
1821 value: ::core::convert::Into::<::rustango::core::SqlValue>::into(
1822 ::core::clone::Clone::clone(&self.#pk_ident)
1823 ),
1824 }
1825 ),
1826 };
1827 let _after_pairs: ::std::vec::Vec<(&'static str, ::serde_json::Value)> =
1828 ::std::vec![ #( #after_pairs_pg ),* ];
1829 ::rustango::audit::save_one_with_diff_pool(
1830 pool,
1831 &_query,
1832 #pk_column_lit,
1833 ::core::convert::Into::<::rustango::core::SqlValue>::into(
1834 #pk_value_for_bind,
1835 ),
1836 <Self as ::rustango::core::Model>::SCHEMA.table,
1837 #pk_str,
1838 _after_pairs,
1839 #pg_select_cols,
1840 #my_select_cols,
1841 |_audit_before_row| ::std::vec![ #( #before_pairs_pg ),* ],
1842 |_audit_before_row| ::std::vec![ #( #before_pairs_my ),* ],
1843 ).await
1844 }
1845 }
1846 } else {
1847 quote!()
1848 }
1849 } else {
1850 pool_save_method
1851 };
1852
1853 let pool_delete_method = {
1860 let pk_column_lit = primary_key
1861 .map(|(_, col)| col.as_str())
1862 .unwrap_or("id");
1863 let pk_ident_for_pool = primary_key.map(|(ident, _)| ident);
1864 if let Some(pk_ident) = pk_ident_for_pool {
1865 if audited_fields.is_some() {
1866 let pairs = audit_pair_tokens.iter();
1867 let pk_str = audit_pk_to_string.clone();
1868 quote! {
1869 pub async fn delete_pool(
1876 &self,
1877 pool: &::rustango::sql::Pool,
1878 ) -> ::core::result::Result<u64, ::rustango::sql::ExecError> {
1879 let _query = ::rustango::core::DeleteQuery {
1880 model: <Self as ::rustango::core::Model>::SCHEMA,
1881 where_clause: ::rustango::core::WhereExpr::Predicate(
1882 ::rustango::core::Filter {
1883 column: #pk_column_lit,
1884 op: ::rustango::core::Op::Eq,
1885 value: ::core::convert::Into::<::rustango::core::SqlValue>::into(
1886 ::core::clone::Clone::clone(&self.#pk_ident)
1887 ),
1888 }
1889 ),
1890 };
1891 let _audit_entry = ::rustango::audit::PendingEntry {
1892 entity_table: <Self as ::rustango::core::Model>::SCHEMA.table,
1893 entity_pk: #pk_str,
1894 operation: ::rustango::audit::AuditOp::Delete,
1895 source: ::rustango::audit::current_source(),
1896 changes: ::rustango::audit::snapshot_changes(&[
1897 #( #pairs ),*
1898 ]),
1899 };
1900 ::rustango::audit::delete_one_with_audit_pool(
1901 pool, &_query, &_audit_entry,
1902 ).await
1903 }
1904 }
1905 } else {
1906 quote! {
1907 pub async fn delete_pool(
1914 &self,
1915 pool: &::rustango::sql::Pool,
1916 ) -> ::core::result::Result<u64, ::rustango::sql::ExecError> {
1917 let _query = ::rustango::core::DeleteQuery {
1918 model: <Self as ::rustango::core::Model>::SCHEMA,
1919 where_clause: ::rustango::core::WhereExpr::Predicate(
1920 ::rustango::core::Filter {
1921 column: #pk_column_lit,
1922 op: ::rustango::core::Op::Eq,
1923 value: ::core::convert::Into::<::rustango::core::SqlValue>::into(
1924 ::core::clone::Clone::clone(&self.#pk_ident)
1925 ),
1926 }
1927 ),
1928 };
1929 ::rustango::sql::delete_pool(pool, &_query).await
1930 }
1931 }
1932 }
1933 } else {
1934 quote!()
1935 }
1936 };
1937
1938 let (audit_update_pre, audit_update_post): (TokenStream2, TokenStream2) =
1948 if let Some(tracked) = audited_fields {
1949 if tracked.is_empty() {
1950 (quote!(), quote!())
1951 } else {
1952 let select_cols: String = tracked
1953 .iter()
1954 .map(|c| format!("\"{}\"", c.column.replace('"', "\"\"")))
1955 .collect::<Vec<_>>()
1956 .join(", ");
1957 let pk_column_for_select = primary_key
1958 .map(|(_, col)| col.clone())
1959 .unwrap_or_default();
1960 let select_cols_lit = select_cols;
1961 let pk_column_lit_for_select = pk_column_for_select;
1962 let pk_value_for_bind = if let Some((pk_ident, _)) = primary_key {
1963 if fields.pk_is_auto {
1964 quote!(self.#pk_ident.get().copied().unwrap_or_default())
1965 } else {
1966 quote!(::core::clone::Clone::clone(&self.#pk_ident))
1967 }
1968 } else {
1969 quote!(0_i64)
1970 };
1971 let before_pairs = tracked.iter().map(|c| {
1972 let column_lit = c.column.as_str();
1973 let value_ty = &c.value_ty;
1974 quote! {
1975 (
1976 #column_lit,
1977 match ::rustango::sql::sqlx::Row::try_get::<#value_ty, _>(
1978 &_audit_before_row, #column_lit,
1979 ) {
1980 ::core::result::Result::Ok(v) => {
1981 ::serde_json::to_value(&v)
1982 .unwrap_or(::serde_json::Value::Null)
1983 }
1984 ::core::result::Result::Err(_) => ::serde_json::Value::Null,
1985 },
1986 )
1987 }
1988 });
1989 let after_pairs = tracked.iter().map(|c| {
1990 let column_lit = c.column.as_str();
1991 let ident = &c.ident;
1992 quote! {
1993 (
1994 #column_lit,
1995 ::serde_json::to_value(&self.#ident)
1996 .unwrap_or(::serde_json::Value::Null),
1997 )
1998 }
1999 });
2000 let pk_str = audit_pk_to_string.clone();
2001 let pre = quote! {
2002 let _audit_select_sql = ::std::format!(
2003 r#"SELECT {} FROM "{}" WHERE "{}" = $1"#,
2004 #select_cols_lit,
2005 <Self as ::rustango::core::Model>::SCHEMA.table,
2006 #pk_column_lit_for_select,
2007 );
2008 let _audit_before_pairs:
2009 ::std::option::Option<::std::vec::Vec<(&'static str, ::serde_json::Value)>> =
2010 match ::rustango::sql::sqlx::query(&_audit_select_sql)
2011 .bind(#pk_value_for_bind)
2012 .fetch_optional(&mut *_executor)
2013 .await
2014 {
2015 ::core::result::Result::Ok(::core::option::Option::Some(_audit_before_row)) => {
2016 ::core::option::Option::Some(::std::vec![ #( #before_pairs ),* ])
2017 }
2018 _ => ::core::option::Option::None,
2019 };
2020 };
2021 let post = quote! {
2022 if let ::core::option::Option::Some(_audit_before) = _audit_before_pairs {
2023 let _audit_after:
2024 ::std::vec::Vec<(&'static str, ::serde_json::Value)> =
2025 ::std::vec![ #( #after_pairs ),* ];
2026 let _audit_entry = ::rustango::audit::PendingEntry {
2027 entity_table: <Self as ::rustango::core::Model>::SCHEMA.table,
2028 entity_pk: #pk_str,
2029 operation: ::rustango::audit::AuditOp::Update,
2030 source: ::rustango::audit::current_source(),
2031 changes: ::rustango::audit::diff_changes(
2032 &_audit_before,
2033 &_audit_after,
2034 ),
2035 };
2036 ::rustango::audit::emit_one(&mut *_executor, &_audit_entry).await?;
2037 }
2038 };
2039 (pre, post)
2040 }
2041 } else {
2042 (quote!(), quote!())
2043 };
2044
2045 let audit_bulk_insert_emit: TokenStream2 = if audited_fields.is_some() {
2049 let row_pk_str = if let Some((pk_ident, _)) = primary_key {
2050 if fields.pk_is_auto {
2051 quote!(_row.#pk_ident.get().map(|v| ::std::format!("{}", v)).unwrap_or_default())
2052 } else {
2053 quote!(::std::format!("{}", &_row.#pk_ident))
2054 }
2055 } else {
2056 quote!(::std::string::String::new())
2057 };
2058 let row_pairs = audited_fields
2059 .unwrap_or(&[])
2060 .iter()
2061 .map(|c| {
2062 let column_lit = c.column.as_str();
2063 let ident = &c.ident;
2064 quote! {
2065 (
2066 #column_lit,
2067 ::serde_json::to_value(&_row.#ident)
2068 .unwrap_or(::serde_json::Value::Null),
2069 )
2070 }
2071 });
2072 quote! {
2073 let _audit_source = ::rustango::audit::current_source();
2074 let mut _audit_entries:
2075 ::std::vec::Vec<::rustango::audit::PendingEntry> =
2076 ::std::vec::Vec::with_capacity(rows.len());
2077 for _row in rows.iter() {
2078 _audit_entries.push(::rustango::audit::PendingEntry {
2079 entity_table: <Self as ::rustango::core::Model>::SCHEMA.table,
2080 entity_pk: #row_pk_str,
2081 operation: ::rustango::audit::AuditOp::Create,
2082 source: _audit_source.clone(),
2083 changes: ::rustango::audit::snapshot_changes(&[
2084 #( #row_pairs ),*
2085 ]),
2086 });
2087 }
2088 ::rustango::audit::emit_many(&mut *_executor, &_audit_entries).await?;
2089 }
2090 } else {
2091 quote!()
2092 };
2093
2094 let save_method = if fields.pk_is_auto {
2095 let (pk_ident, pk_column) = primary_key
2096 .expect("pk_is_auto implies primary_key is Some");
2097 let pk_column_lit = pk_column.as_str();
2098 let assignments = &fields.update_assignments;
2099 let upsert_cols = &fields.upsert_update_columns;
2100 let upsert_pushes = &fields.insert_pushes;
2101 let upsert_returning = &fields.returning_cols;
2102 let upsert_auto_assigns = &fields.auto_assigns;
2103 let conflict_clause = if fields.upsert_update_columns.is_empty() {
2104 quote!(::rustango::core::ConflictClause::DoNothing)
2105 } else {
2106 quote!(::rustango::core::ConflictClause::DoUpdate {
2107 target: ::std::vec![#pk_column_lit],
2108 update_columns: ::std::vec![ #( #upsert_cols ),* ],
2109 })
2110 };
2111 Some(quote! {
2112 pub async fn save(
2130 &mut self,
2131 pool: &::rustango::sql::sqlx::PgPool,
2132 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
2133 #pool_to_save_on
2134 }
2135
2136 pub async fn save_on #executor_generics (
2147 &mut self,
2148 #executor_param,
2149 ) -> ::core::result::Result<(), ::rustango::sql::ExecError>
2150 #executor_where
2151 {
2152 if matches!(self.#pk_ident, ::rustango::sql::Auto::Unset) {
2153 return self.insert_on(#executor_passes_to_data_write).await;
2154 }
2155 #audit_update_pre
2156 let _query = ::rustango::core::UpdateQuery {
2157 model: <Self as ::rustango::core::Model>::SCHEMA,
2158 set: ::std::vec![ #( #assignments ),* ],
2159 where_clause: ::rustango::core::WhereExpr::Predicate(
2160 ::rustango::core::Filter {
2161 column: #pk_column_lit,
2162 op: ::rustango::core::Op::Eq,
2163 value: ::core::convert::Into::<::rustango::core::SqlValue>::into(
2164 ::core::clone::Clone::clone(&self.#pk_ident)
2165 ),
2166 }
2167 ),
2168 };
2169 let _ = ::rustango::sql::update_on(
2170 #executor_passes_to_data_write,
2171 &_query,
2172 ).await?;
2173 #audit_update_post
2174 ::core::result::Result::Ok(())
2175 }
2176
2177 pub async fn save_on_with #executor_generics (
2188 &mut self,
2189 #executor_param,
2190 source: ::rustango::audit::AuditSource,
2191 ) -> ::core::result::Result<(), ::rustango::sql::ExecError>
2192 #executor_where
2193 {
2194 ::rustango::audit::with_source(source, self.save_on(_executor)).await
2195 }
2196
2197 pub async fn upsert(
2207 &mut self,
2208 pool: &::rustango::sql::sqlx::PgPool,
2209 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
2210 #pool_to_upsert_on
2211 }
2212
2213 pub async fn upsert_on #executor_generics (
2219 &mut self,
2220 #executor_param,
2221 ) -> ::core::result::Result<(), ::rustango::sql::ExecError>
2222 #executor_where
2223 {
2224 let mut _columns: ::std::vec::Vec<&'static str> =
2225 ::std::vec::Vec::new();
2226 let mut _values: ::std::vec::Vec<::rustango::core::SqlValue> =
2227 ::std::vec::Vec::new();
2228 #( #upsert_pushes )*
2229 let query = ::rustango::core::InsertQuery {
2230 model: <Self as ::rustango::core::Model>::SCHEMA,
2231 columns: _columns,
2232 values: _values,
2233 returning: ::std::vec![ #( #upsert_returning ),* ],
2234 on_conflict: ::core::option::Option::Some(#conflict_clause),
2235 };
2236 let _returning_row_v = ::rustango::sql::insert_returning_on(
2237 #executor_passes_to_data_write,
2238 &query,
2239 ).await?;
2240 let _returning_row = &_returning_row_v;
2241 #( #upsert_auto_assigns )*
2242 ::core::result::Result::Ok(())
2243 }
2244 })
2245 } else {
2246 None
2247 };
2248
2249 let pk_methods = primary_key.map(|(pk_ident, pk_column)| {
2250 let pk_column_lit = pk_column.as_str();
2251 let soft_delete_methods = if let Some(col) = fields.soft_delete_column.as_deref() {
2258 let col_lit = col;
2259 quote! {
2260 pub async fn soft_delete_on #executor_generics (
2270 &self,
2271 #executor_param,
2272 ) -> ::core::result::Result<u64, ::rustango::sql::ExecError>
2273 #executor_where
2274 {
2275 let _query = ::rustango::core::UpdateQuery {
2276 model: <Self as ::rustango::core::Model>::SCHEMA,
2277 set: ::std::vec![
2278 ::rustango::core::Assignment {
2279 column: #col_lit,
2280 value: ::core::convert::Into::<::rustango::core::SqlValue>::into(
2281 ::chrono::Utc::now()
2282 ),
2283 },
2284 ],
2285 where_clause: ::rustango::core::WhereExpr::Predicate(
2286 ::rustango::core::Filter {
2287 column: #pk_column_lit,
2288 op: ::rustango::core::Op::Eq,
2289 value: ::core::convert::Into::<::rustango::core::SqlValue>::into(
2290 ::core::clone::Clone::clone(&self.#pk_ident)
2291 ),
2292 }
2293 ),
2294 };
2295 let _affected = ::rustango::sql::update_on(
2296 #executor_passes_to_data_write,
2297 &_query,
2298 ).await?;
2299 #audit_softdelete_emit
2300 ::core::result::Result::Ok(_affected)
2301 }
2302
2303 pub async fn restore_on #executor_generics (
2310 &self,
2311 #executor_param,
2312 ) -> ::core::result::Result<u64, ::rustango::sql::ExecError>
2313 #executor_where
2314 {
2315 let _query = ::rustango::core::UpdateQuery {
2316 model: <Self as ::rustango::core::Model>::SCHEMA,
2317 set: ::std::vec![
2318 ::rustango::core::Assignment {
2319 column: #col_lit,
2320 value: ::rustango::core::SqlValue::Null,
2321 },
2322 ],
2323 where_clause: ::rustango::core::WhereExpr::Predicate(
2324 ::rustango::core::Filter {
2325 column: #pk_column_lit,
2326 op: ::rustango::core::Op::Eq,
2327 value: ::core::convert::Into::<::rustango::core::SqlValue>::into(
2328 ::core::clone::Clone::clone(&self.#pk_ident)
2329 ),
2330 }
2331 ),
2332 };
2333 let _affected = ::rustango::sql::update_on(
2334 #executor_passes_to_data_write,
2335 &_query,
2336 ).await?;
2337 #audit_restore_emit
2338 ::core::result::Result::Ok(_affected)
2339 }
2340 }
2341 } else {
2342 quote!()
2343 };
2344 quote! {
2345 pub async fn delete(
2353 &self,
2354 pool: &::rustango::sql::sqlx::PgPool,
2355 ) -> ::core::result::Result<u64, ::rustango::sql::ExecError> {
2356 #pool_to_delete_on
2357 }
2358
2359 pub async fn delete_on #executor_generics (
2366 &self,
2367 #executor_param,
2368 ) -> ::core::result::Result<u64, ::rustango::sql::ExecError>
2369 #executor_where
2370 {
2371 let query = ::rustango::core::DeleteQuery {
2372 model: <Self as ::rustango::core::Model>::SCHEMA,
2373 where_clause: ::rustango::core::WhereExpr::Predicate(
2374 ::rustango::core::Filter {
2375 column: #pk_column_lit,
2376 op: ::rustango::core::Op::Eq,
2377 value: ::core::convert::Into::<::rustango::core::SqlValue>::into(
2378 ::core::clone::Clone::clone(&self.#pk_ident)
2379 ),
2380 }
2381 ),
2382 };
2383 let _affected = ::rustango::sql::delete_on(
2384 #executor_passes_to_data_write,
2385 &query,
2386 ).await?;
2387 #audit_delete_emit
2388 ::core::result::Result::Ok(_affected)
2389 }
2390
2391 pub async fn delete_on_with #executor_generics (
2397 &self,
2398 #executor_param,
2399 source: ::rustango::audit::AuditSource,
2400 ) -> ::core::result::Result<u64, ::rustango::sql::ExecError>
2401 #executor_where
2402 {
2403 ::rustango::audit::with_source(source, self.delete_on(_executor)).await
2404 }
2405 #pool_delete_method
2406 #pool_insert_method
2407 #pool_save_method
2408 #soft_delete_methods
2409 }
2410 });
2411
2412 let insert_method = if fields.has_auto {
2413 let pushes = &fields.insert_pushes;
2414 let returning_cols = &fields.returning_cols;
2415 let auto_assigns = &fields.auto_assigns;
2416 quote! {
2417 pub async fn insert(
2426 &mut self,
2427 pool: &::rustango::sql::sqlx::PgPool,
2428 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
2429 #pool_to_insert_on
2430 }
2431
2432 pub async fn insert_on #executor_generics (
2438 &mut self,
2439 #executor_param,
2440 ) -> ::core::result::Result<(), ::rustango::sql::ExecError>
2441 #executor_where
2442 {
2443 let mut _columns: ::std::vec::Vec<&'static str> =
2444 ::std::vec::Vec::new();
2445 let mut _values: ::std::vec::Vec<::rustango::core::SqlValue> =
2446 ::std::vec::Vec::new();
2447 #( #pushes )*
2448 let query = ::rustango::core::InsertQuery {
2449 model: <Self as ::rustango::core::Model>::SCHEMA,
2450 columns: _columns,
2451 values: _values,
2452 returning: ::std::vec![ #( #returning_cols ),* ],
2453 on_conflict: ::core::option::Option::None,
2454 };
2455 let _returning_row_v = ::rustango::sql::insert_returning_on(
2456 #executor_passes_to_data_write,
2457 &query,
2458 ).await?;
2459 let _returning_row = &_returning_row_v;
2460 #( #auto_assigns )*
2461 #audit_insert_emit
2462 ::core::result::Result::Ok(())
2463 }
2464
2465 pub async fn insert_on_with #executor_generics (
2471 &mut self,
2472 #executor_param,
2473 source: ::rustango::audit::AuditSource,
2474 ) -> ::core::result::Result<(), ::rustango::sql::ExecError>
2475 #executor_where
2476 {
2477 ::rustango::audit::with_source(source, self.insert_on(_executor)).await
2478 }
2479 }
2480 } else {
2481 let insert_columns = &fields.insert_columns;
2482 let insert_values = &fields.insert_values;
2483 quote! {
2484 pub async fn insert(
2490 &self,
2491 pool: &::rustango::sql::sqlx::PgPool,
2492 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
2493 self.insert_on(pool).await
2494 }
2495
2496 pub async fn insert_on<'_c, _E>(
2502 &self,
2503 _executor: _E,
2504 ) -> ::core::result::Result<(), ::rustango::sql::ExecError>
2505 where
2506 _E: ::rustango::sql::sqlx::Executor<'_c, Database = ::rustango::sql::sqlx::Postgres>,
2507 {
2508 let query = ::rustango::core::InsertQuery {
2509 model: <Self as ::rustango::core::Model>::SCHEMA,
2510 columns: ::std::vec![ #( #insert_columns ),* ],
2511 values: ::std::vec![ #( #insert_values ),* ],
2512 returning: ::std::vec::Vec::new(),
2513 on_conflict: ::core::option::Option::None,
2514 };
2515 ::rustango::sql::insert_on(_executor, &query).await
2516 }
2517 }
2518 };
2519
2520 let bulk_insert_method = if fields.has_auto {
2521 let cols_no_auto = &fields.bulk_columns_no_auto;
2522 let cols_all = &fields.bulk_columns_all;
2523 let pushes_no_auto = &fields.bulk_pushes_no_auto;
2524 let pushes_all = &fields.bulk_pushes_all;
2525 let returning_cols = &fields.returning_cols;
2526 let auto_assigns_for_row = bulk_auto_assigns_for_row(fields);
2527 let uniformity = &fields.bulk_auto_uniformity;
2528 let first_auto_ident = fields
2529 .first_auto_ident
2530 .as_ref()
2531 .expect("has_auto implies first_auto_ident is Some");
2532 quote! {
2533 pub async fn bulk_insert(
2547 rows: &mut [Self],
2548 pool: &::rustango::sql::sqlx::PgPool,
2549 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
2550 #pool_to_bulk_insert_on
2551 }
2552
2553 pub async fn bulk_insert_on #executor_generics (
2559 rows: &mut [Self],
2560 #executor_param,
2561 ) -> ::core::result::Result<(), ::rustango::sql::ExecError>
2562 #executor_where
2563 {
2564 if rows.is_empty() {
2565 return ::core::result::Result::Ok(());
2566 }
2567 let _first_unset = matches!(
2568 rows[0].#first_auto_ident,
2569 ::rustango::sql::Auto::Unset
2570 );
2571 #( #uniformity )*
2572
2573 let mut _all_rows: ::std::vec::Vec<
2574 ::std::vec::Vec<::rustango::core::SqlValue>,
2575 > = ::std::vec::Vec::with_capacity(rows.len());
2576 let _columns: ::std::vec::Vec<&'static str> = if _first_unset {
2577 for _row in rows.iter() {
2578 let mut _row_vals: ::std::vec::Vec<::rustango::core::SqlValue> =
2579 ::std::vec::Vec::new();
2580 #( #pushes_no_auto )*
2581 _all_rows.push(_row_vals);
2582 }
2583 ::std::vec![ #( #cols_no_auto ),* ]
2584 } else {
2585 for _row in rows.iter() {
2586 let mut _row_vals: ::std::vec::Vec<::rustango::core::SqlValue> =
2587 ::std::vec::Vec::new();
2588 #( #pushes_all )*
2589 _all_rows.push(_row_vals);
2590 }
2591 ::std::vec![ #( #cols_all ),* ]
2592 };
2593
2594 let _query = ::rustango::core::BulkInsertQuery {
2595 model: <Self as ::rustango::core::Model>::SCHEMA,
2596 columns: _columns,
2597 rows: _all_rows,
2598 returning: ::std::vec![ #( #returning_cols ),* ],
2599 on_conflict: ::core::option::Option::None,
2600 };
2601 let _returned = ::rustango::sql::bulk_insert_on(
2602 #executor_passes_to_data_write,
2603 &_query,
2604 ).await?;
2605 if _returned.len() != rows.len() {
2606 return ::core::result::Result::Err(
2607 ::rustango::sql::ExecError::Sql(
2608 ::rustango::sql::SqlError::BulkInsertReturningMismatch {
2609 expected: rows.len(),
2610 actual: _returned.len(),
2611 }
2612 )
2613 );
2614 }
2615 for (_returning_row, _row_mut) in _returned.iter().zip(rows.iter_mut()) {
2616 #auto_assigns_for_row
2617 }
2618 #audit_bulk_insert_emit
2619 ::core::result::Result::Ok(())
2620 }
2621 }
2622 } else {
2623 let cols_all = &fields.bulk_columns_all;
2624 let pushes_all = &fields.bulk_pushes_all;
2625 quote! {
2626 pub async fn bulk_insert(
2636 rows: &[Self],
2637 pool: &::rustango::sql::sqlx::PgPool,
2638 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
2639 Self::bulk_insert_on(rows, pool).await
2640 }
2641
2642 pub async fn bulk_insert_on<'_c, _E>(
2648 rows: &[Self],
2649 _executor: _E,
2650 ) -> ::core::result::Result<(), ::rustango::sql::ExecError>
2651 where
2652 _E: ::rustango::sql::sqlx::Executor<'_c, Database = ::rustango::sql::sqlx::Postgres>,
2653 {
2654 if rows.is_empty() {
2655 return ::core::result::Result::Ok(());
2656 }
2657 let mut _all_rows: ::std::vec::Vec<
2658 ::std::vec::Vec<::rustango::core::SqlValue>,
2659 > = ::std::vec::Vec::with_capacity(rows.len());
2660 for _row in rows.iter() {
2661 let mut _row_vals: ::std::vec::Vec<::rustango::core::SqlValue> =
2662 ::std::vec::Vec::new();
2663 #( #pushes_all )*
2664 _all_rows.push(_row_vals);
2665 }
2666 let _query = ::rustango::core::BulkInsertQuery {
2667 model: <Self as ::rustango::core::Model>::SCHEMA,
2668 columns: ::std::vec![ #( #cols_all ),* ],
2669 rows: _all_rows,
2670 returning: ::std::vec::Vec::new(),
2671 on_conflict: ::core::option::Option::None,
2672 };
2673 let _ = ::rustango::sql::bulk_insert_on(_executor, &_query).await?;
2674 ::core::result::Result::Ok(())
2675 }
2676 }
2677 };
2678
2679 let pk_value_helper = primary_key.map(|(pk_ident, _)| {
2680 quote! {
2681 #[doc(hidden)]
2686 pub fn __rustango_pk_value(&self) -> ::rustango::core::SqlValue {
2687 ::core::convert::Into::<::rustango::core::SqlValue>::into(
2688 ::core::clone::Clone::clone(&self.#pk_ident)
2689 )
2690 }
2691 }
2692 });
2693
2694 let has_pk_value_impl = primary_key.map(|(pk_ident, _)| {
2695 quote! {
2696 impl ::rustango::sql::HasPkValue for #struct_name {
2697 fn __rustango_pk_value_impl(&self) -> ::rustango::core::SqlValue {
2698 ::core::convert::Into::<::rustango::core::SqlValue>::into(
2699 ::core::clone::Clone::clone(&self.#pk_ident)
2700 )
2701 }
2702 }
2703 }
2704 });
2705
2706 let fk_pk_access_impl = fk_pk_access_impl_tokens(struct_name, &fields.fk_relations);
2707
2708 let assign_auto_pk_pool_impl = {
2714 let auto_assigns = &fields.auto_assigns;
2715 let mysql_body = if let Some(first) = fields.first_auto_ident.as_ref() {
2716 let value_ty = fields
2734 .first_auto_value_ty
2735 .as_ref()
2736 .expect("first_auto_value_ty set whenever first_auto_ident is");
2737 quote! {
2738 let _converted = <#value_ty as ::rustango::sql::MysqlAutoIdSet>
2739 ::rustango_from_mysql_auto_id(_id)?;
2740 self.#first = ::rustango::sql::Auto::Set(_converted);
2741 ::core::result::Result::Ok(())
2742 }
2743 } else {
2744 quote! {
2745 let _ = _id;
2746 ::core::result::Result::Ok(())
2747 }
2748 };
2749 quote! {
2750 impl ::rustango::sql::AssignAutoPkPool for #struct_name {
2751 fn __rustango_assign_from_pg_row(
2752 &mut self,
2753 _returning_row: &::rustango::sql::PgReturningRow,
2754 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
2755 #( #auto_assigns )*
2756 ::core::result::Result::Ok(())
2757 }
2758 fn __rustango_assign_from_mysql_id(
2759 &mut self,
2760 _id: i64,
2761 ) -> ::core::result::Result<(), ::rustango::sql::ExecError> {
2762 #mysql_body
2763 }
2764 }
2765 }
2766 };
2767
2768 let from_aliased_row_inits = &fields.from_aliased_row_inits;
2769 let aliased_row_helper = quote! {
2770 #[doc(hidden)]
2776 pub fn __rustango_from_aliased_row(
2777 row: &::rustango::sql::sqlx::postgres::PgRow,
2778 prefix: &str,
2779 ) -> ::core::result::Result<Self, ::rustango::sql::sqlx::Error> {
2780 ::core::result::Result::Ok(Self {
2781 #( #from_aliased_row_inits ),*
2782 })
2783 }
2784 };
2785 let aliased_row_helper_my = quote! {
2788 ::rustango::__impl_my_aliased_row_decoder!(#struct_name, |row, prefix| {
2789 #( #from_aliased_row_inits ),*
2790 });
2791 };
2792
2793 let load_related_impl =
2794 load_related_impl_tokens(struct_name, &fields.fk_relations);
2795 let load_related_impl_my =
2796 load_related_impl_my_tokens(struct_name, &fields.fk_relations);
2797
2798 quote! {
2799 impl #struct_name {
2800 #[must_use]
2802 pub fn objects() -> ::rustango::query::QuerySet<#struct_name> {
2803 ::rustango::query::QuerySet::new()
2804 }
2805
2806 #insert_method
2807
2808 #bulk_insert_method
2809
2810 #save_method
2811
2812 #pk_methods
2813
2814 #pk_value_helper
2815
2816 #aliased_row_helper
2817
2818 #column_consts
2819 }
2820
2821 #aliased_row_helper_my
2822
2823 #load_related_impl
2824
2825 #load_related_impl_my
2826
2827 #has_pk_value_impl
2828
2829 #fk_pk_access_impl
2830
2831 #assign_auto_pk_pool_impl
2832 }
2833}
2834
2835fn bulk_auto_assigns_for_row(fields: &CollectedFields) -> TokenStream2 {
2839 let lines = fields.auto_field_idents.iter().map(|(ident, column)| {
2840 let col_lit = column.as_str();
2841 quote! {
2842 _row_mut.#ident = ::rustango::sql::sqlx::Row::try_get(
2843 _returning_row,
2844 #col_lit,
2845 )?;
2846 }
2847 });
2848 quote! { #( #lines )* }
2849}
2850
2851fn column_const_tokens(module_ident: &syn::Ident, entries: &[ColumnEntry]) -> TokenStream2 {
2853 let lines = entries.iter().map(|e| {
2854 let ident = &e.ident;
2855 let col_ty = column_type_ident(ident);
2856 quote! {
2857 #[allow(non_upper_case_globals)]
2858 pub const #ident: #module_ident::#col_ty = #module_ident::#col_ty;
2859 }
2860 });
2861 quote! { #(#lines)* }
2862}
2863
2864fn column_module_tokens(
2867 module_ident: &syn::Ident,
2868 struct_name: &syn::Ident,
2869 entries: &[ColumnEntry],
2870) -> TokenStream2 {
2871 let items = entries.iter().map(|e| {
2872 let col_ty = column_type_ident(&e.ident);
2873 let value_ty = &e.value_ty;
2874 let name = &e.name;
2875 let column = &e.column;
2876 let field_type_tokens = &e.field_type_tokens;
2877 quote! {
2878 #[derive(::core::clone::Clone, ::core::marker::Copy)]
2879 pub struct #col_ty;
2880
2881 impl ::rustango::core::Column for #col_ty {
2882 type Model = super::#struct_name;
2883 type Value = #value_ty;
2884 const NAME: &'static str = #name;
2885 const COLUMN: &'static str = #column;
2886 const FIELD_TYPE: ::rustango::core::FieldType = #field_type_tokens;
2887 }
2888 }
2889 });
2890 quote! {
2891 #[doc(hidden)]
2892 #[allow(non_camel_case_types, non_snake_case)]
2893 pub mod #module_ident {
2894 #[allow(unused_imports)]
2899 use super::*;
2900 #(#items)*
2901 }
2902 }
2903}
2904
2905fn column_type_ident(field_ident: &syn::Ident) -> syn::Ident {
2906 syn::Ident::new(&format!("{field_ident}_col"), field_ident.span())
2907}
2908
2909fn column_module_ident(struct_name: &syn::Ident) -> syn::Ident {
2910 syn::Ident::new(
2911 &format!("__rustango_cols_{struct_name}"),
2912 struct_name.span(),
2913 )
2914}
2915
2916fn from_row_impl_tokens(struct_name: &syn::Ident, from_row_inits: &[TokenStream2]) -> TokenStream2 {
2917 quote! {
2928 impl<'r> ::rustango::sql::sqlx::FromRow<'r, ::rustango::sql::sqlx::postgres::PgRow>
2929 for #struct_name
2930 {
2931 fn from_row(
2932 row: &'r ::rustango::sql::sqlx::postgres::PgRow,
2933 ) -> ::core::result::Result<Self, ::rustango::sql::sqlx::Error> {
2934 ::core::result::Result::Ok(Self {
2935 #( #from_row_inits ),*
2936 })
2937 }
2938 }
2939
2940 ::rustango::__impl_my_from_row!(#struct_name, |row| {
2941 #( #from_row_inits ),*
2942 });
2943 }
2944}
2945
2946struct ContainerAttrs {
2947 table: Option<String>,
2948 display: Option<(String, proc_macro2::Span)>,
2949 app: Option<String>,
2956 admin: Option<AdminAttrs>,
2961 audit: Option<AuditAttrs>,
2967 permissions: bool,
2971 m2m: Vec<M2MAttr>,
2975 indexes: Vec<IndexAttr>,
2981 checks: Vec<CheckAttr>,
2984 composite_fks: Vec<CompositeFkAttr>,
2988 generic_fks: Vec<GenericFkAttr>,
2992}
2993
2994struct IndexAttr {
2996 name: Option<String>,
2998 columns: Vec<String>,
3000 unique: bool,
3002}
3003
3004struct CheckAttr {
3006 name: String,
3007 expr: String,
3008}
3009
3010struct CompositeFkAttr {
3016 name: String,
3018 to: String,
3020 from: Vec<String>,
3022 on: Vec<String>,
3024}
3025
3026struct GenericFkAttr {
3031 name: String,
3033 ct_column: String,
3035 pk_column: String,
3037}
3038
3039struct M2MAttr {
3041 name: String,
3043 to: String,
3045 through: String,
3047 src: String,
3049 dst: String,
3051}
3052
3053#[derive(Default)]
3059struct AuditAttrs {
3060 track: Option<(Vec<String>, proc_macro2::Span)>,
3064}
3065
3066#[derive(Default)]
3071struct AdminAttrs {
3072 list_display: Option<(Vec<String>, proc_macro2::Span)>,
3073 search_fields: Option<(Vec<String>, proc_macro2::Span)>,
3074 list_per_page: Option<usize>,
3075 ordering: Option<(Vec<(String, bool)>, proc_macro2::Span)>,
3076 readonly_fields: Option<(Vec<String>, proc_macro2::Span)>,
3077 list_filter: Option<(Vec<String>, proc_macro2::Span)>,
3078 actions: Option<(Vec<String>, proc_macro2::Span)>,
3081 fieldsets: Option<(Vec<(String, Vec<String>)>, proc_macro2::Span)>,
3085}
3086
3087fn parse_container_attrs(input: &DeriveInput) -> syn::Result<ContainerAttrs> {
3088 let mut out = ContainerAttrs {
3089 table: None,
3090 display: None,
3091 app: None,
3092 admin: None,
3093 audit: None,
3094 permissions: false,
3095 m2m: Vec::new(),
3096 indexes: Vec::new(),
3097 checks: Vec::new(),
3098 composite_fks: Vec::new(),
3099 generic_fks: Vec::new(),
3100 };
3101 for attr in &input.attrs {
3102 if !attr.path().is_ident("rustango") {
3103 continue;
3104 }
3105 attr.parse_nested_meta(|meta| {
3106 if meta.path.is_ident("table") {
3107 let s: LitStr = meta.value()?.parse()?;
3108 out.table = Some(s.value());
3109 return Ok(());
3110 }
3111 if meta.path.is_ident("display") {
3112 let s: LitStr = meta.value()?.parse()?;
3113 out.display = Some((s.value(), s.span()));
3114 return Ok(());
3115 }
3116 if meta.path.is_ident("app") {
3117 let s: LitStr = meta.value()?.parse()?;
3118 out.app = Some(s.value());
3119 return Ok(());
3120 }
3121 if meta.path.is_ident("admin") {
3122 let mut admin = AdminAttrs::default();
3123 meta.parse_nested_meta(|inner| {
3124 if inner.path.is_ident("list_display") {
3125 let s: LitStr = inner.value()?.parse()?;
3126 admin.list_display =
3127 Some((split_field_list(&s.value()), s.span()));
3128 return Ok(());
3129 }
3130 if inner.path.is_ident("search_fields") {
3131 let s: LitStr = inner.value()?.parse()?;
3132 admin.search_fields =
3133 Some((split_field_list(&s.value()), s.span()));
3134 return Ok(());
3135 }
3136 if inner.path.is_ident("readonly_fields") {
3137 let s: LitStr = inner.value()?.parse()?;
3138 admin.readonly_fields =
3139 Some((split_field_list(&s.value()), s.span()));
3140 return Ok(());
3141 }
3142 if inner.path.is_ident("list_per_page") {
3143 let lit: syn::LitInt = inner.value()?.parse()?;
3144 admin.list_per_page = Some(lit.base10_parse::<usize>()?);
3145 return Ok(());
3146 }
3147 if inner.path.is_ident("ordering") {
3148 let s: LitStr = inner.value()?.parse()?;
3149 admin.ordering = Some((
3150 parse_ordering_list(&s.value()),
3151 s.span(),
3152 ));
3153 return Ok(());
3154 }
3155 if inner.path.is_ident("list_filter") {
3156 let s: LitStr = inner.value()?.parse()?;
3157 admin.list_filter =
3158 Some((split_field_list(&s.value()), s.span()));
3159 return Ok(());
3160 }
3161 if inner.path.is_ident("actions") {
3162 let s: LitStr = inner.value()?.parse()?;
3163 admin.actions =
3164 Some((split_field_list(&s.value()), s.span()));
3165 return Ok(());
3166 }
3167 if inner.path.is_ident("fieldsets") {
3168 let s: LitStr = inner.value()?.parse()?;
3169 admin.fieldsets =
3170 Some((parse_fieldset_list(&s.value()), s.span()));
3171 return Ok(());
3172 }
3173 Err(inner.error(
3174 "unknown admin attribute (supported: \
3175 `list_display`, `search_fields`, `readonly_fields`, \
3176 `list_filter`, `list_per_page`, `ordering`, `actions`, \
3177 `fieldsets`)",
3178 ))
3179 })?;
3180 out.admin = Some(admin);
3181 return Ok(());
3182 }
3183 if meta.path.is_ident("audit") {
3184 let mut audit = AuditAttrs::default();
3185 meta.parse_nested_meta(|inner| {
3186 if inner.path.is_ident("track") {
3187 let s: LitStr = inner.value()?.parse()?;
3188 audit.track =
3189 Some((split_field_list(&s.value()), s.span()));
3190 return Ok(());
3191 }
3192 Err(inner.error(
3193 "unknown audit attribute (supported: `track`)",
3194 ))
3195 })?;
3196 out.audit = Some(audit);
3197 return Ok(());
3198 }
3199 if meta.path.is_ident("permissions") {
3200 out.permissions = true;
3201 return Ok(());
3202 }
3203 if meta.path.is_ident("unique_together") {
3204 let (columns, name) = parse_together_attr(&meta, "unique_together")?;
3213 out.indexes.push(IndexAttr { name, columns, unique: true });
3214 return Ok(());
3215 }
3216 if meta.path.is_ident("index_together") {
3217 let (columns, name) = parse_together_attr(&meta, "index_together")?;
3223 out.indexes.push(IndexAttr { name, columns, unique: false });
3224 return Ok(());
3225 }
3226 if meta.path.is_ident("index") {
3227 let cols_lit: LitStr = meta.value()?.parse()?;
3235 let columns = split_field_list(&cols_lit.value());
3236 out.indexes.push(IndexAttr { name: None, columns, unique: false });
3237 return Ok(());
3238 }
3239 if meta.path.is_ident("check") {
3240 let mut name: Option<String> = None;
3242 let mut expr: Option<String> = None;
3243 meta.parse_nested_meta(|inner| {
3244 if inner.path.is_ident("name") {
3245 let s: LitStr = inner.value()?.parse()?;
3246 name = Some(s.value());
3247 return Ok(());
3248 }
3249 if inner.path.is_ident("expr") {
3250 let s: LitStr = inner.value()?.parse()?;
3251 expr = Some(s.value());
3252 return Ok(());
3253 }
3254 Err(inner.error("unknown check attribute (supported: `name`, `expr`)"))
3255 })?;
3256 let name = name.ok_or_else(|| meta.error("check requires `name = \"...\"`"))?;
3257 let expr = expr.ok_or_else(|| meta.error("check requires `expr = \"...\"`"))?;
3258 out.checks.push(CheckAttr { name, expr });
3259 return Ok(());
3260 }
3261 if meta.path.is_ident("generic_fk") {
3262 let mut gfk = GenericFkAttr {
3263 name: String::new(),
3264 ct_column: String::new(),
3265 pk_column: String::new(),
3266 };
3267 meta.parse_nested_meta(|inner| {
3268 if inner.path.is_ident("name") {
3269 let s: LitStr = inner.value()?.parse()?;
3270 gfk.name = s.value();
3271 return Ok(());
3272 }
3273 if inner.path.is_ident("ct_column") {
3274 let s: LitStr = inner.value()?.parse()?;
3275 gfk.ct_column = s.value();
3276 return Ok(());
3277 }
3278 if inner.path.is_ident("pk_column") {
3279 let s: LitStr = inner.value()?.parse()?;
3280 gfk.pk_column = s.value();
3281 return Ok(());
3282 }
3283 Err(inner.error(
3284 "unknown generic_fk attribute (supported: `name`, `ct_column`, `pk_column`)",
3285 ))
3286 })?;
3287 if gfk.name.is_empty() {
3288 return Err(meta.error("generic_fk requires `name = \"...\"`"));
3289 }
3290 if gfk.ct_column.is_empty() {
3291 return Err(meta.error("generic_fk requires `ct_column = \"...\"`"));
3292 }
3293 if gfk.pk_column.is_empty() {
3294 return Err(meta.error("generic_fk requires `pk_column = \"...\"`"));
3295 }
3296 out.generic_fks.push(gfk);
3297 return Ok(());
3298 }
3299 if meta.path.is_ident("fk_composite") {
3300 let mut fk = CompositeFkAttr {
3301 name: String::new(),
3302 to: String::new(),
3303 from: Vec::new(),
3304 on: Vec::new(),
3305 };
3306 meta.parse_nested_meta(|inner| {
3307 if inner.path.is_ident("name") {
3308 let s: LitStr = inner.value()?.parse()?;
3309 fk.name = s.value();
3310 return Ok(());
3311 }
3312 if inner.path.is_ident("to") {
3313 let s: LitStr = inner.value()?.parse()?;
3314 fk.to = s.value();
3315 return Ok(());
3316 }
3317 if inner.path.is_ident("on") || inner.path.is_ident("from") {
3320 let value = inner.value()?;
3321 let content;
3322 syn::parenthesized!(content in value);
3323 let lits: syn::punctuated::Punctuated<syn::LitStr, syn::Token![,]> =
3324 content.parse_terminated(
3325 |p| p.parse::<syn::LitStr>(),
3326 syn::Token![,],
3327 )?;
3328 let cols: Vec<String> = lits.iter().map(syn::LitStr::value).collect();
3329 if inner.path.is_ident("on") {
3330 fk.on = cols;
3331 } else {
3332 fk.from = cols;
3333 }
3334 return Ok(());
3335 }
3336 Err(inner.error(
3337 "unknown fk_composite attribute (supported: `name`, `to`, `on`, `from`)",
3338 ))
3339 })?;
3340 if fk.name.is_empty() {
3341 return Err(meta.error("fk_composite requires `name = \"...\"`"));
3342 }
3343 if fk.to.is_empty() {
3344 return Err(meta.error("fk_composite requires `to = \"...\"`"));
3345 }
3346 if fk.from.is_empty() || fk.on.is_empty() {
3347 return Err(meta.error(
3348 "fk_composite requires non-empty `from = (...)` and `on = (...)` tuples",
3349 ));
3350 }
3351 if fk.from.len() != fk.on.len() {
3352 return Err(meta.error(format!(
3353 "fk_composite `from` ({} cols) and `on` ({} cols) must be the same length",
3354 fk.from.len(),
3355 fk.on.len(),
3356 )));
3357 }
3358 out.composite_fks.push(fk);
3359 return Ok(());
3360 }
3361 if meta.path.is_ident("m2m") {
3362 let mut m2m = M2MAttr {
3363 name: String::new(),
3364 to: String::new(),
3365 through: String::new(),
3366 src: String::new(),
3367 dst: String::new(),
3368 };
3369 meta.parse_nested_meta(|inner| {
3370 if inner.path.is_ident("name") {
3371 let s: LitStr = inner.value()?.parse()?;
3372 m2m.name = s.value();
3373 return Ok(());
3374 }
3375 if inner.path.is_ident("to") {
3376 let s: LitStr = inner.value()?.parse()?;
3377 m2m.to = s.value();
3378 return Ok(());
3379 }
3380 if inner.path.is_ident("through") {
3381 let s: LitStr = inner.value()?.parse()?;
3382 m2m.through = s.value();
3383 return Ok(());
3384 }
3385 if inner.path.is_ident("src") {
3386 let s: LitStr = inner.value()?.parse()?;
3387 m2m.src = s.value();
3388 return Ok(());
3389 }
3390 if inner.path.is_ident("dst") {
3391 let s: LitStr = inner.value()?.parse()?;
3392 m2m.dst = s.value();
3393 return Ok(());
3394 }
3395 Err(inner.error("unknown m2m attribute (supported: `name`, `to`, `through`, `src`, `dst`)"))
3396 })?;
3397 if m2m.name.is_empty() {
3398 return Err(meta.error("m2m requires `name = \"...\"`"));
3399 }
3400 if m2m.to.is_empty() {
3401 return Err(meta.error("m2m requires `to = \"...\"`"));
3402 }
3403 if m2m.through.is_empty() {
3404 return Err(meta.error("m2m requires `through = \"...\"`"));
3405 }
3406 if m2m.src.is_empty() {
3407 return Err(meta.error("m2m requires `src = \"...\"`"));
3408 }
3409 if m2m.dst.is_empty() {
3410 return Err(meta.error("m2m requires `dst = \"...\"`"));
3411 }
3412 out.m2m.push(m2m);
3413 return Ok(());
3414 }
3415 Err(meta.error("unknown rustango container attribute"))
3416 })?;
3417 }
3418 Ok(out)
3419}
3420
3421fn split_field_list(raw: &str) -> Vec<String> {
3425 raw.split(',')
3426 .map(str::trim)
3427 .filter(|s| !s.is_empty())
3428 .map(str::to_owned)
3429 .collect()
3430}
3431
3432fn parse_together_attr(
3440 meta: &syn::meta::ParseNestedMeta<'_>,
3441 attr: &str,
3442) -> syn::Result<(Vec<String>, Option<String>)> {
3443 if meta.input.peek(syn::Token![=]) {
3446 let cols_lit: LitStr = meta.value()?.parse()?;
3447 let columns = split_field_list(&cols_lit.value());
3448 check_together_columns(meta, attr, &columns)?;
3449 return Ok((columns, None));
3450 }
3451 let mut columns: Option<Vec<String>> = None;
3452 let mut name: Option<String> = None;
3453 meta.parse_nested_meta(|inner| {
3454 if inner.path.is_ident("columns") {
3455 let s: LitStr = inner.value()?.parse()?;
3456 columns = Some(split_field_list(&s.value()));
3457 return Ok(());
3458 }
3459 if inner.path.is_ident("name") {
3460 let s: LitStr = inner.value()?.parse()?;
3461 name = Some(s.value());
3462 return Ok(());
3463 }
3464 Err(inner.error("unknown sub-attribute (supported: `columns`, `name`)"))
3465 })?;
3466 let columns = columns.ok_or_else(|| meta.error(format!(
3467 "{attr}(...) requires a `columns = \"col1, col2\"` argument",
3468 )))?;
3469 check_together_columns(meta, attr, &columns)?;
3470 Ok((columns, name))
3471}
3472
3473fn check_together_columns(
3474 meta: &syn::meta::ParseNestedMeta<'_>,
3475 attr: &str,
3476 columns: &[String],
3477) -> syn::Result<()> {
3478 if columns.len() < 2 {
3479 let single = if attr == "unique_together" {
3480 "#[rustango(unique)] on the field"
3481 } else {
3482 "#[rustango(index)] on the field"
3483 };
3484 return Err(meta.error(format!(
3485 "{attr} expects two or more columns; for a single-column equivalent use {single}",
3486 )));
3487 }
3488 Ok(())
3489}
3490
3491fn parse_fieldset_list(raw: &str) -> Vec<(String, Vec<String>)> {
3500 raw.split('|')
3501 .map(str::trim)
3502 .filter(|s| !s.is_empty())
3503 .map(|section| {
3504 let (title, rest) = match section.split_once(':') {
3506 Some((title, rest)) if !title.contains(',') => {
3507 (title.trim().to_owned(), rest)
3508 }
3509 _ => (String::new(), section),
3510 };
3511 let fields = split_field_list(rest);
3512 (title, fields)
3513 })
3514 .collect()
3515}
3516
3517fn parse_ordering_list(raw: &str) -> Vec<(String, bool)> {
3520 raw.split(',')
3521 .map(str::trim)
3522 .filter(|s| !s.is_empty())
3523 .map(|spec| {
3524 spec.strip_prefix('-')
3525 .map_or((spec.to_owned(), false), |rest| (rest.trim().to_owned(), true))
3526 })
3527 .collect()
3528}
3529
3530struct FieldAttrs {
3531 column: Option<String>,
3532 primary_key: bool,
3533 fk: Option<String>,
3534 o2o: Option<String>,
3535 on: Option<String>,
3536 max_length: Option<u32>,
3537 min: Option<i64>,
3538 max: Option<i64>,
3539 default: Option<String>,
3540 auto_uuid: bool,
3546 auto_now_add: bool,
3551 auto_now: bool,
3557 soft_delete: bool,
3562 unique: bool,
3565 index: bool,
3569 index_unique: bool,
3570 index_name: Option<String>,
3571}
3572
3573fn parse_field_attrs(field: &syn::Field) -> syn::Result<FieldAttrs> {
3574 let mut out = FieldAttrs {
3575 column: None,
3576 primary_key: false,
3577 fk: None,
3578 o2o: None,
3579 on: None,
3580 max_length: None,
3581 min: None,
3582 max: None,
3583 default: None,
3584 auto_uuid: false,
3585 auto_now_add: false,
3586 auto_now: false,
3587 soft_delete: false,
3588 unique: false,
3589 index: false,
3590 index_unique: false,
3591 index_name: None,
3592 };
3593 for attr in &field.attrs {
3594 if !attr.path().is_ident("rustango") {
3595 continue;
3596 }
3597 attr.parse_nested_meta(|meta| {
3598 if meta.path.is_ident("column") {
3599 let s: LitStr = meta.value()?.parse()?;
3600 out.column = Some(s.value());
3601 return Ok(());
3602 }
3603 if meta.path.is_ident("primary_key") {
3604 out.primary_key = true;
3605 return Ok(());
3606 }
3607 if meta.path.is_ident("fk") {
3608 let s: LitStr = meta.value()?.parse()?;
3609 out.fk = Some(s.value());
3610 return Ok(());
3611 }
3612 if meta.path.is_ident("o2o") {
3613 let s: LitStr = meta.value()?.parse()?;
3614 out.o2o = Some(s.value());
3615 return Ok(());
3616 }
3617 if meta.path.is_ident("on") {
3618 let s: LitStr = meta.value()?.parse()?;
3619 out.on = Some(s.value());
3620 return Ok(());
3621 }
3622 if meta.path.is_ident("max_length") {
3623 let lit: syn::LitInt = meta.value()?.parse()?;
3624 out.max_length = Some(lit.base10_parse::<u32>()?);
3625 return Ok(());
3626 }
3627 if meta.path.is_ident("min") {
3628 out.min = Some(parse_signed_i64(&meta)?);
3629 return Ok(());
3630 }
3631 if meta.path.is_ident("max") {
3632 out.max = Some(parse_signed_i64(&meta)?);
3633 return Ok(());
3634 }
3635 if meta.path.is_ident("default") {
3636 let s: LitStr = meta.value()?.parse()?;
3637 out.default = Some(s.value());
3638 return Ok(());
3639 }
3640 if meta.path.is_ident("auto_uuid") {
3641 out.auto_uuid = true;
3642 out.primary_key = true;
3646 if out.default.is_none() {
3647 out.default = Some("gen_random_uuid()".into());
3648 }
3649 return Ok(());
3650 }
3651 if meta.path.is_ident("auto_now_add") {
3652 out.auto_now_add = true;
3653 if out.default.is_none() {
3654 out.default = Some("now()".into());
3655 }
3656 return Ok(());
3657 }
3658 if meta.path.is_ident("auto_now") {
3659 out.auto_now = true;
3660 if out.default.is_none() {
3661 out.default = Some("now()".into());
3662 }
3663 return Ok(());
3664 }
3665 if meta.path.is_ident("soft_delete") {
3666 out.soft_delete = true;
3667 return Ok(());
3668 }
3669 if meta.path.is_ident("unique") {
3670 out.unique = true;
3671 return Ok(());
3672 }
3673 if meta.path.is_ident("index") {
3674 out.index = true;
3675 if meta.input.peek(syn::token::Paren) {
3677 meta.parse_nested_meta(|inner| {
3678 if inner.path.is_ident("unique") {
3679 out.index_unique = true;
3680 return Ok(());
3681 }
3682 if inner.path.is_ident("name") {
3683 let s: LitStr = inner.value()?.parse()?;
3684 out.index_name = Some(s.value());
3685 return Ok(());
3686 }
3687 Err(inner.error("unknown index sub-attribute (supported: `unique`, `name`)"))
3688 })?;
3689 }
3690 return Ok(());
3691 }
3692 Err(meta.error("unknown rustango field attribute"))
3693 })?;
3694 }
3695 Ok(out)
3696}
3697
3698fn parse_signed_i64(meta: &syn::meta::ParseNestedMeta<'_>) -> syn::Result<i64> {
3700 let expr: syn::Expr = meta.value()?.parse()?;
3701 match expr {
3702 syn::Expr::Lit(syn::ExprLit {
3703 lit: syn::Lit::Int(lit),
3704 ..
3705 }) => lit.base10_parse::<i64>(),
3706 syn::Expr::Unary(syn::ExprUnary {
3707 op: syn::UnOp::Neg(_),
3708 expr,
3709 ..
3710 }) => {
3711 if let syn::Expr::Lit(syn::ExprLit {
3712 lit: syn::Lit::Int(lit),
3713 ..
3714 }) = *expr
3715 {
3716 let v: i64 = lit.base10_parse()?;
3717 Ok(-v)
3718 } else {
3719 Err(syn::Error::new_spanned(expr, "expected integer literal"))
3720 }
3721 }
3722 other => Err(syn::Error::new_spanned(
3723 other,
3724 "expected integer literal (signed)",
3725 )),
3726 }
3727}
3728
3729struct FieldInfo<'a> {
3730 ident: &'a syn::Ident,
3731 column: String,
3732 primary_key: bool,
3733 auto: bool,
3737 value_ty: &'a Type,
3740 field_type_tokens: TokenStream2,
3742 schema: TokenStream2,
3743 from_row_init: TokenStream2,
3744 from_aliased_row_init: TokenStream2,
3750 fk_inner: Option<Type>,
3754 auto_now: bool,
3760 auto_now_add: bool,
3766 soft_delete: bool,
3771}
3772
3773fn process_field<'a>(field: &'a syn::Field, table: &str) -> syn::Result<FieldInfo<'a>> {
3774 let attrs = parse_field_attrs(field)?;
3775 let ident = field
3776 .ident
3777 .as_ref()
3778 .ok_or_else(|| syn::Error::new(field.span(), "tuple structs are not supported"))?;
3779 let name = ident.to_string();
3780 let column = attrs.column.clone().unwrap_or_else(|| name.clone());
3781 let primary_key = attrs.primary_key;
3782 let DetectedType {
3783 kind,
3784 nullable,
3785 auto: detected_auto,
3786 fk_inner,
3787 } = detect_type(&field.ty)?;
3788 check_bound_compatibility(field, &attrs, kind)?;
3789 let auto = detected_auto;
3790 if attrs.auto_uuid {
3796 if kind != DetectedKind::Uuid {
3797 return Err(syn::Error::new_spanned(
3798 field,
3799 "`#[rustango(auto_uuid)]` requires the field type to be \
3800 `Auto<uuid::Uuid>`",
3801 ));
3802 }
3803 if !detected_auto {
3804 return Err(syn::Error::new_spanned(
3805 field,
3806 "`#[rustango(auto_uuid)]` requires the field type to be \
3807 wrapped in `Auto<...>` so the macro skips the column on \
3808 INSERT and the DB DEFAULT (`gen_random_uuid()`) fires",
3809 ));
3810 }
3811 }
3812 if attrs.auto_now_add || attrs.auto_now {
3813 if kind != DetectedKind::DateTime {
3814 return Err(syn::Error::new_spanned(
3815 field,
3816 "`#[rustango(auto_now_add)]` / `#[rustango(auto_now)]` require \
3817 the field type to be `Auto<chrono::DateTime<chrono::Utc>>`",
3818 ));
3819 }
3820 if !detected_auto {
3821 return Err(syn::Error::new_spanned(
3822 field,
3823 "`#[rustango(auto_now_add)]` / `#[rustango(auto_now)]` require \
3824 the field type to be wrapped in `Auto<...>` so the macro skips \
3825 the column on INSERT and the DB DEFAULT (`now()`) fires",
3826 ));
3827 }
3828 }
3829 if attrs.soft_delete && !(kind == DetectedKind::DateTime && nullable) {
3830 return Err(syn::Error::new_spanned(
3831 field,
3832 "`#[rustango(soft_delete)]` requires the field type to be \
3833 `Option<chrono::DateTime<chrono::Utc>>`",
3834 ));
3835 }
3836 let is_mixin_auto = attrs.auto_uuid || attrs.auto_now_add || attrs.auto_now;
3837 if detected_auto && !primary_key && !is_mixin_auto {
3838 return Err(syn::Error::new_spanned(
3839 field,
3840 "`Auto<T>` is only valid on a `#[rustango(primary_key)]` field, \
3841 or on a field carrying one of `auto_uuid`, `auto_now_add`, or \
3842 `auto_now`",
3843 ));
3844 }
3845 if detected_auto && attrs.default.is_some() && !is_mixin_auto {
3846 return Err(syn::Error::new_spanned(
3847 field,
3848 "`#[rustango(default = \"…\")]` is redundant on an `Auto<T>` field — \
3849 SERIAL / BIGSERIAL already supplies a default sequence.",
3850 ));
3851 }
3852 if fk_inner.is_some() && primary_key {
3853 return Err(syn::Error::new_spanned(
3854 field,
3855 "`ForeignKey<T>` is not allowed on a primary-key field — \
3856 a row's PK is its own identity, not a reference to a parent.",
3857 ));
3858 }
3859 let relation = relation_tokens(field, &attrs, fk_inner, table)?;
3860 let column_lit = column.as_str();
3861 let field_type_tokens = kind.variant_tokens();
3862 let max_length = optional_u32(attrs.max_length);
3863 let min = optional_i64(attrs.min);
3864 let max = optional_i64(attrs.max);
3865 let default = optional_str(attrs.default.as_deref());
3866
3867 let unique = attrs.unique;
3868 let schema = quote! {
3869 ::rustango::core::FieldSchema {
3870 name: #name,
3871 column: #column_lit,
3872 ty: #field_type_tokens,
3873 nullable: #nullable,
3874 primary_key: #primary_key,
3875 relation: #relation,
3876 max_length: #max_length,
3877 min: #min,
3878 max: #max,
3879 default: #default,
3880 auto: #auto,
3881 unique: #unique,
3882 }
3883 };
3884
3885 let from_row_init = quote! {
3886 #ident: ::rustango::sql::sqlx::Row::try_get(row, #column_lit)?
3887 };
3888 let from_aliased_row_init = quote! {
3889 #ident: ::rustango::sql::sqlx::Row::try_get(
3890 row,
3891 ::std::format!("{}__{}", prefix, #column_lit).as_str(),
3892 )?
3893 };
3894
3895 Ok(FieldInfo {
3896 ident,
3897 column,
3898 primary_key,
3899 auto,
3900 value_ty: &field.ty,
3901 field_type_tokens,
3902 schema,
3903 from_row_init,
3904 from_aliased_row_init,
3905 fk_inner: fk_inner.cloned(),
3906 auto_now: attrs.auto_now,
3907 auto_now_add: attrs.auto_now_add,
3908 soft_delete: attrs.soft_delete,
3909 })
3910}
3911
3912fn check_bound_compatibility(
3913 field: &syn::Field,
3914 attrs: &FieldAttrs,
3915 kind: DetectedKind,
3916) -> syn::Result<()> {
3917 if attrs.max_length.is_some() && kind != DetectedKind::String {
3918 return Err(syn::Error::new_spanned(
3919 field,
3920 "`max_length` is only valid on `String` fields (or `Option<String>`)",
3921 ));
3922 }
3923 if (attrs.min.is_some() || attrs.max.is_some()) && !kind.is_integer() {
3924 return Err(syn::Error::new_spanned(
3925 field,
3926 "`min` / `max` are only valid on integer fields (`i32`, `i64`, optionally Option-wrapped)",
3927 ));
3928 }
3929 if let (Some(min), Some(max)) = (attrs.min, attrs.max) {
3930 if min > max {
3931 return Err(syn::Error::new_spanned(
3932 field,
3933 format!("`min` ({min}) is greater than `max` ({max})"),
3934 ));
3935 }
3936 }
3937 Ok(())
3938}
3939
3940fn optional_u32(value: Option<u32>) -> TokenStream2 {
3941 if let Some(v) = value {
3942 quote!(::core::option::Option::Some(#v))
3943 } else {
3944 quote!(::core::option::Option::None)
3945 }
3946}
3947
3948fn optional_i64(value: Option<i64>) -> TokenStream2 {
3949 if let Some(v) = value {
3950 quote!(::core::option::Option::Some(#v))
3951 } else {
3952 quote!(::core::option::Option::None)
3953 }
3954}
3955
3956fn optional_str(value: Option<&str>) -> TokenStream2 {
3957 if let Some(v) = value {
3958 quote!(::core::option::Option::Some(#v))
3959 } else {
3960 quote!(::core::option::Option::None)
3961 }
3962}
3963
3964fn relation_tokens(
3965 field: &syn::Field,
3966 attrs: &FieldAttrs,
3967 fk_inner: Option<&syn::Type>,
3968 table: &str,
3969) -> syn::Result<TokenStream2> {
3970 if let Some(inner) = fk_inner {
3971 if attrs.fk.is_some() || attrs.o2o.is_some() {
3972 return Err(syn::Error::new_spanned(
3973 field,
3974 "`ForeignKey<T>` already declares the FK target via the type parameter — \
3975 remove the `fk = \"…\"` / `o2o = \"…\"` attribute.",
3976 ));
3977 }
3978 let on = attrs.on.as_deref().unwrap_or("id");
3979 return Ok(quote! {
3980 ::core::option::Option::Some(::rustango::core::Relation::Fk {
3981 to: <#inner as ::rustango::core::Model>::SCHEMA.table,
3982 on: #on,
3983 })
3984 });
3985 }
3986 match (&attrs.fk, &attrs.o2o) {
3987 (Some(_), Some(_)) => Err(syn::Error::new_spanned(
3988 field,
3989 "`fk` and `o2o` are mutually exclusive",
3990 )),
3991 (Some(to), None) => {
3992 let on = attrs.on.as_deref().unwrap_or("id");
3993 let resolved = if to == "self" { table } else { to };
3999 Ok(quote! {
4000 ::core::option::Option::Some(::rustango::core::Relation::Fk { to: #resolved, on: #on })
4001 })
4002 }
4003 (None, Some(to)) => {
4004 let on = attrs.on.as_deref().unwrap_or("id");
4005 let resolved = if to == "self" { table } else { to };
4006 Ok(quote! {
4007 ::core::option::Option::Some(::rustango::core::Relation::O2O { to: #resolved, on: #on })
4008 })
4009 }
4010 (None, None) => {
4011 if attrs.on.is_some() {
4012 return Err(syn::Error::new_spanned(
4013 field,
4014 "`on` requires `fk` or `o2o`",
4015 ));
4016 }
4017 Ok(quote!(::core::option::Option::None))
4018 }
4019 }
4020}
4021
4022#[derive(Clone, Copy, PartialEq, Eq)]
4026enum DetectedKind {
4027 I32,
4028 I64,
4029 F32,
4030 F64,
4031 Bool,
4032 String,
4033 DateTime,
4034 Date,
4035 Uuid,
4036 Json,
4037}
4038
4039impl DetectedKind {
4040 fn variant_tokens(self) -> TokenStream2 {
4041 match self {
4042 Self::I32 => quote!(::rustango::core::FieldType::I32),
4043 Self::I64 => quote!(::rustango::core::FieldType::I64),
4044 Self::F32 => quote!(::rustango::core::FieldType::F32),
4045 Self::F64 => quote!(::rustango::core::FieldType::F64),
4046 Self::Bool => quote!(::rustango::core::FieldType::Bool),
4047 Self::String => quote!(::rustango::core::FieldType::String),
4048 Self::DateTime => quote!(::rustango::core::FieldType::DateTime),
4049 Self::Date => quote!(::rustango::core::FieldType::Date),
4050 Self::Uuid => quote!(::rustango::core::FieldType::Uuid),
4051 Self::Json => quote!(::rustango::core::FieldType::Json),
4052 }
4053 }
4054
4055 fn is_integer(self) -> bool {
4056 matches!(self, Self::I32 | Self::I64)
4057 }
4058}
4059
4060#[derive(Clone, Copy)]
4066struct DetectedType<'a> {
4067 kind: DetectedKind,
4068 nullable: bool,
4069 auto: bool,
4070 fk_inner: Option<&'a syn::Type>,
4071}
4072
4073fn auto_inner_type(ty: &syn::Type) -> Option<&syn::Type> {
4078 let Type::Path(TypePath { path, qself: None }) = ty else {
4079 return None;
4080 };
4081 let last = path.segments.last()?;
4082 if last.ident != "Auto" {
4083 return None;
4084 }
4085 let syn::PathArguments::AngleBracketed(args) = &last.arguments else {
4086 return None;
4087 };
4088 args.args.iter().find_map(|a| match a {
4089 syn::GenericArgument::Type(t) => Some(t),
4090 _ => None,
4091 })
4092}
4093
4094fn detect_type(ty: &syn::Type) -> syn::Result<DetectedType<'_>> {
4095 let Type::Path(TypePath { path, qself: None }) = ty else {
4096 return Err(syn::Error::new_spanned(ty, "unsupported field type"));
4097 };
4098 let last = path
4099 .segments
4100 .last()
4101 .ok_or_else(|| syn::Error::new_spanned(ty, "empty type path"))?;
4102
4103 if last.ident == "Option" {
4104 let inner = generic_inner(ty, &last.arguments, "Option")?;
4105 let inner_det = detect_type(inner)?;
4106 if inner_det.nullable {
4107 return Err(syn::Error::new_spanned(
4108 ty,
4109 "nested Option is not supported",
4110 ));
4111 }
4112 if inner_det.auto {
4113 return Err(syn::Error::new_spanned(
4114 ty,
4115 "`Option<Auto<T>>` is not supported — Auto fields are server-assigned and cannot be NULL",
4116 ));
4117 }
4118 return Ok(DetectedType {
4119 nullable: true,
4120 ..inner_det
4121 });
4122 }
4123
4124 if last.ident == "Auto" {
4125 let inner = generic_inner(ty, &last.arguments, "Auto")?;
4126 let inner_det = detect_type(inner)?;
4127 if inner_det.auto {
4128 return Err(syn::Error::new_spanned(
4129 ty,
4130 "nested Auto is not supported",
4131 ));
4132 }
4133 if inner_det.nullable {
4134 return Err(syn::Error::new_spanned(
4135 ty,
4136 "`Auto<Option<T>>` is not supported — Auto fields are server-assigned and cannot be NULL",
4137 ));
4138 }
4139 if inner_det.fk_inner.is_some() {
4140 return Err(syn::Error::new_spanned(
4141 ty,
4142 "`Auto<ForeignKey<T>>` is not supported — Auto is for server-assigned PKs, ForeignKey is for parent references",
4143 ));
4144 }
4145 if !matches!(
4146 inner_det.kind,
4147 DetectedKind::I32 | DetectedKind::I64 | DetectedKind::Uuid | DetectedKind::DateTime
4148 ) {
4149 return Err(syn::Error::new_spanned(
4150 ty,
4151 "`Auto<T>` only supports integers (`i32` → SERIAL, `i64` → BIGSERIAL), \
4152 `uuid::Uuid` (DEFAULT gen_random_uuid()), or `chrono::DateTime<chrono::Utc>` \
4153 (DEFAULT now())",
4154 ));
4155 }
4156 return Ok(DetectedType {
4157 auto: true,
4158 ..inner_det
4159 });
4160 }
4161
4162 if last.ident == "ForeignKey" {
4163 let inner = generic_inner(ty, &last.arguments, "ForeignKey")?;
4164 return Ok(DetectedType {
4169 kind: DetectedKind::I64,
4170 nullable: false,
4171 auto: false,
4172 fk_inner: Some(inner),
4173 });
4174 }
4175
4176 let kind = match last.ident.to_string().as_str() {
4177 "i32" => DetectedKind::I32,
4178 "i64" => DetectedKind::I64,
4179 "f32" => DetectedKind::F32,
4180 "f64" => DetectedKind::F64,
4181 "bool" => DetectedKind::Bool,
4182 "String" => DetectedKind::String,
4183 "DateTime" => DetectedKind::DateTime,
4184 "NaiveDate" => DetectedKind::Date,
4185 "Uuid" => DetectedKind::Uuid,
4186 "Value" => DetectedKind::Json,
4187 other => {
4188 return Err(syn::Error::new_spanned(
4189 ty,
4190 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)"),
4191 ));
4192 }
4193 };
4194 Ok(DetectedType {
4195 kind,
4196 nullable: false,
4197 auto: false,
4198 fk_inner: None,
4199 })
4200}
4201
4202fn generic_inner<'a>(
4203 ty: &'a Type,
4204 arguments: &'a PathArguments,
4205 wrapper: &str,
4206) -> syn::Result<&'a Type> {
4207 let PathArguments::AngleBracketed(args) = arguments else {
4208 return Err(syn::Error::new_spanned(
4209 ty,
4210 format!("{wrapper} requires a generic argument"),
4211 ));
4212 };
4213 args.args
4214 .iter()
4215 .find_map(|a| match a {
4216 GenericArgument::Type(t) => Some(t),
4217 _ => None,
4218 })
4219 .ok_or_else(|| {
4220 syn::Error::new_spanned(ty, format!("{wrapper}<T> requires a type argument"))
4221 })
4222}
4223
4224fn to_snake_case(s: &str) -> String {
4225 let mut out = String::with_capacity(s.len() + 4);
4226 for (i, ch) in s.chars().enumerate() {
4227 if ch.is_ascii_uppercase() {
4228 if i > 0 {
4229 out.push('_');
4230 }
4231 out.push(ch.to_ascii_lowercase());
4232 } else {
4233 out.push(ch);
4234 }
4235 }
4236 out
4237}
4238
4239#[derive(Default)]
4245struct FormFieldAttrs {
4246 min: Option<i64>,
4247 max: Option<i64>,
4248 min_length: Option<u32>,
4249 max_length: Option<u32>,
4250}
4251
4252#[derive(Clone, Copy)]
4254enum FormFieldKind {
4255 String,
4256 I32,
4257 I64,
4258 F32,
4259 F64,
4260 Bool,
4261}
4262
4263impl FormFieldKind {
4264 fn parse_method(self) -> &'static str {
4265 match self {
4266 Self::I32 => "i32",
4267 Self::I64 => "i64",
4268 Self::F32 => "f32",
4269 Self::F64 => "f64",
4270 Self::String | Self::Bool => "",
4273 }
4274 }
4275}
4276
4277fn expand_form(input: &DeriveInput) -> syn::Result<TokenStream2> {
4278 let struct_name = &input.ident;
4279
4280 let Data::Struct(data) = &input.data else {
4281 return Err(syn::Error::new_spanned(
4282 struct_name,
4283 "Form can only be derived on structs",
4284 ));
4285 };
4286 let Fields::Named(named) = &data.fields else {
4287 return Err(syn::Error::new_spanned(
4288 struct_name,
4289 "Form requires a struct with named fields",
4290 ));
4291 };
4292
4293 let mut field_blocks: Vec<TokenStream2> = Vec::with_capacity(named.named.len());
4294 let mut field_idents: Vec<&syn::Ident> = Vec::with_capacity(named.named.len());
4295
4296 for field in &named.named {
4297 let ident = field
4298 .ident
4299 .as_ref()
4300 .ok_or_else(|| syn::Error::new(field.span(), "tuple structs are not supported"))?;
4301 let attrs = parse_form_field_attrs(field)?;
4302 let (kind, nullable) = detect_form_field(&field.ty, field.span())?;
4303
4304 let name_lit = ident.to_string();
4305 let parse_block = render_form_field_parse(ident, &name_lit, kind, nullable, &attrs);
4306 field_blocks.push(parse_block);
4307 field_idents.push(ident);
4308 }
4309
4310 Ok(quote! {
4311 impl ::rustango::forms::Form for #struct_name {
4312 fn parse(
4313 data: &::std::collections::HashMap<::std::string::String, ::std::string::String>,
4314 ) -> ::core::result::Result<Self, ::rustango::forms::FormErrors> {
4315 let mut __errors = ::rustango::forms::FormErrors::default();
4316 #( #field_blocks )*
4317 if !__errors.is_empty() {
4318 return ::core::result::Result::Err(__errors);
4319 }
4320 ::core::result::Result::Ok(Self {
4321 #( #field_idents ),*
4322 })
4323 }
4324 }
4325 })
4326}
4327
4328fn parse_form_field_attrs(field: &syn::Field) -> syn::Result<FormFieldAttrs> {
4329 let mut out = FormFieldAttrs::default();
4330 for attr in &field.attrs {
4331 if !attr.path().is_ident("form") {
4332 continue;
4333 }
4334 attr.parse_nested_meta(|meta| {
4335 if meta.path.is_ident("min") {
4336 let lit: syn::LitInt = meta.value()?.parse()?;
4337 out.min = Some(lit.base10_parse::<i64>()?);
4338 return Ok(());
4339 }
4340 if meta.path.is_ident("max") {
4341 let lit: syn::LitInt = meta.value()?.parse()?;
4342 out.max = Some(lit.base10_parse::<i64>()?);
4343 return Ok(());
4344 }
4345 if meta.path.is_ident("min_length") {
4346 let lit: syn::LitInt = meta.value()?.parse()?;
4347 out.min_length = Some(lit.base10_parse::<u32>()?);
4348 return Ok(());
4349 }
4350 if meta.path.is_ident("max_length") {
4351 let lit: syn::LitInt = meta.value()?.parse()?;
4352 out.max_length = Some(lit.base10_parse::<u32>()?);
4353 return Ok(());
4354 }
4355 Err(meta.error(
4356 "unknown form attribute (supported: `min`, `max`, `min_length`, `max_length`)",
4357 ))
4358 })?;
4359 }
4360 Ok(out)
4361}
4362
4363fn detect_form_field(ty: &Type, span: proc_macro2::Span) -> syn::Result<(FormFieldKind, bool)> {
4364 let Type::Path(TypePath { path, qself: None }) = ty else {
4365 return Err(syn::Error::new(
4366 span,
4367 "Form field must be a simple typed path (e.g. `String`, `i32`, `Option<String>`)",
4368 ));
4369 };
4370 let last = path
4371 .segments
4372 .last()
4373 .ok_or_else(|| syn::Error::new(span, "empty type path"))?;
4374
4375 if last.ident == "Option" {
4376 let inner = generic_inner(ty, &last.arguments, "Option")?;
4377 let (kind, nested) = detect_form_field(inner, span)?;
4378 if nested {
4379 return Err(syn::Error::new(
4380 span,
4381 "nested Option in Form fields is not supported",
4382 ));
4383 }
4384 return Ok((kind, true));
4385 }
4386
4387 let kind = match last.ident.to_string().as_str() {
4388 "String" => FormFieldKind::String,
4389 "i32" => FormFieldKind::I32,
4390 "i64" => FormFieldKind::I64,
4391 "f32" => FormFieldKind::F32,
4392 "f64" => FormFieldKind::F64,
4393 "bool" => FormFieldKind::Bool,
4394 other => {
4395 return Err(syn::Error::new(
4396 span,
4397 format!(
4398 "Form field type `{other}` is not supported in v0.8 — use String / \
4399 i32 / i64 / f32 / f64 / bool, optionally wrapped in Option<…>"
4400 ),
4401 ));
4402 }
4403 };
4404 Ok((kind, false))
4405}
4406
4407#[allow(clippy::too_many_lines)]
4408fn render_form_field_parse(
4409 ident: &syn::Ident,
4410 name_lit: &str,
4411 kind: FormFieldKind,
4412 nullable: bool,
4413 attrs: &FormFieldAttrs,
4414) -> TokenStream2 {
4415 let lookup = quote! {
4418 let __raw: ::core::option::Option<&::std::string::String> = data.get(#name_lit);
4419 };
4420
4421 let parsed_value = match kind {
4422 FormFieldKind::Bool => quote! {
4423 let __v: bool = match __raw {
4424 ::core::option::Option::None => false,
4425 ::core::option::Option::Some(__s) => !matches!(
4426 __s.to_ascii_lowercase().as_str(),
4427 "" | "false" | "0" | "off" | "no"
4428 ),
4429 };
4430 },
4431 FormFieldKind::String => {
4432 if nullable {
4433 quote! {
4434 let __v: ::core::option::Option<::std::string::String> = match __raw {
4435 ::core::option::Option::None => ::core::option::Option::None,
4436 ::core::option::Option::Some(__s) if __s.is_empty() => {
4437 ::core::option::Option::None
4438 }
4439 ::core::option::Option::Some(__s) => {
4440 ::core::option::Option::Some(::core::clone::Clone::clone(__s))
4441 }
4442 };
4443 }
4444 } else {
4445 quote! {
4446 let __v: ::std::string::String = match __raw {
4447 ::core::option::Option::Some(__s) if !__s.is_empty() => {
4448 ::core::clone::Clone::clone(__s)
4449 }
4450 _ => {
4451 __errors.add(#name_lit, "This field is required.");
4452 ::std::string::String::new()
4453 }
4454 };
4455 }
4456 }
4457 }
4458 FormFieldKind::I32 | FormFieldKind::I64 | FormFieldKind::F32 | FormFieldKind::F64 => {
4459 let parse_ty = syn::Ident::new(kind.parse_method(), proc_macro2::Span::call_site());
4460 let ty_lit = kind.parse_method();
4461 let default_val = match kind {
4462 FormFieldKind::I32 => quote! { 0i32 },
4463 FormFieldKind::I64 => quote! { 0i64 },
4464 FormFieldKind::F32 => quote! { 0f32 },
4465 FormFieldKind::F64 => quote! { 0f64 },
4466 _ => quote! { Default::default() },
4467 };
4468 if nullable {
4469 quote! {
4470 let __v: ::core::option::Option<#parse_ty> = match __raw {
4471 ::core::option::Option::None => ::core::option::Option::None,
4472 ::core::option::Option::Some(__s) if __s.is_empty() => {
4473 ::core::option::Option::None
4474 }
4475 ::core::option::Option::Some(__s) => {
4476 match __s.parse::<#parse_ty>() {
4477 ::core::result::Result::Ok(__n) => {
4478 ::core::option::Option::Some(__n)
4479 }
4480 ::core::result::Result::Err(__e) => {
4481 __errors.add(
4482 #name_lit,
4483 ::std::format!("Enter a valid {} value: {}", #ty_lit, __e),
4484 );
4485 ::core::option::Option::None
4486 }
4487 }
4488 }
4489 };
4490 }
4491 } else {
4492 quote! {
4493 let __v: #parse_ty = match __raw {
4494 ::core::option::Option::Some(__s) if !__s.is_empty() => {
4495 match __s.parse::<#parse_ty>() {
4496 ::core::result::Result::Ok(__n) => __n,
4497 ::core::result::Result::Err(__e) => {
4498 __errors.add(
4499 #name_lit,
4500 ::std::format!("Enter a valid {} value: {}", #ty_lit, __e),
4501 );
4502 #default_val
4503 }
4504 }
4505 }
4506 _ => {
4507 __errors.add(#name_lit, "This field is required.");
4508 #default_val
4509 }
4510 };
4511 }
4512 }
4513 }
4514 };
4515
4516 let validators = render_form_validators(name_lit, kind, nullable, attrs);
4517
4518 quote! {
4519 let #ident = {
4520 #lookup
4521 #parsed_value
4522 #validators
4523 __v
4524 };
4525 }
4526}
4527
4528fn render_form_validators(
4529 name_lit: &str,
4530 kind: FormFieldKind,
4531 nullable: bool,
4532 attrs: &FormFieldAttrs,
4533) -> TokenStream2 {
4534 let mut checks: Vec<TokenStream2> = Vec::new();
4535
4536 let val_ref = if nullable {
4537 quote! { __v.as_ref() }
4538 } else {
4539 quote! { ::core::option::Option::Some(&__v) }
4540 };
4541
4542 let is_string = matches!(kind, FormFieldKind::String);
4543 let is_numeric = matches!(
4544 kind,
4545 FormFieldKind::I32 | FormFieldKind::I64 | FormFieldKind::F32 | FormFieldKind::F64
4546 );
4547
4548 if is_string {
4549 if let Some(min_len) = attrs.min_length {
4550 let min_len_usize = min_len as usize;
4551 checks.push(quote! {
4552 if let ::core::option::Option::Some(__s) = #val_ref {
4553 if __s.len() < #min_len_usize {
4554 __errors.add(
4555 #name_lit,
4556 ::std::format!("Ensure this value has at least {} characters.", #min_len_usize),
4557 );
4558 }
4559 }
4560 });
4561 }
4562 if let Some(max_len) = attrs.max_length {
4563 let max_len_usize = max_len as usize;
4564 checks.push(quote! {
4565 if let ::core::option::Option::Some(__s) = #val_ref {
4566 if __s.len() > #max_len_usize {
4567 __errors.add(
4568 #name_lit,
4569 ::std::format!("Ensure this value has at most {} characters.", #max_len_usize),
4570 );
4571 }
4572 }
4573 });
4574 }
4575 }
4576
4577 if is_numeric {
4578 if let Some(min) = attrs.min {
4579 checks.push(quote! {
4580 if let ::core::option::Option::Some(__n) = #val_ref {
4581 if (*__n as f64) < (#min as f64) {
4582 __errors.add(
4583 #name_lit,
4584 ::std::format!("Ensure this value is greater than or equal to {}.", #min),
4585 );
4586 }
4587 }
4588 });
4589 }
4590 if let Some(max) = attrs.max {
4591 checks.push(quote! {
4592 if let ::core::option::Option::Some(__n) = #val_ref {
4593 if (*__n as f64) > (#max as f64) {
4594 __errors.add(
4595 #name_lit,
4596 ::std::format!("Ensure this value is less than or equal to {}.", #max),
4597 );
4598 }
4599 }
4600 });
4601 }
4602 }
4603
4604 quote! { #( #checks )* }
4605}
4606
4607struct ViewSetAttrs {
4612 model: syn::Path,
4613 fields: Option<Vec<String>>,
4614 filter_fields: Vec<String>,
4615 search_fields: Vec<String>,
4616 ordering: Vec<(String, bool)>,
4618 page_size: Option<usize>,
4619 read_only: bool,
4620 perms: ViewSetPermsAttrs,
4621}
4622
4623#[derive(Default)]
4624struct ViewSetPermsAttrs {
4625 list: Vec<String>,
4626 retrieve: Vec<String>,
4627 create: Vec<String>,
4628 update: Vec<String>,
4629 destroy: Vec<String>,
4630}
4631
4632fn expand_viewset(input: &DeriveInput) -> syn::Result<TokenStream2> {
4633 let struct_name = &input.ident;
4634
4635 match &input.data {
4637 Data::Struct(s) => match &s.fields {
4638 Fields::Unit | Fields::Named(_) => {}
4639 Fields::Unnamed(_) => {
4640 return Err(syn::Error::new_spanned(
4641 struct_name,
4642 "ViewSet can only be derived on a unit struct or an empty named struct",
4643 ));
4644 }
4645 },
4646 _ => {
4647 return Err(syn::Error::new_spanned(
4648 struct_name,
4649 "ViewSet can only be derived on a struct",
4650 ));
4651 }
4652 }
4653
4654 let attrs = parse_viewset_attrs(input)?;
4655 let model_path = &attrs.model;
4656
4657 let fields_call = if let Some(ref fields) = attrs.fields {
4659 let lits = fields.iter().map(|f| f.as_str());
4660 quote!(.fields(&[ #(#lits),* ]))
4661 } else {
4662 quote!()
4663 };
4664
4665 let filter_fields_call = if attrs.filter_fields.is_empty() {
4666 quote!()
4667 } else {
4668 let lits = attrs.filter_fields.iter().map(|f| f.as_str());
4669 quote!(.filter_fields(&[ #(#lits),* ]))
4670 };
4671
4672 let search_fields_call = if attrs.search_fields.is_empty() {
4673 quote!()
4674 } else {
4675 let lits = attrs.search_fields.iter().map(|f| f.as_str());
4676 quote!(.search_fields(&[ #(#lits),* ]))
4677 };
4678
4679 let ordering_call = if attrs.ordering.is_empty() {
4680 quote!()
4681 } else {
4682 let pairs = attrs.ordering.iter().map(|(f, desc)| {
4683 let f = f.as_str();
4684 quote!((#f, #desc))
4685 });
4686 quote!(.ordering(&[ #(#pairs),* ]))
4687 };
4688
4689 let page_size_call = if let Some(n) = attrs.page_size {
4690 quote!(.page_size(#n))
4691 } else {
4692 quote!()
4693 };
4694
4695 let read_only_call = if attrs.read_only {
4696 quote!(.read_only())
4697 } else {
4698 quote!()
4699 };
4700
4701 let perms = &attrs.perms;
4702 let perms_call = if perms.list.is_empty()
4703 && perms.retrieve.is_empty()
4704 && perms.create.is_empty()
4705 && perms.update.is_empty()
4706 && perms.destroy.is_empty()
4707 {
4708 quote!()
4709 } else {
4710 let list_lits = perms.list.iter().map(|s| s.as_str());
4711 let retrieve_lits = perms.retrieve.iter().map(|s| s.as_str());
4712 let create_lits = perms.create.iter().map(|s| s.as_str());
4713 let update_lits = perms.update.iter().map(|s| s.as_str());
4714 let destroy_lits = perms.destroy.iter().map(|s| s.as_str());
4715 quote! {
4716 .permissions(::rustango::viewset::ViewSetPerms {
4717 list: ::std::vec![ #(#list_lits.to_owned()),* ],
4718 retrieve: ::std::vec![ #(#retrieve_lits.to_owned()),* ],
4719 create: ::std::vec![ #(#create_lits.to_owned()),* ],
4720 update: ::std::vec![ #(#update_lits.to_owned()),* ],
4721 destroy: ::std::vec![ #(#destroy_lits.to_owned()),* ],
4722 })
4723 }
4724 };
4725
4726 Ok(quote! {
4727 impl #struct_name {
4728 pub fn router(prefix: &str, pool: ::rustango::sql::sqlx::PgPool) -> ::axum::Router {
4731 ::rustango::viewset::ViewSet::for_model(#model_path::SCHEMA)
4732 #fields_call
4733 #filter_fields_call
4734 #search_fields_call
4735 #ordering_call
4736 #page_size_call
4737 #perms_call
4738 #read_only_call
4739 .router(prefix, pool)
4740 }
4741 }
4742 })
4743}
4744
4745fn parse_viewset_attrs(input: &DeriveInput) -> syn::Result<ViewSetAttrs> {
4746 let mut model: Option<syn::Path> = None;
4747 let mut fields: Option<Vec<String>> = None;
4748 let mut filter_fields: Vec<String> = Vec::new();
4749 let mut search_fields: Vec<String> = Vec::new();
4750 let mut ordering: Vec<(String, bool)> = Vec::new();
4751 let mut page_size: Option<usize> = None;
4752 let mut read_only = false;
4753 let mut perms = ViewSetPermsAttrs::default();
4754
4755 for attr in &input.attrs {
4756 if !attr.path().is_ident("viewset") {
4757 continue;
4758 }
4759 attr.parse_nested_meta(|meta| {
4760 if meta.path.is_ident("model") {
4761 let path: syn::Path = meta.value()?.parse()?;
4762 model = Some(path);
4763 return Ok(());
4764 }
4765 if meta.path.is_ident("fields") {
4766 let s: LitStr = meta.value()?.parse()?;
4767 fields = Some(split_field_list(&s.value()));
4768 return Ok(());
4769 }
4770 if meta.path.is_ident("filter_fields") {
4771 let s: LitStr = meta.value()?.parse()?;
4772 filter_fields = split_field_list(&s.value());
4773 return Ok(());
4774 }
4775 if meta.path.is_ident("search_fields") {
4776 let s: LitStr = meta.value()?.parse()?;
4777 search_fields = split_field_list(&s.value());
4778 return Ok(());
4779 }
4780 if meta.path.is_ident("ordering") {
4781 let s: LitStr = meta.value()?.parse()?;
4782 ordering = parse_ordering_list(&s.value());
4783 return Ok(());
4784 }
4785 if meta.path.is_ident("page_size") {
4786 let lit: syn::LitInt = meta.value()?.parse()?;
4787 page_size = Some(lit.base10_parse::<usize>()?);
4788 return Ok(());
4789 }
4790 if meta.path.is_ident("read_only") {
4791 read_only = true;
4792 return Ok(());
4793 }
4794 if meta.path.is_ident("permissions") {
4795 meta.parse_nested_meta(|inner| {
4796 let parse_codenames = |inner: &syn::meta::ParseNestedMeta| -> syn::Result<Vec<String>> {
4797 let s: LitStr = inner.value()?.parse()?;
4798 Ok(split_field_list(&s.value()))
4799 };
4800 if inner.path.is_ident("list") {
4801 perms.list = parse_codenames(&inner)?;
4802 } else if inner.path.is_ident("retrieve") {
4803 perms.retrieve = parse_codenames(&inner)?;
4804 } else if inner.path.is_ident("create") {
4805 perms.create = parse_codenames(&inner)?;
4806 } else if inner.path.is_ident("update") {
4807 perms.update = parse_codenames(&inner)?;
4808 } else if inner.path.is_ident("destroy") {
4809 perms.destroy = parse_codenames(&inner)?;
4810 } else {
4811 return Err(inner.error(
4812 "unknown permissions key (supported: list, retrieve, create, update, destroy)",
4813 ));
4814 }
4815 Ok(())
4816 })?;
4817 return Ok(());
4818 }
4819 Err(meta.error(
4820 "unknown viewset attribute (supported: model, fields, filter_fields, \
4821 search_fields, ordering, page_size, read_only, permissions(...))",
4822 ))
4823 })?;
4824 }
4825
4826 let model = model.ok_or_else(|| {
4827 syn::Error::new_spanned(
4828 &input.ident,
4829 "`#[viewset(model = SomeModel)]` is required",
4830 )
4831 })?;
4832
4833 Ok(ViewSetAttrs {
4834 model,
4835 fields,
4836 filter_fields,
4837 search_fields,
4838 ordering,
4839 page_size,
4840 read_only,
4841 perms,
4842 })
4843}
4844
4845struct SerializerContainerAttrs {
4848 model: syn::Path,
4849}
4850
4851#[derive(Default)]
4852struct SerializerFieldAttrs {
4853 read_only: bool,
4854 write_only: bool,
4855 source: Option<String>,
4856 skip: bool,
4857 method: Option<String>,
4861 validate: Option<String>,
4866 nested: bool,
4876 nested_strict: bool,
4881 many: Option<syn::Type>,
4890}
4891
4892fn parse_serializer_container_attrs(input: &DeriveInput) -> syn::Result<SerializerContainerAttrs> {
4893 let mut model: Option<syn::Path> = None;
4894 for attr in &input.attrs {
4895 if !attr.path().is_ident("serializer") {
4896 continue;
4897 }
4898 attr.parse_nested_meta(|meta| {
4899 if meta.path.is_ident("model") {
4900 let _eq: syn::Token![=] = meta.input.parse()?;
4901 model = Some(meta.input.parse()?);
4902 return Ok(());
4903 }
4904 Err(meta.error("unknown serializer container attribute (supported: `model`)"))
4905 })?;
4906 }
4907 let model = model.ok_or_else(|| {
4908 syn::Error::new_spanned(
4909 &input.ident,
4910 "`#[serializer(model = SomeModel)]` is required",
4911 )
4912 })?;
4913 Ok(SerializerContainerAttrs { model })
4914}
4915
4916fn parse_serializer_field_attrs(field: &syn::Field) -> syn::Result<SerializerFieldAttrs> {
4917 let mut out = SerializerFieldAttrs::default();
4918 for attr in &field.attrs {
4919 if !attr.path().is_ident("serializer") {
4920 continue;
4921 }
4922 attr.parse_nested_meta(|meta| {
4923 if meta.path.is_ident("read_only") {
4924 out.read_only = true;
4925 return Ok(());
4926 }
4927 if meta.path.is_ident("write_only") {
4928 out.write_only = true;
4929 return Ok(());
4930 }
4931 if meta.path.is_ident("skip") {
4932 out.skip = true;
4933 return Ok(());
4934 }
4935 if meta.path.is_ident("source") {
4936 let s: LitStr = meta.value()?.parse()?;
4937 out.source = Some(s.value());
4938 return Ok(());
4939 }
4940 if meta.path.is_ident("method") {
4941 let s: LitStr = meta.value()?.parse()?;
4942 out.method = Some(s.value());
4943 return Ok(());
4944 }
4945 if meta.path.is_ident("validate") {
4946 let s: LitStr = meta.value()?.parse()?;
4947 out.validate = Some(s.value());
4948 return Ok(());
4949 }
4950 if meta.path.is_ident("many") {
4951 let _eq: syn::Token![=] = meta.input.parse()?;
4952 out.many = Some(meta.input.parse()?);
4953 return Ok(());
4954 }
4955 if meta.path.is_ident("nested") {
4956 out.nested = true;
4957 if meta.input.peek(syn::token::Paren) {
4960 meta.parse_nested_meta(|inner| {
4961 if inner.path.is_ident("strict") {
4962 out.nested_strict = true;
4963 return Ok(());
4964 }
4965 Err(inner.error("unknown nested sub-attribute (supported: `strict`)"))
4966 })?;
4967 }
4968 return Ok(());
4969 }
4970 Err(meta.error(
4971 "unknown serializer field attribute (supported: \
4972 `read_only`, `write_only`, `source`, `skip`, `method`, `validate`, `nested`)",
4973 ))
4974 })?;
4975 }
4976 if out.read_only && out.write_only {
4978 return Err(syn::Error::new_spanned(
4979 field,
4980 "a field cannot be both `read_only` and `write_only`",
4981 ));
4982 }
4983 if out.method.is_some() && out.source.is_some() {
4984 return Err(syn::Error::new_spanned(
4985 field,
4986 "`method` and `source` are mutually exclusive — `method` computes \
4987 the value from a method, `source` reads it from a different model field",
4988 ));
4989 }
4990 Ok(out)
4991}
4992
4993fn expand_serializer(input: &DeriveInput) -> syn::Result<TokenStream2> {
4994 let struct_name = &input.ident;
4995 let struct_name_lit = struct_name.to_string();
4996
4997 let Data::Struct(data) = &input.data else {
4998 return Err(syn::Error::new_spanned(
4999 struct_name,
5000 "Serializer can only be derived on structs",
5001 ));
5002 };
5003 let Fields::Named(named) = &data.fields else {
5004 return Err(syn::Error::new_spanned(
5005 struct_name,
5006 "Serializer requires a struct with named fields",
5007 ));
5008 };
5009
5010 let container = parse_serializer_container_attrs(input)?;
5011 let model_path = &container.model;
5012
5013 #[allow(dead_code)]
5017 struct FieldInfo {
5018 ident: syn::Ident,
5019 ty: syn::Type,
5020 attrs: SerializerFieldAttrs,
5021 }
5022 let mut fields_info: Vec<FieldInfo> = Vec::new();
5023 for field in &named.named {
5024 let ident = field.ident.clone().expect("named field has ident");
5025 let attrs = parse_serializer_field_attrs(field)?;
5026 fields_info.push(FieldInfo {
5027 ident,
5028 ty: field.ty.clone(),
5029 attrs,
5030 });
5031 }
5032
5033 let from_model_fields = fields_info.iter().map(|fi| {
5035 let ident = &fi.ident;
5036 let ty = &fi.ty;
5037 if let Some(_inner) = &fi.attrs.many {
5038 quote! { #ident: ::std::vec::Vec::new() }
5042 } else if let Some(method) = &fi.attrs.method {
5043 let method_ident = syn::Ident::new(method, ident.span());
5047 quote! { #ident: Self::#method_ident(model) }
5048 } else if fi.attrs.nested {
5049 let src_name = fi.attrs.source.as_deref().unwrap_or(&fi.ident.to_string()).to_owned();
5065 let src_ident = syn::Ident::new(&src_name, ident.span());
5066 if fi.attrs.nested_strict {
5067 let panic_msg = format!(
5068 "nested(strict) serializer for `{ident}` requires `model.{src_name}` to be loaded — \
5069 call .get(&pool).await? or .select_related(\"{src_name}\") on the model first",
5070 );
5071 quote! {
5072 #ident: <#ty as ::rustango::serializer::ModelSerializer>::from_model(
5073 model.#src_ident.value().expect(#panic_msg),
5074 )
5075 }
5076 } else {
5077 quote! {
5078 #ident: match model.#src_ident.value() {
5079 ::core::option::Option::Some(__loaded) =>
5080 <#ty as ::rustango::serializer::ModelSerializer>::from_model(__loaded),
5081 ::core::option::Option::None =>
5082 ::core::default::Default::default(),
5083 }
5084 }
5085 }
5086 } else if fi.attrs.write_only || fi.attrs.skip {
5087 quote! { #ident: ::core::default::Default::default() }
5089 } else if let Some(src) = &fi.attrs.source {
5090 let src_ident = syn::Ident::new(src, ident.span());
5091 quote! { #ident: ::core::clone::Clone::clone(&model.#src_ident) }
5092 } else {
5093 quote! { #ident: ::core::clone::Clone::clone(&model.#ident) }
5094 }
5095 });
5096
5097 let validator_calls: Vec<_> = fields_info.iter().filter_map(|fi| {
5101 let ident = &fi.ident;
5102 let name_lit = ident.to_string();
5103 let method = fi.attrs.validate.as_ref()?;
5104 let method_ident = syn::Ident::new(method, ident.span());
5105 Some(quote! {
5106 if let ::core::result::Result::Err(__e) = Self::#method_ident(&self.#ident) {
5107 __errors.add(#name_lit.to_owned(), __e);
5108 }
5109 })
5110 }).collect();
5111 let validate_method = if validator_calls.is_empty() {
5112 quote! {}
5113 } else {
5114 quote! {
5115 impl #struct_name {
5116 pub fn validate(&self) -> ::core::result::Result<(), ::rustango::forms::FormErrors> {
5120 let mut __errors = ::rustango::forms::FormErrors::default();
5121 #( #validator_calls )*
5122 if __errors.is_empty() {
5123 ::core::result::Result::Ok(())
5124 } else {
5125 ::core::result::Result::Err(__errors)
5126 }
5127 }
5128 }
5129 }
5130 };
5131
5132 let many_setters: Vec<_> = fields_info.iter().filter_map(|fi| {
5136 let many_ty = fi.attrs.many.as_ref()?;
5137 let ident = &fi.ident;
5138 let setter = syn::Ident::new(&format!("set_{ident}"), ident.span());
5139 Some(quote! {
5140 pub fn #setter(
5145 &mut self,
5146 models: &[<#many_ty as ::rustango::serializer::ModelSerializer>::Model],
5147 ) -> &mut Self {
5148 self.#ident = models.iter()
5149 .map(<#many_ty as ::rustango::serializer::ModelSerializer>::from_model)
5150 .collect();
5151 self
5152 }
5153 })
5154 }).collect();
5155 let many_setters_impl = if many_setters.is_empty() {
5156 quote! {}
5157 } else {
5158 quote! {
5159 impl #struct_name {
5160 #( #many_setters )*
5161 }
5162 }
5163 };
5164
5165 let output_fields: Vec<_> = fields_info
5167 .iter()
5168 .filter(|fi| !fi.attrs.write_only)
5169 .collect();
5170 let output_field_count = output_fields.len();
5171 let serialize_fields = output_fields.iter().map(|fi| {
5172 let ident = &fi.ident;
5173 let name_lit = ident.to_string();
5174 quote! { __state.serialize_field(#name_lit, &self.#ident)?; }
5175 });
5176
5177 let writable_lits: Vec<_> = fields_info
5179 .iter()
5180 .filter(|fi| !fi.attrs.read_only && !fi.attrs.skip)
5181 .map(|fi| fi.ident.to_string())
5182 .collect();
5183
5184 let openapi_impl = {
5188 #[cfg(feature = "openapi")]
5189 {
5190 let property_calls = output_fields.iter().map(|fi| {
5191 let ident = &fi.ident;
5192 let name_lit = ident.to_string();
5193 let ty = &fi.ty;
5194 let nullable_call = if is_option(ty) {
5195 quote! { .nullable() }
5196 } else {
5197 quote! {}
5198 };
5199 quote! {
5200 .property(
5201 #name_lit,
5202 <#ty as ::rustango::openapi::OpenApiSchema>::openapi_schema()
5203 #nullable_call,
5204 )
5205 }
5206 });
5207 let required_lits: Vec<_> = output_fields
5208 .iter()
5209 .filter(|fi| !is_option(&fi.ty))
5210 .map(|fi| fi.ident.to_string())
5211 .collect();
5212 quote! {
5213 impl ::rustango::openapi::OpenApiSchema for #struct_name {
5214 fn openapi_schema() -> ::rustango::openapi::Schema {
5215 ::rustango::openapi::Schema::object()
5216 #( #property_calls )*
5217 .required([ #( #required_lits ),* ])
5218 }
5219 }
5220 }
5221 }
5222 #[cfg(not(feature = "openapi"))]
5223 {
5224 quote! {}
5225 }
5226 };
5227
5228 Ok(quote! {
5229 impl ::rustango::serializer::ModelSerializer for #struct_name {
5230 type Model = #model_path;
5231
5232 fn from_model(model: &Self::Model) -> Self {
5233 Self {
5234 #( #from_model_fields ),*
5235 }
5236 }
5237
5238 fn writable_fields() -> &'static [&'static str] {
5239 &[ #( #writable_lits ),* ]
5240 }
5241 }
5242
5243 impl ::serde::Serialize for #struct_name {
5244 fn serialize<S>(&self, serializer: S)
5245 -> ::core::result::Result<S::Ok, S::Error>
5246 where
5247 S: ::serde::Serializer,
5248 {
5249 use ::serde::ser::SerializeStruct;
5250 let mut __state = serializer.serialize_struct(
5251 #struct_name_lit,
5252 #output_field_count,
5253 )?;
5254 #( #serialize_fields )*
5255 __state.end()
5256 }
5257 }
5258
5259 #openapi_impl
5260
5261 #validate_method
5262
5263 #many_setters_impl
5264 })
5265}
5266
5267#[cfg_attr(not(feature = "openapi"), allow(dead_code))]
5271fn is_option(ty: &syn::Type) -> bool {
5272 if let syn::Type::Path(p) = ty {
5273 if let Some(last) = p.path.segments.last() {
5274 return last.ident == "Option";
5275 }
5276 }
5277 false
5278}