Skip to main content

camel_endpoint_macros/
lib.rs

1//! Proc-macro derive for `UriConfig` — generates URI parsing implementations from struct field attributes.
2//!
3//! Main macro: `#[derive(UriConfig)]`. Supports `#[uri_scheme]`, `#[uri_param]`, and related attributes.
4
5mod uri_config;
6
7use proc_macro::TokenStream;
8use syn::{DeriveInput, parse_macro_input};
9
10/// Derive macro for UriConfig trait implementation.
11///
12/// This macro generates the `from_uri()` implementation based on struct field attributes.
13///
14/// # Attributes
15///
16/// ## Struct-level attributes
17///
18/// - `#[uri_scheme = "xxx"]` - Required, defines the URI scheme
19/// - `#[uri_config(skip_impl)]` - Optional, generates only the parsing helper method
20///   instead of the full trait impl. Use this when you need custom `validate()` logic.
21///
22/// ## Field-level attributes
23///
24/// - `#[uri_param]` - Marks a field as a URI query parameter (uses field name as param name)
25/// - `#[uri_param(default = "value")]` - Provides a default value if param not present
26/// - `#[uri_param(name = "paramName")]` - Maps to a different query parameter name
27///
28/// # Example
29///
30/// ## Basic usage
31///
32/// ```ignore
33/// use camel_endpoint::UriConfig;
34///
35/// #[derive(Debug, Clone, UriConfig)]
36/// #[uri_scheme = "timer"]
37/// struct TimerConfig {
38///     // First field without #[uri_param] gets the path component
39///     name: String,
40///
41///     // Query parameters
42///     #[uri_param(default = "1000")]
43///     period: u64,
44///
45///     #[uri_param(default = "true")]
46///     repeat: bool,
47///
48///     #[uri_param(name = "cronExpr")]
49///     cron: Option<String>,
50/// }
51///
52/// // Generated impl allows:
53/// let config = TimerConfig::from_uri("timer:tick?period=5000").unwrap();
54/// assert_eq!(config.name, "tick");
55/// assert_eq!(config.period, 5000);
56/// assert!(config.repeat); // uses default
57/// assert!(config.cron.is_none()); // Option defaults to None
58/// ```
59///
60/// ## Custom validation with `skip_impl`
61///
62/// ```ignore
63/// use camel_endpoint::UriConfig;
64///
65/// #[derive(Debug, Clone, UriConfig)]
66/// #[uri_scheme = "file"]
67/// #[uri_config(skip_impl)]
68/// struct FileConfig {
69///     directory: String,
70///     #[uri_param(default = "false")]
71///     delete: bool,
72///     #[uri_param(name = "move")]
73///     move_to: Option<String>,
74/// }
75///
76/// // Implement the trait manually with custom validation
77/// impl UriConfig for FileConfig {
78///     fn scheme() -> &'static str { "file" }
79///     
80///     fn from_uri(uri: &str) -> Result<Self, CamelError> {
81///         let parts = parse_uri(uri)?;
82///         Self::from_components(parts)
83///     }
84///     
85///     fn from_components(parts: UriComponents) -> Result<Self, CamelError> {
86///         Self::parse_uri_components(parts)?.validate()
87///     }
88///     
89///     fn validate(self) -> Result<Self, CamelError> {
90///         // Custom validation: move_to is None if delete is true
91///         let move_to = if self.delete { None } else { self.move_to };
92///         Ok(Self { move_to, ..self })
93///     }
94/// }
95/// ```
96#[proc_macro_derive(UriConfig, attributes(uri_scheme, uri_param, uri_config))]
97pub fn derive_uri_config(input: TokenStream) -> TokenStream {
98    let input = parse_macro_input!(input as DeriveInput);
99    uri_config::impl_uri_config(&input).into()
100}