1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
//! Proc-macro derive for `UriConfig` — generates URI parsing implementations from struct field attributes.
//!
//! Main macro: `#[derive(UriConfig)]`. Supports `#[uri_scheme]`, `#[uri_param]`, and related attributes.
use TokenStream;
use ;
/// Derive macro for UriConfig trait implementation.
///
/// This macro generates the `from_uri()` implementation based on struct field attributes.
///
/// # Attributes
///
/// ## Struct-level attributes
///
/// - `#[uri_scheme = "xxx"]` - Required, defines the URI scheme
/// - `#[uri_config(skip_impl)]` - Optional, generates only the parsing helper method
/// instead of the full trait impl. Use this when you need custom `validate()` logic.
/// - `#[uri_config(crate = "path")]` - Optional, overrides the generated code's crate
/// path. Defaults to `camel_endpoint`. Component crates using the
/// `camel-component-api` re-exports set this to `camel_component_api`.
/// - `#[uri_config(metadata(scheme = "..", description = "..", producer, consumer,
/// polling_consumer, streaming))]` - Optional, opts in to generating an inherent
/// `fn metadata() -> ComponentMetadata` on the config struct. The group mixes bare
/// capability flags (`producer`, `consumer`, `polling_consumer`, `streaming`) with
/// `key = "value"` pairs (`scheme`, `description`). When `scheme` is omitted it falls
/// back to the `#[uri_scheme]` value.
///
/// ## Field-level attributes
///
/// - `#[uri_param]` - Marks a field as a URI query parameter (uses field name as param name)
/// - `#[uri_param(default = "value")]` - Provides a default value if param not present
/// - `#[uri_param(name = "paramName")]` - Maps to a different query parameter name
/// - `#[uri_param(desc = "text")]` - Human-readable description for the generated
/// `UriOption`.
/// - `#[uri_param(required)]` - Marks the option required (bare flag; also accepts
/// `required = true`). Without it, `Option<T>` fields are not required, and
/// non-`Option` fields without a `default` are required.
/// - `#[uri_param(secret)]` - Marks the option as secret (bare flag; also accepts
/// `secret = true`). Combining `secret` with `default` is a compile error.
/// - `#[uri_param(deprecated = "reason")]` - Deprecation notice.
/// - `#[uri_param(aliases = ["a", "b"])]` - Alias parameter names.
/// - `#[uri_param(kind = "duration|bool|int|float|string|enum:A,B")]` - Overrides the
/// inferred `OptionKind`. Inference never produces `Enum`; the only way to get an
/// `Enum` option is an explicit `kind = "enum:..."`. An unrecognized kind string is a
/// spanned compile error.
///
/// # Generated helper functions
///
/// In addition to the URI parsing impl, the derive always generates:
///
/// - `pub fn uri_options() -> Vec<UriOption>` - one entry per `#[uri_param]` field
/// (the path field is excluded). `OptionKind` is inferred from the Rust type after
/// unwrapping `Option<T>`.
///
/// And, when `#[uri_config(metadata(..))]` is present:
///
/// - `pub fn metadata() -> ComponentMetadata` - built from the metadata attribute and
/// the derived `uri_options()`. Component structs delegate their `Component::metadata`
/// override to this (e.g. `fn metadata(&self) -> ComponentMetadata { Config::metadata() }`).
///
/// # Example
///
/// ## Basic usage
///
/// ```ignore
/// use camel_endpoint::UriConfig;
///
/// #[derive(Debug, Clone, UriConfig)]
/// #[uri_scheme = "timer"]
/// struct TimerConfig {
/// // First field without #[uri_param] gets the path component
/// name: String,
///
/// // Query parameters
/// #[uri_param(default = "1000")]
/// period: u64,
///
/// #[uri_param(default = "true")]
/// repeat: bool,
///
/// #[uri_param(name = "cronExpr")]
/// cron: Option<String>,
/// }
///
/// // Generated impl allows:
/// let config = TimerConfig::from_uri("timer:tick?period=5000").unwrap();
/// assert_eq!(config.name, "tick");
/// assert_eq!(config.period, 5000);
/// assert!(config.repeat); // uses default
/// assert!(config.cron.is_none()); // Option defaults to None
/// ```
///
/// ## Custom validation with `skip_impl`
///
/// ```ignore
/// use camel_endpoint::UriConfig;
///
/// #[derive(Debug, Clone, UriConfig)]
/// #[uri_scheme = "file"]
/// #[uri_config(skip_impl)]
/// struct FileConfig {
/// directory: String,
/// #[uri_param(default = "false")]
/// delete: bool,
/// #[uri_param(name = "move")]
/// move_to: Option<String>,
/// }
///
/// // Implement the trait manually with custom validation
/// impl UriConfig for FileConfig {
/// fn scheme() -> &'static str { "file" }
///
/// fn from_uri(uri: &str) -> Result<Self, CamelError> {
/// let parts = parse_uri(uri)?;
/// Self::from_components(parts)
/// }
///
/// fn from_components(parts: UriComponents) -> Result<Self, CamelError> {
/// Self::parse_uri_components(parts)?.validate()
/// }
///
/// fn validate(self) -> Result<Self, CamelError> {
/// // Custom validation: move_to is None if delete is true
/// let move_to = if self.delete { None } else { self.move_to };
/// Ok(Self { move_to, ..self })
/// }
/// }
/// ```
///
/// # OptionKind type inference
///
/// The `OptionKind` for each `#[uri_param]` field is inferred from its Rust
/// type after unwrapping `Option<T>`:
///
/// | Rust type | Inferred `OptionKind` |
/// |-------------------------------|-----------------------------------------------------|
/// | `std::time::Duration` | `Duration` |
/// | `bool` | `Bool` |
/// | `u8`, `u16`, `u32`, `u64`, `usize`, `i8`, `i16`, `i32`, `i64`, `isize` | `Int` |
/// | `f32`, `f64` | `Float` |
/// | `String`, `&str` | `String` |
/// | `Vec<T>` | `List(Box::new(inner_kind_of_T))` |
/// | anything else (enums, custom types, …) | `String` |
///
/// **Inference never produces `OptionKind::Enum`.** The only way to get an
/// `Enum` option is an explicit `kind = "enum:A,B,C"` override.
///
/// # Guardrail: `secret` + `default` is a compile error
///
/// `#[uri_param(secret, default = "x")]` produces a compile-time error:
/// *\"`#[uri_param]` cannot have both `secret` and `default`; a secret must
/// never carry a default value.\"* This prevents sensitive values from being embedded
/// in generated code or discovery output.
///
/// # Delegation convention
///
/// The macro generates `uri_options()` and (when opted in) `metadata()` as
/// inherent methods on the **config** struct. The **component** struct
/// implements `Component`, whose `metadata()` default returns
/// `ComponentMetadata::minimal(scheme)` with empty `uri_options`. Every
/// component MUST override `metadata()` to delegate to its config struct:
///
/// ```ignore
/// impl Component for MyComponent {
/// fn scheme(&self) -> &str { "my-scheme" }
///
/// fn metadata(&self) -> ComponentMetadata {
/// MyConfig::metadata()
/// // Or, without the metadata(..) opt-in:
/// // ComponentMetadata::minimal(self.scheme())
/// // .with_uri_options(MyConfig::uri_options())
/// }
/// }
/// ```
///
/// Without this delegation step, the catalog returns empty `uri_options`.
///
/// # Worked example: component with metadata
///
/// ```ignore
/// use camel_endpoint::UriConfig;
///
/// #[derive(Debug, Clone, UriConfig)]
/// #[uri_scheme = "sql"]
/// #[uri_config(
/// metadata(
/// scheme = "sql",
/// description = "Execute SQL against a configured datasource",
/// producer,
/// consumer,
/// )
/// )]
/// struct SqlConfig {
/// query: String,
///
/// #[uri_param(secret, desc = "Database connection URL")]
/// db_url: String,
///
/// #[uri_param(
/// name = "outputType",
/// desc = "Output type for query results",
/// kind = "enum:SelectList,SelectOne,StreamList",
/// default = "SelectList"
/// )]
/// output_type: SqlOutputType,
///
/// #[uri_param(
/// name = "maxConnections",
/// desc = "Maximum connections in the pool",
/// default = "5"
/// )]
/// max_connections: u32,
/// }
///
/// // Generated:
/// // - SqlConfig::uri_options() returns 3 UriOption entries
/// // - SqlConfig::metadata() returns ComponentMetadata with scheme "sql",
/// // producer + consumer capabilities, and the derived uri_options
///
/// // Component delegation (hand-written):
/// impl Component for SqlComponent {
/// fn scheme(&self) -> &str { "sql" }
/// fn metadata(&self) -> ComponentMetadata { SqlConfig::metadata() }
/// }
/// ```