1use proc_macro::TokenStream;
58use quote::quote;
59use syn::{
60 parse::{Parse, ParseStream},
61 parse_macro_input, Data, DeriveInput, Fields, Ident, ItemFn, Lit, LitInt, LitStr, Token,
62};
63
64mod handler;
65
66fn annotate(method: &str, path: LitStr, func: ItemFn) -> TokenStream {
69 handler::expand(method, path, func).into()
70}
71
72struct RouteArgs {
75 method: Ident,
76 path: LitStr,
77}
78
79impl Parse for RouteArgs {
80 fn parse(input: ParseStream) -> syn::Result<Self> {
81 let method: Ident = input.parse()?;
82 input.parse::<Token![,]>()?;
83 let path: LitStr = input.parse()?;
84 Ok(RouteArgs { method, path })
85 }
86}
87
88#[proc_macro_attribute]
120pub fn route(args: TokenStream, input: TokenStream) -> TokenStream {
121 let RouteArgs { method, path } = parse_macro_input!(args as RouteArgs);
122 let func = parse_macro_input!(input as ItemFn);
123 annotate(&method.to_string(), path, func)
124}
125
126#[proc_macro_attribute]
145pub fn get(args: TokenStream, input: TokenStream) -> TokenStream {
146 let path = parse_macro_input!(args as LitStr);
147 let func = parse_macro_input!(input as ItemFn);
148 annotate("GET", path, func)
149}
150
151#[proc_macro_attribute]
153pub fn post(args: TokenStream, input: TokenStream) -> TokenStream {
154 let path = parse_macro_input!(args as LitStr);
155 let func = parse_macro_input!(input as ItemFn);
156 annotate("POST", path, func)
157}
158
159#[proc_macro_attribute]
161pub fn put(args: TokenStream, input: TokenStream) -> TokenStream {
162 let path = parse_macro_input!(args as LitStr);
163 let func = parse_macro_input!(input as ItemFn);
164 annotate("PUT", path, func)
165}
166
167#[proc_macro_attribute]
169pub fn patch(args: TokenStream, input: TokenStream) -> TokenStream {
170 let path = parse_macro_input!(args as LitStr);
171 let func = parse_macro_input!(input as ItemFn);
172 annotate("PATCH", path, func)
173}
174
175#[proc_macro_attribute]
177pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream {
178 let path = parse_macro_input!(args as LitStr);
179 let func = parse_macro_input!(input as ItemFn);
180 annotate("DELETE", path, func)
181}
182
183#[proc_macro_derive(FromPath)]
198pub fn derive_from_path(input: TokenStream) -> TokenStream {
199 let ast = parse_macro_input!(input as DeriveInput);
200 handler::derive_from_path(ast)
201 .unwrap_or_else(|e| e.to_compile_error())
202 .into()
203}
204
205#[proc_macro_derive(FromRequest)]
224pub fn derive_from_request(input: TokenStream) -> TokenStream {
225 let ast = parse_macro_input!(input as DeriveInput);
226 impl_from_request(ast)
227 .unwrap_or_else(|e| e.to_compile_error())
228 .into()
229}
230
231#[proc_macro_derive(Validate, attributes(validate))]
266pub fn derive_validate(input: TokenStream) -> TokenStream {
267 let ast = parse_macro_input!(input as DeriveInput);
268 impl_validate(ast)
269 .unwrap_or_else(|e| e.to_compile_error())
270 .into()
271}
272
273fn impl_validate(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
274 let span = input.ident.span();
275 let name = &input.ident;
276
277 let fields = match &input.data {
278 Data::Struct(s) => match &s.fields {
279 Fields::Named(f) => &f.named,
280 _ => {
281 return Err(syn::Error::new(
282 span,
283 "#[derive(Validate)] only supports structs with named fields",
284 ))
285 }
286 },
287 _ => {
288 return Err(syn::Error::new(
289 span,
290 "#[derive(Validate)] can only be derived on structs",
291 ))
292 }
293 };
294
295 let mut all_checks: Vec<proc_macro2::TokenStream> = Vec::new();
296
297 for field in fields {
298 let field_ident = field.ident.as_ref().unwrap();
299 let field_name = field_ident.to_string();
300 for attr in &field.attrs {
301 if attr.path().is_ident("validate") {
302 let checks = generate_field_checks(attr, field_ident, &field_name)?;
303 all_checks.extend(checks);
304 }
305 }
306 }
307
308 Ok(quote! {
309 const _: () = {
310 use ::rust_web_server as _rws;
311 impl _rws::validate::Validate for #name {
312 fn validate(&self) -> ::core::result::Result<(), _rws::validate::ValidationErrors> {
313 let mut __errors = _rws::validate::ValidationErrors::new();
314 #(#all_checks)*
315 if __errors.is_empty() {
316 ::core::result::Result::Ok(())
317 } else {
318 ::core::result::Result::Err(__errors)
319 }
320 }
321 }
322 };
323 })
324}
325
326fn generate_field_checks(
327 attr: &syn::Attribute,
328 field_ident: &syn::Ident,
329 field_name: &str,
330) -> syn::Result<Vec<proc_macro2::TokenStream>> {
331 let mut checks: Vec<proc_macro2::TokenStream> = Vec::new();
332
333 attr.parse_nested_meta(|meta| {
334 if meta.path.is_ident("email") {
335 let msg = format!("{field_name} must be a valid email address");
336 checks.push(quote! {
337 if !_rws::validate::is_email(&self.#field_ident) {
338 __errors.add(#field_name, #msg);
339 }
340 });
341 } else if meta.path.is_ident("required") {
342 let msg = format!("{field_name} must not be empty");
343 checks.push(quote! {
344 if self.#field_ident.is_empty() {
345 __errors.add(#field_name, #msg);
346 }
347 });
348 } else if meta.path.is_ident("url") {
349 let msg = format!("{field_name} must be a valid URL (http:// or https://)");
350 checks.push(quote! {
351 if !_rws::validate::is_url(&self.#field_ident) {
352 __errors.add(#field_name, #msg);
353 }
354 });
355 } else if meta.path.is_ident("length") {
356 let mut min: Option<u64> = None;
357 let mut max: Option<u64> = None;
358 meta.parse_nested_meta(|inner| {
359 if inner.path.is_ident("min") {
360 let lit: LitInt = inner.value()?.parse()?;
361 min = Some(lit.base10_parse()?);
362 } else if inner.path.is_ident("max") {
363 let lit: LitInt = inner.value()?.parse()?;
364 max = Some(lit.base10_parse()?);
365 } else {
366 return Err(inner.error("expected `min` or `max`"));
367 }
368 Ok(())
369 })?;
370
371 let mut len_checks: Vec<proc_macro2::TokenStream> = Vec::new();
372 if let Some(n) = min {
373 let msg = format!("{field_name} must be at least {n} character(s) long");
374 let n_lit = proc_macro2::Literal::usize_suffixed(n as usize);
375 len_checks.push(quote! {
376 if __len < #n_lit { __errors.add(#field_name, #msg); }
377 });
378 }
379 if let Some(n) = max {
380 let msg = format!("{field_name} must be at most {n} character(s) long");
381 let n_lit = proc_macro2::Literal::usize_suffixed(n as usize);
382 len_checks.push(quote! {
383 if __len > #n_lit { __errors.add(#field_name, #msg); }
384 });
385 }
386 checks.push(quote! {
387 {
388 let __len = self.#field_ident.chars().count();
389 #(#len_checks)*
390 }
391 });
392 } else if meta.path.is_ident("range") {
393 let mut min: Option<f64> = None;
394 let mut max: Option<f64> = None;
395 meta.parse_nested_meta(|inner| {
396 if inner.path.is_ident("min") {
397 min = Some(lit_to_f64(&inner.value()?.parse::<Lit>()?)?);
398 } else if inner.path.is_ident("max") {
399 max = Some(lit_to_f64(&inner.value()?.parse::<Lit>()?)?);
400 } else {
401 return Err(inner.error("expected `min` or `max`"));
402 }
403 Ok(())
404 })?;
405
406 let mut rng_checks: Vec<proc_macro2::TokenStream> = Vec::new();
407 if let Some(n) = min {
408 let msg = format!("{field_name} must be at least {n}");
409 let n_lit = proc_macro2::Literal::f64_suffixed(n);
410 rng_checks.push(quote! {
411 if __val < #n_lit { __errors.add(#field_name, #msg); }
412 });
413 }
414 if let Some(n) = max {
415 let msg = format!("{field_name} must be at most {n}");
416 let n_lit = proc_macro2::Literal::f64_suffixed(n);
417 rng_checks.push(quote! {
418 if __val > #n_lit { __errors.add(#field_name, #msg); }
419 });
420 }
421 checks.push(quote! {
422 {
423 let __val = self.#field_ident as f64;
424 #(#rng_checks)*
425 }
426 });
427 } else {
428 return Err(meta.error(
429 "unknown validator; expected: email, required, url, length, range",
430 ));
431 }
432 Ok(())
433 })?;
434
435 Ok(checks)
436}
437
438fn lit_to_f64(lit: &Lit) -> syn::Result<f64> {
439 match lit {
440 Lit::Float(f) => Ok(f.base10_parse()?),
441 Lit::Int(i) => Ok(i.base10_parse::<i64>()? as f64),
442 _ => Err(syn::Error::new_spanned(lit, "expected a numeric literal")),
443 }
444}
445
446fn impl_from_request(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
447 let span = input.ident.span();
448 let name = &input.ident;
449
450 let fields = match &input.data {
451 Data::Struct(s) => match &s.fields {
452 Fields::Named(f) => &f.named,
453 _ => {
454 return Err(syn::Error::new(
455 span,
456 "#[derive(FromRequest)] only supports structs with named fields",
457 ))
458 }
459 },
460 _ => {
461 return Err(syn::Error::new(
462 span,
463 "#[derive(FromRequest)] can only be derived on structs",
464 ))
465 }
466 };
467
468 let extractions = fields.iter().map(|f| {
469 let ident = f.ident.as_ref().unwrap();
470 let ty = &f.ty;
471 quote! {
472 let #ident = <#ty as _rws::extract::FromRequest>::from_request(__req)?;
473 }
474 });
475
476 let field_names = fields.iter().map(|f| f.ident.as_ref().unwrap());
477
478 Ok(quote! {
479 const _: () = {
480 use ::rust_web_server as _rws;
481 impl _rws::extract::FromRequest for #name {
482 fn from_request(__req: &_rws::request::Request) -> ::core::result::Result<Self, _rws::response::Response> {
483 #(#extractions)*
484 ::core::result::Result::Ok(#name { #(#field_names),* })
485 }
486 }
487 };
488 })
489}
490
491#[proc_macro_derive(Config, attributes(config))]
537pub fn derive_config(input: TokenStream) -> TokenStream {
538 let ast = parse_macro_input!(input as DeriveInput);
539 impl_config(ast)
540 .unwrap_or_else(|e| e.to_compile_error())
541 .into()
542}
543
544fn impl_config(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
545 let span = input.ident.span();
546 let name = &input.ident;
547
548 let prefix = parse_config_prefix(&input.attrs)?;
550
551 let fields = match &input.data {
552 Data::Struct(s) => match &s.fields {
553 Fields::Named(f) => &f.named,
554 _ => {
555 return Err(syn::Error::new(
556 span,
557 "#[derive(Config)] only supports structs with named fields",
558 ))
559 }
560 },
561 _ => {
562 return Err(syn::Error::new(
563 span,
564 "#[derive(Config)] can only be derived on structs",
565 ))
566 }
567 };
568
569 let mut field_loads: Vec<proc_macro2::TokenStream> = Vec::new();
570 let mut field_names: Vec<&syn::Ident> = Vec::new();
571
572 for field in fields {
573 let ident = field.ident.as_ref().unwrap();
574 field_names.push(ident);
575
576 let (env_key, default) = parse_field_config(&field.attrs, ident, &prefix)?;
578
579 let is_option = is_option_type(&field.ty);
580
581 let load_expr = if is_option {
582 quote! {
583 _rws::config_binding::load_optional(&#env_key)?
584 }
585 } else if let Some(default_str) = default {
586 quote! {
587 _rws::config_binding::load_with_default(&#env_key, #default_str)?
588 }
589 } else {
590 quote! {
591 _rws::config_binding::load_required(&#env_key)?
592 }
593 };
594
595 field_loads.push(quote! {
596 let #ident = #load_expr;
597 });
598 }
599
600 Ok(quote! {
601 impl #name {
602 pub fn load() -> ::core::result::Result<Self, ::std::string::String> {
604 use ::rust_web_server as _rws;
605 #(#field_loads)*
606 ::core::result::Result::Ok(#name {
607 #(#field_names),*
608 })
609 }
610 }
611 })
612}
613
614fn parse_field_config(
617 attrs: &[syn::Attribute],
618 ident: &syn::Ident,
619 prefix: &str,
620) -> syn::Result<(proc_macro2::TokenStream, Option<LitStr>)> {
621 let mut env_name: Option<String> = None;
622 let mut default: Option<LitStr> = None;
623
624 for attr in attrs {
625 if !attr.path().is_ident("config") {
626 continue;
627 }
628 attr.parse_nested_meta(|meta| {
629 if meta.path.is_ident("env") {
630 let lit: LitStr = meta.value()?.parse()?;
631 env_name = Some(lit.value());
632 } else if meta.path.is_ident("default") {
633 default = Some(meta.value()?.parse()?);
634 } else {
635 return Err(meta.error("unknown config key; expected `env` or `default`"));
636 }
637 Ok(())
638 })?;
639 }
640
641 let key = format!(
642 "{}{}",
643 prefix,
644 env_name.unwrap_or_else(|| ident.to_string().to_uppercase())
645 );
646
647 Ok((quote! { #key }, default))
648}
649
650fn parse_config_prefix(attrs: &[syn::Attribute]) -> syn::Result<String> {
652 let mut prefix = String::new();
653 for attr in attrs {
654 if !attr.path().is_ident("config") {
655 continue;
656 }
657 attr.parse_nested_meta(|meta| {
658 if meta.path.is_ident("prefix") {
659 let lit: LitStr = meta.value()?.parse()?;
660 prefix = lit.value();
661 } else {
662 return Err(meta.error("unknown struct config key; expected `prefix`"));
663 }
664 Ok(())
665 })?;
666 }
667 Ok(prefix)
668}
669
670fn is_option_type(ty: &syn::Type) -> bool {
672 if let syn::Type::Path(tp) = ty {
673 if let Some(seg) = tp.path.segments.last() {
674 return seg.ident == "Option";
675 }
676 }
677 false
678}
679
680#[proc_macro_derive(Model, attributes(table, column, primary_key, timestamps, soft_delete, version))]
739pub fn derive_model(input: TokenStream) -> TokenStream {
740 let ast = parse_macro_input!(input as DeriveInput);
741 impl_model(ast)
742 .unwrap_or_else(|e| e.to_compile_error())
743 .into()
744}
745
746fn impl_model(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
747 let span = input.ident.span();
748 let struct_name = &input.ident;
749
750 let table_name = parse_table_name(&input.attrs, struct_name)?;
752
753 let fields = match &input.data {
754 Data::Struct(s) => match &s.fields {
755 Fields::Named(f) => &f.named,
756 _ => {
757 return Err(syn::Error::new(
758 span,
759 "#[derive(Model)] only supports structs with named fields",
760 ))
761 }
762 },
763 _ => {
764 return Err(syn::Error::new(
765 span,
766 "#[derive(Model)] can only be derived on structs",
767 ))
768 }
769 };
770
771 struct FieldMeta {
773 field_ident: syn::Ident,
774 col_name: String,
775 #[allow(dead_code)]
776 is_pk: bool,
777 is_auto_increment: bool,
778 is_ignored: bool,
779 is_version: bool,
780 ty: syn::Type,
781 }
782
783 let mut pk_field: Option<FieldMeta> = None;
784 let mut regular_fields: Vec<FieldMeta> = Vec::new();
785
786 for field in fields {
787 let ident = field.ident.as_ref().unwrap().clone();
788 let mut col_name = ident.to_string();
789 let mut is_pk = false;
790 let mut is_auto_increment = false;
791 let mut is_ignored = false;
792 let mut is_version = false;
793
794 for attr in &field.attrs {
795 if attr.path().is_ident("ignore") {
796 is_ignored = true;
797 } else if attr.path().is_ident("version") {
798 is_version = true;
799 } else if attr.path().is_ident("primary_key") {
800 is_pk = true;
801 let _ = attr.parse_nested_meta(|meta| {
803 if meta.path.is_ident("auto_increment") {
804 is_auto_increment = true;
805 }
806 Ok(())
807 });
808 } else if attr.path().is_ident("column") {
809 let _ = attr.parse_nested_meta(|meta| {
810 if meta.path.is_ident("name") {
811 let lit: LitStr = meta.value()?.parse()?;
812 col_name = lit.value();
813 }
814 Ok(())
816 });
817 }
818 }
819
820 let meta = FieldMeta {
821 field_ident: ident,
822 col_name,
823 is_pk,
824 is_auto_increment,
825 is_ignored,
826 is_version,
827 ty: field.ty.clone(),
828 };
829
830 if is_version && (is_pk || is_ignored) {
831 return Err(syn::Error::new_spanned(field, "#[version] goes on a mapped integer field, not the #[primary_key] or an #[ignore]d one"));
832 }
833 if is_pk {
834 pk_field = Some(meta);
835 } else {
836 regular_fields.push(meta);
837 }
838 }
839
840 let pk = pk_field.ok_or_else(|| {
841 syn::Error::new(span, "#[derive(Model)] requires exactly one #[primary_key] field")
842 })?;
843
844 let has_struct_attr = |name: &str| input.attrs.iter().any(|a| a.path().is_ident(name));
846 let column_of = |field: &str, attr: &str, must_be_option: bool| -> syn::Result<String> {
847 let found = regular_fields.iter().find(|f| f.field_ident == field && !f.is_ignored).ok_or_else(|| {
848 syn::Error::new(span, format!("#[{attr}] needs a mapped field named `{field}` (e.g. `{field}: {}`)", if must_be_option { "Option<Timestamp>" } else { "Timestamp" }))
849 })?;
850 if must_be_option && !is_option_type(&found.ty) {
851 return Err(syn::Error::new_spanned(&found.ty, format!("#[{attr}]: `{field}` must be an Option (NULL means not deleted), e.g. Option<Timestamp>")));
852 }
853 Ok(found.col_name.clone())
854 };
855 let opt = |name: &str, c: Option<String>| -> proc_macro2::TokenStream {
859 let name = syn::Ident::new(name, proc_macro2::Span::call_site());
860 match c {
861 Some(c) => quote! {
862 fn #name() -> ::core::option::Option<&'static str> {
863 ::core::option::Option::Some(#c)
864 }
865 },
866 None => quote! {},
867 }
868 };
869 let (created_at, updated_at) = if has_struct_attr("timestamps") {
870 (Some(column_of("created_at", "timestamps", false)?), Some(column_of("updated_at", "timestamps", false)?))
871 } else {
872 (None, None)
873 };
874 let deleted_at = if has_struct_attr("soft_delete") { Some(column_of("deleted_at", "soft_delete", true)?) } else { None };
875 let versions: Vec<&FieldMeta> = regular_fields.iter().filter(|f| f.is_version).collect();
876 if versions.len() > 1 {
877 return Err(syn::Error::new(span, "#[derive(Model)] takes at most one #[version] field"));
878 }
879 let version = versions.first().map(|f| f.col_name.clone());
880 let (created_at, updated_at, deleted_at, version) = (
881 opt("created_at_column", created_at),
882 opt("updated_at_column", updated_at),
883 opt("soft_delete_column", deleted_at),
884 opt("version_column", version),
885 );
886
887 let pk_ty = &pk.ty;
890 let is_integer = matches!(pk_ty, syn::Type::Path(tp) if tp.qself.is_none() && tp.path.segments.len() == 1
891 && ["i8", "i16", "i32", "i64", "isize", "u8", "u16", "u32", "u64", "usize"].iter().any(|t| tp.path.segments[0].ident == t));
892 let repo_key = if is_integer { quote! { i64 } } else { quote! { #pk_ty } };
893
894 let mut all_col_names: Vec<String> = Vec::new();
896 all_col_names.push(pk.col_name.clone());
897 for f in regular_fields.iter().filter(|f| !f.is_ignored) {
898 all_col_names.push(f.col_name.clone());
899 }
900
901 let pk_col_name = pk.col_name.clone();
902 let pk_field_ident = &pk.field_ident;
903 let auto_inc = pk.is_auto_increment;
904
905 let pk_from_row = {
907 let col = &pk.col_name;
908 let fident = &pk.field_ident;
909 quote! { #fident: row.get(#col)? }
910 };
911
912 let regular_from_row: Vec<proc_macro2::TokenStream> = fields
913 .iter()
914 .filter(|f| {
915 let ident_str = f.ident.as_ref().unwrap().to_string();
916 let is_pk_field = ident_str == pk_field_ident.to_string();
918 !is_pk_field
919 })
920 .map(|f| {
921 let fident = f.ident.as_ref().unwrap();
922 let is_ignored = f.attrs.iter().any(|a| a.path().is_ident("ignore"));
924 if is_ignored {
925 quote! { #fident: ::core::default::Default::default() }
926 } else {
927 let mut col = fident.to_string();
929 for attr in &f.attrs {
930 if attr.path().is_ident("column") {
931 let _ = attr.parse_nested_meta(|meta| {
932 if meta.path.is_ident("name") {
933 if let Ok(lit) = meta.value()?.parse::<LitStr>() {
934 col = lit.value();
935 }
936 }
937 Ok(())
938 });
939 }
940 }
941 quote! { #fident: row.get(#col)? }
942 }
943 })
944 .collect();
945
946 let pk_to_values = {
948 let col = &pk.col_name;
949 quote! {
950 (#col, ::rust_web_server::model::ToColumn::to_column(&self.#pk_field_ident))
951 }
952 };
953
954 let regular_to_values: Vec<proc_macro2::TokenStream> = fields
955 .iter()
956 .filter(|f| {
957 let ident_str = f.ident.as_ref().unwrap().to_string();
958 let is_pk_field = ident_str == pk_field_ident.to_string();
959 let is_ignored = f.attrs.iter().any(|a| a.path().is_ident("ignore"));
960 !is_pk_field && !is_ignored
961 })
962 .map(|f| {
963 let fident = f.ident.as_ref().unwrap();
964 let mut col = fident.to_string();
965 for attr in &f.attrs {
966 if attr.path().is_ident("column") {
967 let _ = attr.parse_nested_meta(|meta| {
968 if meta.path.is_ident("name") {
969 if let Ok(lit) = meta.value()?.parse::<LitStr>() {
970 col = lit.value();
971 }
972 }
973 Ok(())
974 });
975 }
976 }
977 quote! {
978 (#col, ::rust_web_server::model::ToColumn::to_column(&self.#fident))
979 }
980 })
981 .collect();
982
983 Ok(quote! {
985 impl ::rust_web_server::model::Model for #struct_name {
986 fn table_name() -> &'static str {
987 #table_name
988 }
989
990 fn column_names() -> &'static [&'static str] {
991 &[#(#all_col_names),*]
992 }
993
994 fn primary_key_name() -> &'static str {
995 #pk_col_name
996 }
997
998 fn primary_key_value(&self) -> ::rust_web_server::model::Value {
999 ::rust_web_server::model::ToColumn::to_column(&self.#pk_field_ident)
1000 }
1001
1002 fn primary_key_auto_increment() -> bool {
1003 #auto_inc
1004 }
1005
1006 fn from_row(row: &::rust_web_server::model::ModelRow) -> ::core::result::Result<Self, ::rust_web_server::model::DbError> {
1007 ::core::result::Result::Ok(#struct_name {
1008 #pk_from_row,
1009 #(#regular_from_row),*
1010 })
1011 }
1012
1013 fn to_values(&self) -> ::std::vec::Vec<(&'static str, ::rust_web_server::model::Value)> {
1014 vec![
1015 #pk_to_values,
1016 #(#regular_to_values),*
1017 ]
1018 }
1019
1020 #created_at
1021 #updated_at
1022 #deleted_at
1023 #version
1024 }
1025
1026 impl #struct_name {
1027 pub fn repository(pool: &::rust_web_server::model::DbPool) -> ::rust_web_server::model::ModelRepository<#struct_name, #repo_key> {
1029 ::rust_web_server::model::ModelRepository::new(pool)
1030 }
1031
1032 pub fn query(pool: &::rust_web_server::model::DbPool) -> ::rust_web_server::model::QueryBuilder<#struct_name> {
1034 ::rust_web_server::model::QueryBuilder::new(pool)
1035 }
1036 }
1037 })
1038}
1039
1040fn parse_table_name(
1041 attrs: &[syn::Attribute],
1042 struct_name: &syn::Ident,
1043) -> syn::Result<String> {
1044 for attr in attrs {
1045 if attr.path().is_ident("table") {
1046 let mut name: Option<String> = None;
1047 attr.parse_nested_meta(|meta| {
1048 if meta.path.is_ident("name") {
1049 let lit: LitStr = meta.value()?.parse()?;
1050 name = Some(lit.value());
1051 }
1052 Ok(())
1053 })?;
1054 if let Some(n) = name {
1055 return Ok(n);
1056 }
1057 }
1058 }
1059 Ok(struct_name.to_string().to_lowercase())
1061}
1062
1063#[proc_macro_derive(Factory)]
1088pub fn derive_factory(input: TokenStream) -> TokenStream {
1089 let ast = parse_macro_input!(input as DeriveInput);
1090 impl_factory(ast)
1091 .unwrap_or_else(|e| e.to_compile_error())
1092 .into()
1093}
1094
1095fn impl_factory(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
1096 let span = input.ident.span();
1097 let struct_name = &input.ident;
1098 if !input.generics.params.is_empty() {
1099 return Err(syn::Error::new(span, "#[derive(Factory)] doesn't support generic structs"));
1100 }
1101 let fields = match &input.data {
1102 Data::Struct(s) => match &s.fields {
1103 Fields::Named(f) => &f.named,
1104 _ => return Err(syn::Error::new(span, "#[derive(Factory)] only supports structs with named fields")),
1105 },
1106 _ => return Err(syn::Error::new(span, "#[derive(Factory)] can only be derived on structs")),
1107 };
1108
1109 let mut has_pk = false;
1110 let mut inits: Vec<proc_macro2::TokenStream> = Vec::new();
1111 for field in fields {
1112 let ident = field.ident.as_ref().unwrap();
1113 let ty = &field.ty;
1114 let name = ident.to_string();
1115 let name = name.strip_prefix("r#").unwrap_or(&name).to_string();
1116 let mut is_pk = false;
1117 let mut auto_increment = false;
1118 let mut ignored = false;
1119 for attr in &field.attrs {
1120 if attr.path().is_ident("ignore") {
1121 ignored = true;
1122 } else if attr.path().is_ident("primary_key") {
1123 is_pk = true;
1124 let _ = attr.parse_nested_meta(|meta| {
1125 if meta.path.is_ident("auto_increment") {
1126 auto_increment = true;
1127 }
1128 Ok(())
1129 });
1130 }
1131 }
1132 has_pk |= is_pk;
1133 let probe = quote! { (&&__Probe::<#ty>(::core::marker::PhantomData)) };
1134 inits.push(if ignored {
1135 quote! { #ident: ::core::default::Default::default() }
1136 } else if is_pk && !auto_increment {
1137 quote! { #ident: #probe.make_key(#name, __seq) }
1138 } else {
1139 quote! { #ident: #probe.make(#name, __seq) }
1140 });
1141 }
1142 if !has_pk {
1143 return Err(syn::Error::new(span, "#[derive(Factory)] needs the #[primary_key] field of #[derive(Model)]"));
1144 }
1145
1146 Ok(quote! {
1147 impl ::rust_web_server::test_client::factory::Factory for #struct_name {
1148 fn factory_default(__seq: u64) -> Self {
1149 #[allow(unused_imports)]
1150 use ::rust_web_server::test_client::factory::__private::{
1151 Probe as __Probe, ViaDefault as _, ViaValue as _,
1152 };
1153 #struct_name {
1154 #(#inits),*
1155 }
1156 }
1157 }
1158
1159 impl #struct_name {
1160 pub fn factory() -> ::rust_web_server::test_client::factory::ModelFactory<#struct_name> {
1162 ::rust_web_server::test_client::factory::ModelFactory::new()
1163 }
1164 }
1165 })
1166}
1167
1168#[proc_macro_derive(JsonSchema, attributes(serde))]
1194pub fn derive_json_schema(input: TokenStream) -> TokenStream {
1195 let ast = parse_macro_input!(input as DeriveInput);
1196 impl_json_schema(ast)
1197 .unwrap_or_else(|e| e.to_compile_error())
1198 .into()
1199}
1200
1201fn impl_json_schema(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
1202 let name = &input.ident;
1203 let container = SerdeAttrs::parse(&input.attrs)?;
1204 let description = doc_comment(&input.attrs);
1205 let body = match &input.data {
1206 Data::Struct(s) => {
1207 let Fields::Named(fields) = &s.fields else {
1208 return Err(syn::Error::new_spanned(
1209 name,
1210 "#[derive(JsonSchema)] supports structs with named fields and enums of unit variants",
1211 ));
1212 };
1213 let mut properties = Vec::new();
1214 for field in &fields.named {
1215 let attrs = SerdeAttrs::parse(&field.attrs)?;
1216 if attrs.skip {
1217 continue;
1218 }
1219 let ident = field.ident.as_ref().expect("named field");
1220 let json_name = attrs.rename.clone().unwrap_or_else(|| {
1221 rename_with(ident.to_string().trim_start_matches("r#"), container.rename_all.as_deref(), false)
1222 });
1223 let ty = &field.ty;
1224 let field_doc = doc_comment(&field.attrs);
1225 let may_be_absent = attrs.default || container.default || attrs.skip_serializing_if;
1226 properties.push(quote! {
1227 __schema.property(
1228 #json_name,
1229 &_rws::schema::nested::<#ty>(),
1230 #field_doc,
1231 !#may_be_absent && !<#ty as _rws::schema::JsonSchema>::is_optional(),
1232 );
1233 });
1234 }
1235 quote! {
1236 let mut __schema = _rws::schema::ObjectSchema::new(#description);
1237 #(#properties)*
1238 __schema.finish()
1239 }
1240 }
1241 Data::Enum(e) => {
1242 let mut values = Vec::new();
1243 for variant in &e.variants {
1244 if !matches!(variant.fields, Fields::Unit) {
1245 return Err(syn::Error::new_spanned(
1246 variant,
1247 "#[derive(JsonSchema)] supports only unit enum variants",
1248 ));
1249 }
1250 let attrs = SerdeAttrs::parse(&variant.attrs)?;
1251 if attrs.skip {
1252 continue;
1253 }
1254 values.push(attrs.rename.unwrap_or_else(|| {
1255 rename_with(&variant.ident.to_string(), container.rename_all.as_deref(), true)
1256 }));
1257 }
1258 quote! { _rws::schema::string_enum(#description, &[#(#values),*]) }
1259 }
1260 Data::Union(_) => {
1261 return Err(syn::Error::new_spanned(name, "#[derive(JsonSchema)] does not support unions"));
1262 }
1263 };
1264
1265 let mut generics = input.generics.clone();
1266 for param in generics.type_params_mut() {
1267 param.bounds.push(syn::parse_quote!(::rust_web_server::schema::JsonSchema));
1268 }
1269 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
1270 Ok(quote! {
1271 const _: () = {
1272 use ::rust_web_server as _rws;
1273 impl #impl_generics _rws::schema::JsonSchema for #name #ty_generics #where_clause {
1274 fn json_schema() -> ::std::string::String {
1275 #body
1276 }
1277 }
1278 };
1279 })
1280}
1281
1282#[derive(Default)]
1284struct SerdeAttrs {
1285 rename: Option<String>,
1286 rename_all: Option<String>,
1287 default: bool,
1288 skip: bool,
1289 skip_serializing_if: bool,
1290}
1291
1292impl SerdeAttrs {
1293 fn parse(attrs: &[syn::Attribute]) -> syn::Result<Self> {
1294 let mut out = SerdeAttrs::default();
1295 for attr in attrs.iter().filter(|a| a.path().is_ident("serde")) {
1296 attr.parse_nested_meta(|meta| {
1297 let key = meta.path.get_ident().map(|i| i.to_string()).unwrap_or_default();
1298 match key.as_str() {
1299 "rename" | "rename_all" => {
1300 if !meta.input.peek(Token![=]) {
1301 return Err(meta.error(format!(
1302 "#[derive(JsonSchema)] needs one `{key}` for both directions (one schema describes input and output)"
1303 )));
1304 }
1305 let value = meta.value()?.parse::<LitStr>()?.value();
1306 if key == "rename" {
1307 out.rename = Some(value);
1308 } else {
1309 if rename_with("a_b", Some(&value), false).is_empty() {
1310 return Err(meta.error(format!("unknown rename_all rule \"{value}\"")));
1311 }
1312 out.rename_all = Some(value);
1313 }
1314 }
1315 "default" => {
1316 out.default = true;
1317 if meta.input.peek(Token![=]) {
1318 meta.value()?.parse::<LitStr>()?;
1319 }
1320 }
1321 "skip" | "skip_serializing" | "skip_deserializing" => out.skip = true,
1322 "skip_serializing_if" => {
1323 out.skip_serializing_if = true;
1324 meta.value()?.parse::<LitStr>()?;
1325 }
1326 "flatten" | "tag" | "content" | "untagged" | "transparent" | "with" | "serialize_with"
1327 | "deserialize_with" | "remote" | "from" | "try_from" | "into" | "getter" => {
1328 return Err(meta.error(format!(
1329 "#[derive(JsonSchema)] does not support #[serde({key})]: the schema would not match the JSON"
1330 )));
1331 }
1332 _ => {
1333 if meta.input.peek(Token![=]) {
1337 meta.value()?.parse::<proc_macro2::TokenStream>()?;
1338 } else if meta.input.peek(syn::token::Paren) {
1339 meta.parse_nested_meta(|nested| {
1340 if nested.input.peek(Token![=]) {
1341 nested.value()?.parse::<proc_macro2::TokenTree>()?;
1342 }
1343 Ok(())
1344 })?;
1345 }
1346 }
1347 }
1348 Ok(())
1349 })?;
1350 }
1351 Ok(out)
1352 }
1353}
1354
1355fn rename_with(name: &str, rule: Option<&str>, variant: bool) -> String {
1359 let Some(rule) = rule else { return name.to_string() };
1360 let words: Vec<String> = if variant {
1362 let mut words = Vec::new();
1363 let mut current = String::new();
1364 for c in name.chars() {
1365 if c.is_uppercase() && !current.is_empty() {
1366 words.push(std::mem::take(&mut current));
1367 }
1368 current.extend(c.to_lowercase());
1369 }
1370 if !current.is_empty() {
1371 words.push(current);
1372 }
1373 words
1374 } else {
1375 name.split('_').map(str::to_string).collect()
1376 };
1377 let capitalize = |w: &String| {
1378 let mut chars = w.chars();
1379 chars.next().map(|c| c.to_uppercase().chain(chars).collect::<String>()).unwrap_or_default()
1380 };
1381 match rule {
1382 "lowercase" => words.concat(),
1383 "UPPERCASE" => words.concat().to_uppercase(),
1384 "PascalCase" => words.iter().map(capitalize).collect(),
1385 "camelCase" => {
1386 let pascal: String = words.iter().map(capitalize).collect();
1387 let mut chars = pascal.chars();
1388 chars.next().map(|c| c.to_lowercase().chain(chars).collect()).unwrap_or_default()
1389 }
1390 "snake_case" => words.join("_"),
1391 "SCREAMING_SNAKE_CASE" => words.join("_").to_uppercase(),
1392 "kebab-case" => words.join("-"),
1393 "SCREAMING-KEBAB-CASE" => words.join("-").to_uppercase(),
1394 _ => String::new(),
1395 }
1396}
1397
1398fn doc_comment(attrs: &[syn::Attribute]) -> String {
1400 let mut lines = Vec::new();
1401 for attr in attrs.iter().filter(|a| a.path().is_ident("doc")) {
1402 if let syn::Meta::NameValue(nv) = &attr.meta {
1403 if let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value {
1404 let line = s.value().trim().to_string();
1405 if !line.is_empty() {
1406 lines.push(line);
1407 }
1408 }
1409 }
1410 }
1411 lines.join(" ")
1412}
1413
1414#[cfg(test)]
1415mod tests {
1416 use super::rename_with;
1417
1418 #[test]
1419 fn rename_all_rules_match_serde() {
1420 let field = |rule| rename_with("line_item_id", Some(rule), false);
1421 assert_eq!("lineitemid", field("lowercase"));
1422 assert_eq!("LINEITEMID", field("UPPERCASE"));
1423 assert_eq!("LineItemId", field("PascalCase"));
1424 assert_eq!("lineItemId", field("camelCase"));
1425 assert_eq!("line_item_id", field("snake_case"));
1426 assert_eq!("LINE_ITEM_ID", field("SCREAMING_SNAKE_CASE"));
1427 assert_eq!("line-item-id", field("kebab-case"));
1428 assert_eq!("LINE-ITEM-ID", field("SCREAMING-KEBAB-CASE"));
1429 let variant = |rule| rename_with("BackOrdered", Some(rule), true);
1430 assert_eq!("backordered", variant("lowercase"));
1431 assert_eq!("backOrdered", variant("camelCase"));
1432 assert_eq!("back_ordered", variant("snake_case"));
1433 assert_eq!("BACK-ORDERED", variant("SCREAMING-KEBAB-CASE"));
1434 assert_eq!("BackOrdered", variant("PascalCase"));
1435 assert_eq!("as_is", rename_with("as_is", None, false));
1436 assert_eq!("", rename_with("a_b", Some("Train-Case"), false));
1437 }
1438}