Rust library to generate static websites
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: cleanup macro

+85 -27
+44 -27
crates/maudit-macros/src/lib.rs
··· 63 63 return Ok(RouteArgs { path, locales }); 64 64 } 65 65 66 - // Try to parse the first argument 67 - let lookahead = input.lookahead1(); 66 + // First argument: either a path expression or a named argument like locales(...) 67 + if input.peek(Ident) && input.peek2(syn::token::Paren) { 68 + // First argument is a named argument (e.g., locales(...)) 69 + // This means it's a variant-only route with no base path 70 + let ident: Ident = input.parse()?; 71 + let ident_str = ident.to_string(); 68 72 69 - // Check if it's "locales(...)" 70 - if lookahead.peek(Ident) { 71 - let ident: Ident = input.fork().parse()?; 72 - if ident == "locales" { 73 - // Parse locales(...) argument 74 - input.parse::<Ident>()?; // consume "locales" 73 + if ident_str == "locales" { 75 74 let content; 76 75 syn::parenthesized!(content in input); 77 - 78 76 let variants = Punctuated::<LocaleVariant, Token![,]>::parse_terminated(&content)?; 79 77 locales = variants.into_iter().collect(); 80 - 81 - // Check for duplicate locales 82 - Self::check_duplicate_locales(&locales)?; 83 - 84 - return Ok(RouteArgs { path, locales }); 78 + } else { 79 + return Err(syn::Error::new_spanned( 80 + ident, 81 + format!("unknown argument '{}', expected 'locales'", ident_str), 82 + )); 85 83 } 86 - } 87 - 88 - // Otherwise, try to parse as path expression 89 - if !input.is_empty() { 84 + } else { 85 + // First argument is a path expression 90 86 path = Some(input.parse::<Expr>()?); 91 87 } 92 88 93 - // Check if there's a comma and more args 94 - if !input.is_empty() { 89 + // Parse remaining named arguments (locales, middleware, etc.) 90 + while !input.is_empty() { 95 91 input.parse::<Token![,]>()?; 96 92 97 - // Check for locales(...) as second argument 98 - if input.peek(Ident) { 93 + if input.is_empty() { 94 + break; 95 + } 96 + 97 + // All subsequent arguments must be named (e.g., locales(...), middleware(...)) 98 + if input.peek(Ident) && input.peek2(syn::token::Paren) { 99 99 let ident: Ident = input.parse()?; 100 - if ident == "locales" { 100 + let ident_str = ident.to_string(); 101 + 102 + if ident_str == "locales" { 103 + if !locales.is_empty() { 104 + return Err(syn::Error::new_spanned( 105 + ident, 106 + "locales specified multiple times", 107 + )); 108 + } 101 109 let content; 102 110 syn::parenthesized!(content in input); 103 - 104 111 let variants = 105 112 Punctuated::<LocaleVariant, Token![,]>::parse_terminated(&content)?; 106 113 locales = variants.into_iter().collect(); 107 - 108 - // Check for duplicate locales 109 - Self::check_duplicate_locales(&locales)?; 114 + } else { 115 + return Err(syn::Error::new_spanned( 116 + ident, 117 + format!("unknown argument '{}'", ident_str), 118 + )); 110 119 } 120 + } else { 121 + return Err(syn::Error::new( 122 + input.span(), 123 + "expected named argument (e.g., locales(...)), path must be first argument", 124 + )); 111 125 } 112 126 } 127 + 128 + // Check for duplicate locales 129 + Self::check_duplicate_locales(&locales)?; 113 130 114 131 Ok(RouteArgs { path, locales }) 115 132 }
+15
examples/i18n/src/routes/wrong_order.rs.test
··· 1 + use crate::layout::layout; 2 + use maud::html; 3 + use maudit::route::prelude::*; 4 + 5 + // This should produce a compile error because path comes after locales 6 + #[route(locales(en = "/en"), "/about")] 7 + pub struct WrongOrder; 8 + 9 + impl Route for WrongOrder { 10 + fn render(&self, _ctx: &mut PageContext) -> impl Into<RenderResult> { 11 + layout(html! { 12 + h1 { "This should not compile!" } 13 + }) 14 + } 15 + }
+26
examples/i18n/test_errors.txt
··· 1 + Test cases for error handling: 2 + 3 + 1. Duplicate locales error: 4 + #[route("/about", locales(en = "/en"), locales(sv = "/sv"))] 5 + Expected error: "locales specified multiple times" 6 + 7 + 2. Duplicate locale within locales: 8 + #[route("/about", locales(en = "/en", en = "/english"))] 9 + Expected error: "duplicate locale 'en' specified" 10 + 11 + 3. Unknown argument: 12 + #[route("/about", something(foo = "bar"))] 13 + Expected error: "unknown argument 'something'" 14 + 15 + 4. Prefix without base path: 16 + #[route(locales(en(prefix = "/en")))] 17 + Expected error: "Cannot use locale prefix without a base route path" 18 + 19 + 5. Path in wrong position: 20 + #[route(locales(en = "/en"), "/about")] 21 + Expected error: "expected named argument (e.g., locales(...)), path must be first argument" 22 + 23 + All these cases should produce compile-time errors with clear messages. 24 + The refactored parser enforces that the path must be the first argument (if present), 25 + followed by named arguments like locales(...). This is flexible enough to support 26 + future named arguments while maintaining clear syntax rules.