Skip to main content

camel_dsl/
route_ast.rs

1// serde_yml migrated to noyalib (compat-serde-yaml shim) — closes RUSTSEC-2025-0068.
2// Module alias preserves call-site paths byte-for-byte.
3use noyalib::compat::serde_yaml as serde_yml;
4
5use std::collections::BTreeMap;
6
7use serde::Deserialize;
8
9#[derive(Deserialize, Clone)]
10#[serde(deny_unknown_fields)]
11pub struct RouteDslRoutes {
12    /// Optional JSON Schema URL (ignored by the parser; consumed by SDKs/editors).
13    #[serde(default, skip_serializing, rename = "$schema")]
14    pub schema_url: Option<String>,
15
16    #[serde(default)]
17    pub routes: Vec<RouteDslRoute>,
18    #[serde(default)]
19    pub templates: Vec<RouteDslTemplate>,
20    #[serde(default)]
21    pub templated_routes: Vec<RouteDslTemplatedRoute>,
22    #[serde(default)]
23    pub rest: Vec<RouteDslRest>,
24}
25
26#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
27#[derive(Deserialize, Clone)]
28#[serde(deny_unknown_fields)]
29pub struct RouteDslRoute {
30    pub id: String,
31    pub from: String,
32    #[serde(default)]
33    pub steps: Vec<RouteDslStep>,
34    #[serde(default = "default_true")]
35    pub auto_startup: bool,
36    #[serde(default = "default_startup_order")]
37    pub startup_order: i32,
38    #[serde(default)]
39    pub sequential: bool,
40    #[serde(default)]
41    pub concurrent: Option<usize>,
42    #[serde(default)]
43    pub error_handler: Option<RouteDslErrorHandler>,
44    #[serde(default)]
45    pub circuit_breaker: Option<RouteDslCircuitBreaker>,
46    #[serde(default)]
47    pub security_policy: Option<RouteDslSecurityPolicy>,
48    #[serde(default)]
49    pub on_complete: Option<String>,
50    #[serde(default)]
51    pub on_failure: Option<String>,
52}
53
54#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
55#[derive(Deserialize, Debug, Clone)]
56#[serde(deny_unknown_fields)]
57pub struct RouteDslSecurityPolicy {
58    #[serde(default)]
59    pub roles: Option<Vec<String>>,
60    #[serde(default)]
61    pub scopes: Option<Vec<String>>,
62    #[serde(default)]
63    pub all_required: Option<bool>,
64    /// When `true`, the policy accepts an already-populated
65    /// `camel.auth.principal` exchange property when no Bearer token is
66    /// present. Default `false` (fail-closed) — the principal must come
67    /// from token validation. See H1 in
68    /// `docs/superpowers/specs/v1-sec-stabilization-spec.md`.
69    #[serde(default)]
70    pub trust_upstream_principal: Option<bool>,
71    #[serde(default)]
72    pub r#ref: Option<String>,
73    #[serde(default)]
74    pub wasm: Option<String>,
75    #[serde(default)]
76    pub config: Option<std::collections::HashMap<String, String>>,
77    #[serde(default)]
78    pub permission: Option<RouteDslPermissionPolicy>,
79}
80
81#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
82#[derive(Deserialize, Debug, Clone)]
83#[serde(deny_unknown_fields)]
84pub struct RouteDslPermissionPolicy {
85    pub policy: String,
86    #[serde(default)]
87    pub resource: Option<RouteDslPermissionValueSource>,
88    #[serde(default)]
89    pub action: Option<RouteDslPermissionValueSource>,
90    #[serde(default)]
91    pub scopes: Option<Vec<String>>,
92    #[serde(default)]
93    pub context: Option<RouteDslPermissionContext>,
94    #[serde(default)]
95    pub cache_ttl_secs: Option<u64>,
96    #[serde(default)]
97    pub cache_negative_ttl_secs: Option<u64>,
98}
99
100#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
101#[derive(Deserialize, Debug, Clone)]
102#[serde(deny_unknown_fields)]
103pub struct RouteDslPermissionValueSource {
104    #[serde(default)]
105    pub literal: Option<String>,
106    #[serde(default)]
107    pub header: Option<String>,
108    #[serde(default)]
109    pub property: Option<String>,
110}
111
112#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
113#[derive(Deserialize, Debug, Clone)]
114#[serde(deny_unknown_fields)]
115pub struct RouteDslPermissionContext {
116    #[serde(default)]
117    pub headers: Vec<String>,
118    #[serde(default)]
119    pub properties: Vec<String>,
120}
121
122#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
123#[derive(Deserialize, Clone)]
124#[serde(deny_unknown_fields)]
125pub struct RouteDslErrorHandler {
126    #[serde(default)]
127    pub dead_letter_channel: Option<String>,
128    #[serde(default)]
129    pub retry: Option<RouteDslRedeliveryPolicy>,
130    #[serde(default)]
131    pub on_exceptions: Option<Vec<RouteDslOnException>>,
132    #[serde(default)]
133    pub use_original_message: bool,
134}
135
136#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
137#[derive(Deserialize, Clone)]
138#[serde(deny_unknown_fields)]
139pub struct RouteDslOnException {
140    #[serde(default)]
141    pub kind: Option<String>,
142    #[serde(default)]
143    pub message_contains: Option<String>,
144    #[serde(default)]
145    pub retry: Option<RouteDslRedeliveryPolicy>,
146    #[serde(default)]
147    pub steps: Vec<RouteDslStep>,
148    #[serde(default)]
149    pub handled: Option<bool>,
150    #[serde(default)]
151    pub continued: Option<bool>,
152}
153
154#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
155#[derive(Deserialize, Clone)]
156#[serde(deny_unknown_fields)]
157pub struct RouteDslRedeliveryPolicy {
158    pub max_attempts: u32,
159    #[serde(default = "default_initial_delay_ms")]
160    pub initial_delay_ms: u64,
161    #[serde(default = "default_multiplier")]
162    pub multiplier: f64,
163    #[serde(default = "default_max_delay_ms")]
164    pub max_delay_ms: u64,
165    #[serde(default = "default_jitter_factor")]
166    pub jitter_factor: f64,
167    #[serde(default)]
168    pub handled_by: Option<String>,
169}
170
171#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
172#[derive(Deserialize, Clone)]
173#[serde(deny_unknown_fields)]
174pub struct RouteDslCircuitBreaker {
175    #[serde(default = "default_failure_threshold")]
176    pub failure_threshold: u32,
177    #[serde(default = "default_open_duration_ms")]
178    pub open_duration_ms: u64,
179}
180
181fn default_true() -> bool {
182    true
183}
184
185fn default_startup_order() -> i32 {
186    1000
187}
188
189fn default_initial_delay_ms() -> u64 {
190    100
191}
192
193fn default_multiplier() -> f64 {
194    2.0
195}
196
197fn default_max_delay_ms() -> u64 {
198    10_000
199}
200
201fn default_jitter_factor() -> f64 {
202    0.0
203}
204
205fn default_failure_threshold() -> u32 {
206    5
207}
208
209fn default_open_duration_ms() -> u64 {
210    30_000
211}
212
213#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
214#[derive(Deserialize, Debug, Clone)]
215#[serde(deny_unknown_fields)]
216pub struct DelayStep {
217    pub delay: DelayBody,
218}
219
220#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
221#[derive(Deserialize, Debug, Clone)]
222#[serde(untagged)]
223#[cfg_attr(feature = "schema", schemars(untagged))]
224pub enum DelayBody {
225    Short(u64),
226    Full(DelayFullConfig),
227}
228
229#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
230#[derive(Deserialize, Debug, Clone)]
231#[serde(deny_unknown_fields)]
232pub struct DelayFullConfig {
233    pub delay_ms: u64,
234    #[serde(default)]
235    pub dynamic_header: Option<String>,
236}
237
238#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
239#[derive(Deserialize, Debug, Clone)]
240#[serde(deny_unknown_fields)]
241pub struct LoopStep {
242    #[serde(rename = "loop")]
243    pub loop_data: LoopData,
244}
245
246#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
247#[derive(Deserialize, Debug, Clone)]
248#[serde(untagged)]
249#[cfg_attr(feature = "schema", schemars(untagged))]
250pub enum LoopData {
251    /// Shorthand: `loop: 3`
252    Count(usize),
253    /// Full form with config block.
254    Full(LoopFullConfig),
255}
256
257#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
258#[derive(Deserialize, Debug, Clone)]
259#[serde(deny_unknown_fields)]
260pub struct LoopFullConfig {
261    /// Fixed iteration count. Mutually exclusive with `while`.
262    pub count: Option<usize>,
263    /// While condition — expression-only (no nested steps).
264    #[serde(rename = "while")]
265    pub while_expr: Option<LoopWhileExpr>,
266    #[serde(default)]
267    pub steps: Vec<RouteDslStep>,
268}
269
270/// Expression-only predicate for loop while condition.
271#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
272#[derive(Deserialize, Debug, Clone)]
273#[serde(deny_unknown_fields)]
274pub struct LoopWhileExpr {
275    #[serde(default)]
276    pub language: Option<String>,
277    #[serde(default)]
278    pub source: Option<String>,
279    #[serde(default)]
280    pub simple: Option<String>,
281    #[serde(default)]
282    pub rhai: Option<String>,
283    #[serde(default)]
284    pub jsonpath: Option<String>,
285    #[serde(default)]
286    pub xpath: Option<String>,
287}
288
289#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
290#[derive(Deserialize, Debug, Clone)]
291#[serde(untagged)]
292#[cfg_attr(feature = "schema", schemars(untagged))]
293pub enum RouteDslStep {
294    To(ToStep),
295    SetHeader(SetHeaderStep),
296    SetProperty(SetPropertyStep),
297    SetBody(SetBodyStep),
298    Bean(BeanStep),
299    Choice(ChoiceStep),
300    DynamicRouter(DynamicRouterStep),
301    Filter(FilterStep),
302    Function(FunctionStep),
303    LoadBalance(LoadBalanceStep),
304    Log(LogStep),
305    Split(SplitStep),
306    Aggregate(AggregateStep),
307    WireTap(WireTapStep),
308    Multicast(MulticastStep),
309    RoutingSlip(RoutingSlipStep),
310    RecipientList(RecipientListStep),
311    ScatterGather(ScatterGatherStep),
312    Stop(StopStep),
313    StreamCache(StreamCacheStep),
314    Throttle(ThrottleStep),
315    Transform(TransformStep),
316    Script(ScriptStep),
317    ConvertBodyTo(ConvertBodyToStep),
318    Marshal(MarshalStep),
319    Unmarshal(UnmarshalStep),
320    Delay(DelayStep),
321    DoTry(DoTryStep),
322    Loop(LoopStep),
323    Validate(ValidateStep),
324    Enrich(EnrichStep),
325    PollEnrich(PollEnrichStep),
326    IdempotentConsumer(IdempotentConsumerStep),
327    ClaimCheck(ClaimCheckStep),
328    Sampling(SamplingStep),
329    Sort(SortStep),
330    Resequence(ResequenceStep),
331}
332
333#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
334#[derive(Deserialize, Debug, Clone)]
335#[serde(deny_unknown_fields)]
336pub struct FunctionStep {
337    pub function: FunctionData,
338}
339
340#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
341#[derive(Deserialize, Debug, Clone)]
342#[serde(deny_unknown_fields)]
343pub struct FunctionData {
344    pub runtime: String,
345    pub source: String,
346    #[serde(default)]
347    pub timeout_ms: Option<u64>,
348}
349
350#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
351#[derive(Deserialize, Debug, Clone)]
352#[serde(deny_unknown_fields)]
353pub struct ToStep {
354    pub to: String,
355}
356
357#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
358#[derive(Deserialize, Debug, Clone)]
359#[serde(deny_unknown_fields)]
360pub struct SetHeaderStep {
361    pub set_header: SetHeaderData,
362}
363
364#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
365#[derive(Deserialize, Debug, Clone)]
366#[serde(deny_unknown_fields)]
367pub struct SetPropertyStep {
368    pub set_property: SetPropertyData,
369}
370
371#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
372#[derive(Deserialize, Debug, Clone)]
373#[serde(deny_unknown_fields)]
374pub struct SetHeaderData {
375    pub key: String,
376    #[serde(default)]
377    pub value: Option<serde_json::Value>,
378    #[serde(default)]
379    pub language: Option<String>,
380    #[serde(default)]
381    pub source: Option<String>,
382    #[serde(default)]
383    pub simple: Option<String>,
384    #[serde(default)]
385    pub rhai: Option<String>,
386    #[serde(default)]
387    pub jsonpath: Option<String>,
388    #[serde(default)]
389    pub xpath: Option<String>,
390}
391
392#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
393#[derive(Deserialize, Debug, Clone)]
394#[serde(deny_unknown_fields)]
395pub struct SetPropertyData {
396    pub name: String,
397    #[serde(default)]
398    pub value: Option<serde_json::Value>,
399    #[serde(default)]
400    pub language: Option<String>,
401    #[serde(default)]
402    pub source: Option<String>,
403    #[serde(default)]
404    pub simple: Option<String>,
405    #[serde(default)]
406    pub rhai: Option<String>,
407    #[serde(default)]
408    pub jsonpath: Option<String>,
409    #[serde(default)]
410    pub xpath: Option<String>,
411}
412
413#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
414#[derive(Deserialize, Debug, Clone)]
415#[serde(deny_unknown_fields)]
416pub struct SetBodyStep {
417    pub set_body: SetBodyData,
418}
419
420/// `transform:` step — alias for `set_body:`, reuses all SetBodyData types.
421#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
422#[derive(Deserialize, Debug, Clone)]
423#[serde(deny_unknown_fields)]
424pub struct TransformStep {
425    pub transform: SetBodyData,
426}
427
428#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
429#[derive(Deserialize, Debug, Clone)]
430#[serde(untagged)]
431#[cfg_attr(feature = "schema", schemars(untagged))]
432pub enum SetBodyData {
433    // Config must be tried BEFORE Literal: serde_json::Value matches any YAML value (including
434    // objects), so it would greedily swallow `{ simple: "..." }` as a Literal JSON object
435    // instead of a SetBodyConfig. By placing Config first, structured forms like
436    // `set_body: { simple: "..." }` deserialize correctly.
437    Config(SetBodyConfig),
438    Literal(serde_json::Value),
439}
440
441#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
442#[derive(Deserialize, Debug, Clone)]
443#[serde(deny_unknown_fields)]
444pub struct SetBodyConfig {
445    #[serde(default)]
446    pub value: Option<serde_json::Value>,
447    #[serde(default)]
448    pub language: Option<String>,
449    #[serde(default)]
450    pub source: Option<String>,
451    #[serde(default)]
452    pub simple: Option<String>,
453    #[serde(default)]
454    pub rhai: Option<String>,
455    #[serde(default)]
456    pub jsonpath: Option<String>,
457    #[serde(default)]
458    pub xpath: Option<String>,
459}
460
461#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
462#[derive(Deserialize, Debug, Clone)]
463#[serde(untagged)]
464#[cfg_attr(feature = "schema", schemars(untagged))]
465pub enum LogBody {
466    Message(String),
467    Config(LogConfig),
468}
469
470#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
471#[derive(Deserialize, Debug, Clone)]
472#[serde(deny_unknown_fields)]
473pub struct LogConfig {
474    /// The log message. Can be a plain string literal or a nested expression object.
475    pub message: LogMessageData,
476    #[serde(default)]
477    pub level: Option<String>,
478}
479
480/// The `message` field inside a `log: { message: ... }` config block.
481/// Either a bare string literal or a value-source expression (simple, rhai, language+source).
482#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
483#[derive(Deserialize, Debug, Clone)]
484#[serde(untagged)]
485#[cfg_attr(feature = "schema", schemars(untagged))]
486pub enum LogMessageData {
487    Literal(String),
488    Expr(LogMessageExpr),
489}
490
491#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
492#[derive(Deserialize, Debug, Clone)]
493#[serde(deny_unknown_fields)]
494pub struct LogMessageExpr {
495    #[serde(default)]
496    pub value: Option<String>,
497    #[serde(default)]
498    pub language: Option<String>,
499    #[serde(default)]
500    pub source: Option<String>,
501    #[serde(default)]
502    pub simple: Option<String>,
503    #[serde(default)]
504    pub rhai: Option<String>,
505    #[serde(default)]
506    pub jsonpath: Option<String>,
507    #[serde(default)]
508    pub xpath: Option<String>,
509}
510
511#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
512#[derive(Deserialize, Debug, Clone)]
513#[serde(deny_unknown_fields)]
514pub struct LogStep {
515    pub log: LogBody,
516}
517
518#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
519#[derive(Deserialize, Debug, Clone)]
520#[serde(deny_unknown_fields)]
521pub struct FilterStep {
522    pub filter: PredicateBlock,
523}
524
525#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
526#[derive(Deserialize, Debug, Clone)]
527#[serde(deny_unknown_fields)]
528pub struct PredicateBlock {
529    #[serde(default)]
530    pub language: Option<String>,
531    #[serde(default)]
532    pub source: Option<String>,
533    #[serde(default)]
534    pub simple: Option<String>,
535    #[serde(default)]
536    pub rhai: Option<String>,
537    #[serde(default)]
538    pub jsonpath: Option<String>,
539    #[serde(default)]
540    pub xpath: Option<String>,
541    #[serde(default)]
542    pub steps: Vec<RouteDslStep>,
543}
544
545#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
546#[derive(Deserialize, Debug, Clone)]
547#[serde(deny_unknown_fields)]
548pub struct ChoiceStep {
549    pub choice: ChoiceData,
550}
551
552#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
553#[derive(Deserialize, Debug, Clone)]
554#[serde(deny_unknown_fields)]
555pub struct ChoiceData {
556    #[serde(default)]
557    pub when: Vec<PredicateBlock>,
558    #[serde(default)]
559    pub otherwise: Option<Vec<RouteDslStep>>,
560}
561
562#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
563#[derive(Deserialize, Debug, Clone)]
564#[serde(deny_unknown_fields)]
565pub struct DoTryStep {
566    pub do_try: DoTryData,
567}
568
569#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
570#[derive(Deserialize, Debug, Clone)]
571#[serde(deny_unknown_fields)]
572pub struct DoTryData {
573    pub steps: Vec<RouteDslStep>,
574    #[serde(default)]
575    pub catch: Vec<CatchClauseData>,
576    #[serde(default)]
577    pub finally: Option<FinallyData>,
578}
579
580#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
581#[derive(Deserialize, Debug, Clone)]
582#[serde(deny_unknown_fields)]
583pub struct CatchClauseData {
584    pub exception: Option<Vec<String>>,
585    pub when: Option<String>,
586    pub on_when: Option<String>,
587    #[serde(default = "default_handled_disposition")]
588    pub disposition: camel_api::error_handler::ExceptionDisposition,
589    pub steps: Vec<RouteDslStep>,
590}
591
592#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
593#[derive(Deserialize, Debug, Clone)]
594#[serde(deny_unknown_fields)]
595pub struct FinallyData {
596    pub on_when: Option<String>,
597    pub steps: Vec<RouteDslStep>,
598}
599
600fn default_handled_disposition() -> camel_api::error_handler::ExceptionDisposition {
601    camel_api::error_handler::ExceptionDisposition::Handled
602}
603
604#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
605#[derive(Deserialize, Debug, Clone)]
606#[serde(deny_unknown_fields)]
607pub struct SplitStep {
608    pub split: SplitData,
609}
610
611#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
612#[derive(Deserialize, Debug, Clone)]
613#[serde(deny_unknown_fields)]
614pub struct SplitData {
615    #[serde(default)]
616    pub expression: Option<SplitExpressionYaml>,
617    #[serde(default = "default_split_aggregation")]
618    pub aggregation: String,
619    #[serde(default)]
620    pub parallel: bool,
621    #[serde(default)]
622    pub parallel_limit: Option<usize>,
623    #[serde(default = "default_true")]
624    pub stop_on_exception: bool,
625    #[serde(default)]
626    pub steps: Vec<RouteDslStep>,
627    #[serde(default)]
628    pub streaming: bool,
629    #[serde(default)]
630    pub stream: Option<StreamConfigYaml>,
631}
632
633#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
634#[derive(Default, Deserialize, Debug, Clone)]
635#[serde(deny_unknown_fields)]
636pub struct StreamConfigYaml {
637    #[serde(default)]
638    pub format: Option<String>,
639    #[serde(default)]
640    pub max_record_bytes: Option<usize>,
641    #[serde(default)]
642    pub batch_size: Option<usize>,
643    #[serde(default)]
644    pub chunk_size: Option<usize>,
645}
646
647#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
648#[derive(Deserialize, Debug, Clone)]
649#[serde(untagged)]
650#[cfg_attr(feature = "schema", schemars(untagged))]
651pub enum SplitExpressionYaml {
652    Simple(String),
653    Config(SplitExpressionConfig),
654}
655
656#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
657#[derive(Deserialize, Debug, Clone)]
658#[serde(deny_unknown_fields)]
659pub struct SplitExpressionConfig {
660    #[serde(default)]
661    pub language: Option<String>,
662    #[serde(default)]
663    pub source: Option<String>,
664    #[serde(default)]
665    pub simple: Option<String>,
666    #[serde(default)]
667    pub rhai: Option<String>,
668    #[serde(default)]
669    pub jsonpath: Option<String>,
670    #[serde(default)]
671    pub xpath: Option<String>,
672}
673
674fn default_split_aggregation() -> String {
675    "last_wins".to_string()
676}
677
678#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
679#[derive(Deserialize, Debug, Clone)]
680#[serde(deny_unknown_fields)]
681pub struct AggregateStep {
682    pub aggregate: AggregateData,
683}
684
685#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
686#[derive(Deserialize, Debug, Clone)]
687#[serde(deny_unknown_fields)]
688pub struct AggregateData {
689    pub header: String,
690    #[serde(default)]
691    pub correlation_key: Option<String>,
692    #[serde(default)]
693    pub completion_size: Option<usize>,
694    #[serde(default)]
695    pub completion_timeout_ms: Option<u64>,
696    #[serde(default)]
697    pub completion_predicate: Option<PredicateBlock>,
698    #[serde(default = "default_aggregate_strategy")]
699    pub strategy: String,
700    #[serde(default)]
701    pub max_buckets: Option<usize>,
702    #[serde(default)]
703    pub bucket_ttl_ms: Option<u64>,
704    #[serde(default)]
705    pub force_completion_on_stop: Option<bool>,
706    #[serde(default)]
707    pub discard_on_timeout: Option<bool>,
708}
709
710fn default_aggregate_strategy() -> String {
711    "collect_all".to_string()
712}
713
714#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
715#[derive(Deserialize, Debug, Clone)]
716#[serde(deny_unknown_fields)]
717pub struct WireTapStep {
718    pub wire_tap: String,
719}
720
721#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
722#[derive(Deserialize, Debug, Clone)]
723#[serde(deny_unknown_fields)]
724pub struct MulticastStep {
725    pub multicast: MulticastData,
726}
727
728#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
729#[derive(Deserialize, Debug, Clone)]
730#[serde(deny_unknown_fields)]
731pub struct MulticastData {
732    #[serde(default)]
733    pub parallel: bool,
734    #[serde(default)]
735    pub parallel_limit: Option<usize>,
736    #[serde(default)]
737    pub stop_on_exception: bool,
738    #[serde(default)]
739    pub timeout_ms: Option<u64>,
740    #[serde(default = "default_multicast_aggregation")]
741    pub aggregation: String,
742    #[serde(default)]
743    pub steps: Vec<RouteDslStep>,
744}
745
746fn default_multicast_aggregation() -> String {
747    "last_wins".to_string()
748}
749
750#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
751#[derive(Deserialize, Debug, Clone)]
752#[serde(deny_unknown_fields)]
753pub struct ScatterGatherStep {
754    pub scatter_gather: ScatterGatherData,
755}
756
757/// Scatter-Gather EIP — stateless parallel fan-out + gather.
758/// Lowers to Multicast with aggregation. No correlation key
759/// (spec §5 "correlation key" wording corrected: stateless form
760/// has none; stateful aggregation is the separate Aggregator EIP).
761#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
762#[derive(Deserialize, Debug, Clone)]
763#[serde(deny_unknown_fields)]
764pub struct ScatterGatherData {
765    #[serde(default)]
766    pub endpoints: Vec<String>,
767    #[serde(default = "default_multicast_aggregation")]
768    pub aggregation: String,
769}
770
771#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
772#[derive(Deserialize, Debug, Clone)]
773#[serde(deny_unknown_fields)]
774pub struct StopStep {
775    pub stop: bool,
776}
777
778#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
779#[derive(Deserialize, Debug, Clone)]
780#[serde(deny_unknown_fields)]
781pub struct StreamCacheStep {
782    pub stream_cache: StreamCacheBody,
783}
784
785#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
786#[derive(Deserialize, Debug, Clone)]
787#[serde(untagged)]
788#[cfg_attr(feature = "schema", schemars(untagged))]
789pub enum StreamCacheBody {
790    Enabled(bool),
791    Config(StreamCacheConfig),
792}
793
794#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
795#[derive(Deserialize, Debug, Clone)]
796#[serde(deny_unknown_fields)]
797pub struct StreamCacheConfig {
798    #[serde(default)]
799    pub threshold: Option<usize>,
800}
801
802#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
803#[derive(Deserialize, Debug, Clone)]
804#[serde(deny_unknown_fields)]
805pub struct ScriptStep {
806    pub script: ScriptData,
807}
808
809#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
810#[derive(Deserialize, Debug, Clone)]
811#[serde(deny_unknown_fields)]
812pub struct ConvertBodyToStep {
813    pub convert_body_to: String,
814}
815
816#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
817#[derive(Deserialize, Debug, Clone)]
818#[serde(deny_unknown_fields)]
819pub struct MarshalStep {
820    pub marshal: String,
821}
822
823#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
824#[derive(Deserialize, Debug, Clone)]
825#[serde(deny_unknown_fields)]
826pub struct UnmarshalStep {
827    pub unmarshal: String,
828    /// Optional JSON Schema. When present, the compiled UnmarshalService is
829    /// wrapped with a `JsonSchemaValidateService` that returns
830    /// `CamelError::ValidationError` (→ 400 Bad Request) on a mismatch.
831    /// Only effective when the format parses to `Body::Json` (currently `json`
832    /// is the only format that produces JSON in v1).
833    #[serde(default)]
834    pub schema: Option<serde_json::Value>,
835}
836
837#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
838#[derive(Deserialize, Debug, Clone)]
839#[serde(deny_unknown_fields)]
840pub struct ValidateStep {
841    pub validate: String,
842}
843
844#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
845#[derive(Deserialize, Debug, Clone)]
846#[serde(deny_unknown_fields)]
847pub struct ClaimCheckStep {
848    pub claim_check: ClaimCheckBody,
849}
850
851#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
852#[derive(Deserialize, Debug, Clone)]
853#[serde(deny_unknown_fields)]
854pub struct ClaimCheckBody {
855    /// Name of the registered ClaimCheckRepository (e.g. "memory").
856    pub repository: String,
857    /// Operation: "set", "get", "get_and_remove", "push", "pop".
858    pub operation: String,
859    /// Simple-language expression for the claim-check key (e.g. "${header.claimKey}").
860    pub key: String,
861    /// Optional filter string for selective merge-back during checkout operations.
862    /// Grammar: comma-separated tokens with optional `+`/`-`/`--` prefix.
863    #[serde(default)]
864    pub filter: Option<String>,
865}
866
867#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
868#[derive(Deserialize, Debug, Clone)]
869#[serde(deny_unknown_fields)]
870pub struct SamplingStep {
871    pub sampling: SamplingBody,
872}
873
874#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
875#[derive(Deserialize, Debug, Clone)]
876#[serde(untagged)]
877#[cfg_attr(feature = "schema", schemars(untagged))]
878pub enum SamplingBody {
879    /// Shorthand: `sampling: 5` (period)
880    Short(usize),
881    /// Full form: `sampling: { period: 5 }`
882    Full(SamplingConfig),
883}
884
885#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
886#[derive(Deserialize, Debug, Clone)]
887#[serde(deny_unknown_fields)]
888pub struct SamplingConfig {
889    pub period: usize,
890}
891
892#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
893#[derive(Deserialize, Debug, Clone)]
894#[serde(deny_unknown_fields)]
895pub struct SortStep {
896    pub sort: SortBody,
897}
898
899#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
900#[derive(Deserialize, Debug, Clone)]
901#[serde(deny_unknown_fields)]
902pub struct SortBody {
903    /// Expression that extracts a sort key from each body element (e.g. simple: "${body.field}").
904    pub expression: String,
905    /// Reverse (descending) sort. Default false.
906    #[serde(default)]
907    pub reverse: bool,
908    /// Optional language specification (default: "simple").
909    #[serde(default)]
910    pub language: Option<String>,
911}
912
913#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
914#[derive(Deserialize, Debug, Clone)]
915#[serde(deny_unknown_fields)]
916pub struct IdempotentConsumerStep {
917    pub idempotent_consumer: IdempotentConsumerBody,
918}
919
920#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
921#[derive(Deserialize, Debug, Clone)]
922#[serde(deny_unknown_fields)]
923pub struct IdempotentConsumerBody {
924    /// Name of the registered IdempotentRepository (e.g. `"memory"`).
925    pub repository: String,
926    /// Simple-language expression that extracts the message-id (e.g. `"${header.messageId}"`).
927    pub expression: String,
928    /// Child sub-pipeline executed on first-time (non-duplicate) exchanges.
929    #[serde(default)]
930    pub steps: Vec<RouteDslStep>,
931    /// Reserve the key before running the child (default `false`).
932    #[serde(default)]
933    pub eager: Option<bool>,
934    /// When `eager`, remove the key if the child fails (default `false`).
935    #[serde(default)]
936    pub remove_on_failure: Option<bool>,
937}
938
939#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
940#[derive(Deserialize, Debug, Clone)]
941#[serde(deny_unknown_fields)]
942pub struct EnrichStep {
943    pub enrich: EnrichBody,
944}
945
946#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
947#[derive(Deserialize, Debug, Clone)]
948#[serde(deny_unknown_fields)]
949pub struct PollEnrichStep {
950    pub poll_enrich: EnrichBody,
951}
952
953#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
954#[derive(Deserialize, Debug, Clone)]
955#[serde(untagged)]
956#[cfg_attr(feature = "schema", schemars(untagged))]
957pub enum EnrichBody {
958    /// Shorthand: `enrich: "file:..."` or `poll_enrich: "file:..."`
959    Uri(String),
960    /// Full form: `enrich: { uri: "...", strategy: "...", timeout: ... }`
961    Full(EnrichConfig),
962}
963
964#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
965#[derive(Deserialize, Debug, Clone)]
966#[serde(deny_unknown_fields)]
967pub struct EnrichConfig {
968    pub uri: String,
969    #[serde(default)]
970    pub strategy: Option<String>,
971    #[serde(default)]
972    pub timeout: Option<u64>,
973}
974
975#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
976#[derive(Deserialize, Debug, Clone)]
977#[serde(deny_unknown_fields)]
978pub struct ScriptData {
979    pub language: String,
980    pub source: String,
981}
982
983#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
984#[derive(Deserialize, Debug, Clone)]
985#[serde(deny_unknown_fields)]
986pub struct ThrottleStep {
987    pub throttle: ThrottleData,
988}
989
990#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
991#[derive(Deserialize, Debug, Clone)]
992#[serde(deny_unknown_fields)]
993pub struct ThrottleData {
994    pub max_requests: usize,
995    #[serde(default = "default_throttle_period_secs")]
996    pub period_secs: u64,
997    #[serde(default)]
998    pub strategy: Option<String>,
999    #[serde(default)]
1000    pub steps: Vec<RouteDslStep>,
1001}
1002
1003fn default_throttle_period_secs() -> u64 {
1004    1
1005}
1006
1007#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
1008#[derive(Deserialize, Debug, Clone)]
1009#[serde(deny_unknown_fields)]
1010pub struct LoadBalanceStep {
1011    pub load_balance: LoadBalanceData,
1012}
1013
1014#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
1015#[derive(Deserialize, Debug, Clone)]
1016#[serde(deny_unknown_fields)]
1017pub struct LoadBalanceData {
1018    #[serde(default = "default_lb_strategy")]
1019    pub strategy: String,
1020    #[serde(default)]
1021    pub distribution_ratio: Option<String>,
1022    #[serde(default)]
1023    pub steps: Vec<RouteDslStep>,
1024}
1025
1026fn default_lb_strategy() -> String {
1027    "round_robin".to_string()
1028}
1029
1030// ---------------------------------------------------------------------------
1031// Template support types (Phase 4)
1032// ---------------------------------------------------------------------------
1033
1034/// A reusable route template declared in YAML/JSON.
1035#[derive(Deserialize, Debug, Clone)]
1036#[serde(deny_unknown_fields)]
1037pub struct RouteDslTemplate {
1038    pub id: String,
1039    #[serde(default)]
1040    pub parameters: Vec<RouteDslTemplateParameter>,
1041    #[serde(default)]
1042    pub routes: Vec<serde_yml::Value>,
1043}
1044
1045/// A single parameter that a route template accepts.
1046#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
1047#[derive(Deserialize, Debug, Clone)]
1048#[serde(deny_unknown_fields)]
1049pub struct RouteDslTemplateParameter {
1050    /// The parameter name (used inside `{{name}}` placeholders).
1051    pub name: String,
1052    /// Optional default value used when the caller does not supply one.
1053    #[serde(default)]
1054    pub default_value: Option<String>,
1055    /// Optional human-readable description.
1056    #[serde(default)]
1057    pub description: Option<String>,
1058}
1059
1060/// A request to instantiate a template with concrete parameter values.
1061#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
1062#[derive(Deserialize, Debug, Clone)]
1063#[serde(deny_unknown_fields)]
1064pub struct RouteDslTemplatedRoute {
1065    pub route_template_ref: String,
1066    /// Optional explicit route id for the resulting instance.
1067    #[serde(default)]
1068    pub route_id: Option<String>,
1069    /// Concrete parameter values keyed by parameter name.
1070    #[serde(default)]
1071    pub parameters: BTreeMap<String, String>,
1072}
1073
1074// ---------------------------------------------------------------------------
1075// REST DSL types (Phase 1)
1076// ---------------------------------------------------------------------------
1077
1078/// A top-level REST block (`rest:` in YAML/JSON).
1079#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
1080#[derive(Deserialize, Debug, Clone)]
1081#[serde(deny_unknown_fields)]
1082pub struct RouteDslRest {
1083    /// Listen host address.
1084    #[serde(default = "default_rest_host")]
1085    pub host: String,
1086    /// Listen port.
1087    #[serde(default = "default_rest_port")]
1088    pub port: u16,
1089    /// Base path for all operations in this block (e.g. `/api/users`).
1090    #[serde(default)]
1091    pub path: String,
1092    /// Operations for this block, each carrying its own HTTP method.
1093    #[serde(default)]
1094    pub operations: Vec<RouteDslRestOperation>,
1095}
1096
1097/// A single REST operation (identified by its `method` field).
1098#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
1099#[derive(Deserialize, Debug, Clone)]
1100#[serde(deny_unknown_fields)]
1101pub struct RouteDslRestOperation {
1102    /// HTTP method for this operation (e.g. `GET`, `post`). Case-insensitive;
1103    /// validated against the known-verb set during lowering.
1104    pub method: String,
1105    /// Sub-path relative to the parent RouteDslRest path.
1106    #[serde(default = "default_operation_path")]
1107    pub path: String,
1108    /// Optional unique identifier for this operation.
1109    #[serde(default)]
1110    pub operation_id: Option<String>,
1111    /// Endpoint URI to route matching requests to (e.g. `bean:userService`).
1112    #[serde(default)]
1113    pub to: Option<String>,
1114    /// Child sub-pipeline steps.
1115    #[serde(default)]
1116    pub steps: Vec<RouteDslStep>,
1117    /// Default content-type consumed by this operation.
1118    #[serde(default = "default_consumes")]
1119    pub consumes: String,
1120    /// Default content-type produced by this operation.
1121    #[serde(default = "default_produces")]
1122    pub produces: String,
1123    /// Expected success HTTP status code (e.g. 200, 201).
1124    #[serde(default)]
1125    pub success_status: Option<u16>,
1126    /// Optional request body schema.
1127    #[serde(default)]
1128    pub request_schema: Option<serde_json::Value>,
1129    /// Response definition.
1130    #[serde(default)]
1131    pub response: Option<RouteDslRestResponse>,
1132    /// Optional description of this operation.
1133    #[serde(default)]
1134    pub description: Option<String>,
1135    /// Additional operation parameters.
1136    #[serde(default)]
1137    pub parameters: BTreeMap<String, serde_json::Value>,
1138}
1139
1140/// Response definition for a REST operation.
1141#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
1142#[derive(Deserialize, Debug, Clone)]
1143#[serde(deny_unknown_fields)]
1144pub struct RouteDslRestResponse {
1145    /// Optional response description.
1146    #[serde(default)]
1147    pub description: Option<String>,
1148    /// Response body schema.
1149    #[serde(default)]
1150    pub schema: Option<serde_json::Value>,
1151    /// Response headers map.
1152    #[serde(default)]
1153    pub headers: BTreeMap<String, serde_json::Value>,
1154}
1155
1156fn default_rest_host() -> String {
1157    "0.0.0.0".to_string()
1158}
1159
1160fn default_rest_port() -> u16 {
1161    8080
1162}
1163
1164fn default_operation_path() -> String {
1165    "/".to_string()
1166}
1167
1168fn default_consumes() -> String {
1169    "application/json".to_string()
1170}
1171
1172fn default_produces() -> String {
1173    "application/json".to_string()
1174}
1175
1176#[cfg(test)]
1177mod tests {
1178    use super::*;
1179
1180    #[test]
1181    fn default_values() {
1182        assert!(default_true());
1183        assert_eq!(default_startup_order(), 1000);
1184        assert_eq!(default_initial_delay_ms(), 100);
1185        assert_eq!(default_multiplier(), 2.0);
1186        assert_eq!(default_max_delay_ms(), 10_000);
1187        assert_eq!(default_jitter_factor(), 0.0);
1188        assert_eq!(default_failure_threshold(), 5);
1189        assert_eq!(default_open_duration_ms(), 30_000);
1190        assert_eq!(default_split_aggregation(), "last_wins");
1191        assert_eq!(default_aggregate_strategy(), "collect_all");
1192        assert_eq!(default_multicast_aggregation(), "last_wins");
1193        assert_eq!(default_throttle_period_secs(), 1);
1194        assert_eq!(default_lb_strategy(), "round_robin");
1195        assert_eq!(default_uri_delimiter(), ",");
1196        assert_eq!(default_cache_size(), 1000);
1197        assert_eq!(default_max_iterations(), 1000);
1198        assert_eq!(default_rest_host(), "0.0.0.0");
1199        assert_eq!(default_rest_port(), 8080);
1200        assert_eq!(default_operation_path(), "/");
1201        assert_eq!(default_consumes(), "application/json");
1202        assert_eq!(default_produces(), "application/json");
1203    }
1204
1205    #[test]
1206    fn parse_minimal_route() {
1207        let yaml = r#"
1208routes:
1209  - id: r1
1210    from: direct:start
1211"#;
1212        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1213        assert_eq!(parsed.routes.len(), 1);
1214        assert_eq!(parsed.routes[0].id, "r1");
1215        assert_eq!(parsed.routes[0].from, "direct:start");
1216        assert!(parsed.routes[0].auto_startup);
1217        assert_eq!(parsed.routes[0].startup_order, 1000);
1218        assert!(parsed.routes[0].steps.is_empty());
1219    }
1220
1221    #[test]
1222    fn parse_route_with_concurrency() {
1223        let yaml = r#"
1224routes:
1225  - id: r2
1226    from: timer:tick
1227    concurrent: 4
1228"#;
1229        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1230        assert_eq!(parsed.routes[0].concurrent, Some(4));
1231        assert!(!parsed.routes[0].sequential);
1232    }
1233
1234    #[test]
1235    fn parse_route_with_error_handler() {
1236        let yaml = r#"
1237routes:
1238  - id: r3
1239    from: direct:in
1240    error_handler:
1241      dead_letter_channel: log:dlq
1242"#;
1243        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1244        let eh = parsed.routes[0].error_handler.as_ref().unwrap();
1245        assert_eq!(eh.dead_letter_channel.as_deref(), Some("log:dlq"));
1246    }
1247
1248    #[test]
1249    fn parse_route_with_circuit_breaker() {
1250        let yaml = r#"
1251routes:
1252  - id: r4
1253    from: direct:in
1254    circuit_breaker:
1255      failure_threshold: 3
1256      open_duration_ms: 5000
1257"#;
1258        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1259        let cb = parsed.routes[0].circuit_breaker.as_ref().unwrap();
1260        assert_eq!(cb.failure_threshold, 3);
1261        assert_eq!(cb.open_duration_ms, 5000);
1262    }
1263
1264    #[test]
1265    fn parse_circuit_breaker_defaults() {
1266        let yaml = r#"
1267routes:
1268  - id: r5
1269    from: direct:in
1270    circuit_breaker: {}
1271"#;
1272        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1273        let cb = parsed.routes[0].circuit_breaker.as_ref().unwrap();
1274        assert_eq!(cb.failure_threshold, 5);
1275        assert_eq!(cb.open_duration_ms, 30_000);
1276    }
1277
1278    #[test]
1279    fn parse_to_step() {
1280        let yaml = r#"
1281routes:
1282  - id: r6
1283    from: direct:start
1284    steps:
1285      - to: log:out
1286"#;
1287        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1288        assert_eq!(parsed.routes[0].steps.len(), 1);
1289    }
1290
1291    #[test]
1292    fn parse_delay_short_form() {
1293        let yaml = r#"
1294routes:
1295  - id: r7
1296    from: direct:start
1297    steps:
1298      - delay: 500
1299"#;
1300        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1301        match &parsed.routes[0].steps[0] {
1302            RouteDslStep::Delay(d) => match &d.delay {
1303                DelayBody::Short(ms) => assert_eq!(*ms, 500),
1304                _ => panic!("expected short form"),
1305            },
1306            _ => panic!("expected delay step"),
1307        }
1308    }
1309
1310    #[test]
1311    fn parse_delay_full_form() {
1312        let yaml = r#"
1313routes:
1314  - id: r8
1315    from: direct:start
1316    steps:
1317      - delay:
1318          delay_ms: 200
1319          dynamic_header: X-Delay
1320"#;
1321        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1322        match &parsed.routes[0].steps[0] {
1323            RouteDslStep::Delay(d) => match &d.delay {
1324                DelayBody::Full(cfg) => {
1325                    assert_eq!(cfg.delay_ms, 200);
1326                    assert_eq!(cfg.dynamic_header.as_deref(), Some("X-Delay"));
1327                }
1328                _ => panic!("expected full form"),
1329            },
1330            _ => panic!("expected delay step"),
1331        }
1332    }
1333
1334    #[test]
1335    fn parse_redelivery_policy_defaults() {
1336        let yaml = r#"
1337routes:
1338  - id: r9
1339    from: direct:in
1340    error_handler:
1341      retry:
1342        max_attempts: 3
1343"#;
1344        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1345        let retry = parsed.routes[0]
1346            .error_handler
1347            .as_ref()
1348            .unwrap()
1349            .retry
1350            .as_ref()
1351            .unwrap();
1352        assert_eq!(retry.max_attempts, 3);
1353        assert_eq!(retry.initial_delay_ms, 100);
1354        assert_eq!(retry.multiplier, 2.0);
1355        assert_eq!(retry.max_delay_ms, 10_000);
1356        assert_eq!(retry.jitter_factor, 0.0);
1357    }
1358
1359    #[test]
1360    fn parse_stream_cache_bool() {
1361        let yaml = r#"
1362routes:
1363  - id: r10
1364    from: direct:start
1365    steps:
1366      - stream_cache: true
1367"#;
1368        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1369        match &parsed.routes[0].steps[0] {
1370            RouteDslStep::StreamCache(s) => match &s.stream_cache {
1371                StreamCacheBody::Enabled(b) => assert!(*b),
1372                _ => panic!("expected enabled"),
1373            },
1374            _ => panic!("expected stream_cache"),
1375        }
1376    }
1377
1378    #[test]
1379    fn parse_stop_step() {
1380        let yaml = r#"
1381routes:
1382  - id: r11
1383    from: direct:start
1384    steps:
1385      - stop: true
1386"#;
1387        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1388        match &parsed.routes[0].steps[0] {
1389            RouteDslStep::Stop(s) => assert!(s.stop),
1390            _ => panic!("expected stop"),
1391        }
1392    }
1393
1394    #[test]
1395    fn parse_convert_body_to() {
1396        let yaml = r#"
1397routes:
1398  - id: r12
1399    from: direct:start
1400    steps:
1401      - convert_body_to: json
1402"#;
1403        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1404        match &parsed.routes[0].steps[0] {
1405            RouteDslStep::ConvertBodyTo(s) => assert_eq!(s.convert_body_to, "json"),
1406            _ => panic!("expected convert_body_to"),
1407        }
1408    }
1409
1410    #[test]
1411    fn parse_marshal_unmarshal() {
1412        let yaml = r#"
1413routes:
1414  - id: r13
1415    from: direct:start
1416    steps:
1417      - marshal: protobuf
1418      - unmarshal: protobuf
1419"#;
1420        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1421        assert_eq!(parsed.routes[0].steps.len(), 2);
1422    }
1423
1424    #[test]
1425    fn parse_bean_step() {
1426        let yaml = r#"
1427routes:
1428  - id: r14
1429    from: direct:start
1430    steps:
1431      - bean:
1432          name: myBean
1433          method: handle
1434"#;
1435        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1436        match &parsed.routes[0].steps[0] {
1437            RouteDslStep::Bean(b) => {
1438                assert_eq!(b.bean.name, "myBean");
1439                assert_eq!(b.bean.method, "handle");
1440            }
1441            _ => panic!("expected bean"),
1442        }
1443    }
1444
1445    #[test]
1446    fn parse_script_step() {
1447        let yaml = r#"
1448routes:
1449  - id: r15
1450    from: direct:start
1451    steps:
1452      - script:
1453          language: rhai
1454          source: "1 + 1"
1455"#;
1456        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1457        match &parsed.routes[0].steps[0] {
1458            RouteDslStep::Script(s) => {
1459                assert_eq!(s.script.language, "rhai");
1460                assert_eq!(s.script.source, "1 + 1");
1461            }
1462            _ => panic!("expected script"),
1463        }
1464    }
1465
1466    // --- Template AST tests (Phase 4) ---
1467
1468    #[test]
1469    fn parse_yaml_with_templates_and_templated_routes() {
1470        let yaml = r#"
1471routes:
1472  - id: r1
1473    from: direct:start
1474templates:
1475  - id: http-route
1476    parameters:
1477      - name: path
1478        default_value: /api
1479        description: The REST path
1480    routes:
1481      - id: "instance-route"
1482        from: "rest:{{path}}"
1483        steps:
1484          - to: "log:info"
1485templated_routes:
1486  - route_template_ref: http-route
1487    route_id: my-http-route
1488    parameters:
1489      path: /users
1490"#;
1491        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1492        assert_eq!(parsed.routes.len(), 1);
1493        assert_eq!(parsed.templates.len(), 1);
1494        assert_eq!(parsed.templated_routes.len(), 1);
1495
1496        let tpl = &parsed.templates[0];
1497        assert_eq!(tpl.id, "http-route");
1498        assert_eq!(tpl.parameters.len(), 1);
1499        assert_eq!(tpl.parameters[0].name, "path");
1500        assert_eq!(tpl.parameters[0].default_value.as_deref(), Some("/api"));
1501        assert_eq!(
1502            tpl.parameters[0].description.as_deref(),
1503            Some("The REST path")
1504        );
1505
1506        let tr = &parsed.templated_routes[0];
1507        assert_eq!(tr.route_template_ref, "http-route");
1508        assert_eq!(tr.route_id.as_deref(), Some("my-http-route"));
1509        assert_eq!(tr.parameters["path"], "/users");
1510    }
1511
1512    #[test]
1513    fn parse_yaml_backward_compat_no_templates() {
1514        let yaml = r#"
1515routes:
1516  - id: r1
1517    from: direct:start
1518    steps:
1519      - to: log:info
1520"#;
1521        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1522        assert_eq!(parsed.routes.len(), 1);
1523        assert!(parsed.templates.is_empty());
1524        assert!(parsed.templated_routes.is_empty());
1525    }
1526
1527    #[test]
1528    fn parse_yaml_template_with_empty_parameters() {
1529        let yaml = r#"
1530routes: []
1531templates:
1532  - id: simple-tpl
1533    routes:
1534      - id: simple-route
1535        from: timer:tick
1536"#;
1537        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1538        assert_eq!(parsed.templates.len(), 1);
1539        assert!(parsed.templates[0].parameters.is_empty());
1540    }
1541
1542    #[test]
1543    fn parse_yaml_templated_routes_with_defaults() {
1544        let yaml = r#"
1545routes: []
1546templated_routes:
1547  - route_template_ref: my-tpl
1548"#;
1549        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1550        assert_eq!(parsed.templated_routes.len(), 1);
1551        let tr = &parsed.templated_routes[0];
1552        assert_eq!(tr.route_template_ref, "my-tpl");
1553        assert!(tr.route_id.is_none());
1554        assert!(tr.parameters.is_empty());
1555    }
1556
1557    // ── REST DSL tests (Phase 1) ──
1558
1559    #[test]
1560    fn parse_rest_block_basic() {
1561        let yaml = r#"
1562rest:
1563  - host: 0.0.0.0
1564    port: 8080
1565    path: /users
1566    operations:
1567      - method: GET
1568        path: /{id}
1569        operation_id: getUser
1570        to: bean:userService
1571        produces: application/json
1572      - method: POST
1573        operation_id: createUser
1574        consumes: application/json
1575        produces: application/json
1576        success_status: 201
1577        to: bean:createUser
1578"#;
1579        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1580        assert_eq!(parsed.rest.len(), 1);
1581        let rest = &parsed.rest[0];
1582        assert_eq!(rest.host, "0.0.0.0");
1583        assert_eq!(rest.port, 8080);
1584        assert_eq!(rest.path, "/users");
1585        assert_eq!(rest.operations.len(), 2);
1586
1587        let get_op = &rest.operations[0];
1588        assert_eq!(get_op.method, "GET");
1589        assert_eq!(get_op.path, "/{id}");
1590        assert_eq!(get_op.operation_id.as_deref(), Some("getUser"));
1591        assert_eq!(get_op.to.as_deref(), Some("bean:userService"));
1592        assert_eq!(get_op.produces, "application/json");
1593
1594        let post_op = &rest.operations[1];
1595        assert_eq!(post_op.method, "POST");
1596        assert_eq!(post_op.operation_id.as_deref(), Some("createUser"));
1597        assert_eq!(post_op.consumes, "application/json");
1598        assert_eq!(post_op.produces, "application/json");
1599        assert_eq!(post_op.success_status, Some(201));
1600        assert_eq!(post_op.to.as_deref(), Some("bean:createUser"));
1601    }
1602
1603    #[test]
1604    fn parse_rest_defaults() {
1605        let yaml = r#"
1606rest:
1607  - path: /api
1608    operations:
1609      - method: GET
1610        to: direct:handler
1611"#;
1612        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1613        let rest = &parsed.rest[0];
1614        assert_eq!(rest.host, "0.0.0.0");
1615        assert_eq!(rest.port, 8080);
1616        assert_eq!(rest.path, "/api");
1617        let op = &rest.operations[0];
1618        assert_eq!(op.method, "GET");
1619        assert_eq!(op.path, "/");
1620        assert_eq!(op.consumes, "application/json");
1621        assert_eq!(op.produces, "application/json");
1622        assert!(op.success_status.is_none());
1623        assert!(op.response.is_none());
1624        assert!(op.steps.is_empty());
1625    }
1626
1627    #[test]
1628    fn parse_rest_empty() {
1629        let yaml = r#"
1630routes:
1631  - id: r1
1632    from: direct:start
1633"#;
1634        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1635        assert!(parsed.rest.is_empty());
1636    }
1637
1638    #[test]
1639    fn parse_rest_multiple_blocks() {
1640        let yaml = r#"
1641rest:
1642  - path: /users
1643    operations:
1644      - method: GET
1645        to: direct:handleUsers
1646  - path: /orders
1647    operations:
1648      - method: GET
1649        to: direct:handleOrders
1650"#;
1651        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1652        assert_eq!(parsed.rest.len(), 2);
1653        assert_eq!(parsed.rest[0].path, "/users");
1654        assert_eq!(parsed.rest[1].path, "/orders");
1655    }
1656
1657    #[test]
1658    fn parse_rest_backward_compat_no_rest() {
1659        let yaml = r#"
1660routes:
1661  - id: r1
1662    from: direct:start
1663    steps:
1664      - to: log:info
1665"#;
1666        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1667        assert_eq!(parsed.routes.len(), 1);
1668        assert!(parsed.rest.is_empty());
1669    }
1670
1671    #[test]
1672    fn parse_rest_and_routes_together() {
1673        let yaml = r#"
1674routes:
1675  - id: r1
1676    from: direct:start
1677    steps:
1678      - to: log:info
1679rest:
1680  - host: localhost
1681    port: 3000
1682    path: /api
1683    operations:
1684      - method: GET
1685        to: direct:apiHandler
1686"#;
1687        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1688        assert_eq!(parsed.routes.len(), 1);
1689        assert_eq!(parsed.rest.len(), 1);
1690        assert_eq!(parsed.rest[0].host, "localhost");
1691        assert_eq!(parsed.rest[0].port, 3000);
1692        assert_eq!(parsed.rest[0].path, "/api");
1693        assert_eq!(parsed.rest[0].operations.len(), 1);
1694        assert_eq!(parsed.rest[0].operations[0].method, "GET");
1695    }
1696
1697    #[test]
1698    fn parse_rest_with_response() {
1699        let yaml = r#"
1700rest:
1701  - path: /api
1702    operations:
1703      - method: GET
1704        path: /items
1705        to: direct:listItems
1706        response:
1707          description: A list of items
1708          schema:
1709            type: array
1710            items:
1711              type: string
1712          headers:
1713            X-Total-Count:
1714              type: integer
1715"#;
1716        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1717        let rest = &parsed.rest[0];
1718        let op = &rest.operations[0];
1719        let resp = op.response.as_ref().unwrap();
1720        assert_eq!(resp.description.as_deref(), Some("A list of items"));
1721        assert!(resp.schema.is_some());
1722        assert!(resp.headers.contains_key("X-Total-Count"));
1723    }
1724
1725    #[test]
1726    fn parse_rest_with_request_schema_and_parameters() {
1727        let yaml = r#"
1728rest:
1729  - path: /api
1730    operations:
1731      - method: POST
1732        path: /create
1733        to: direct:create
1734        request_schema:
1735          type: object
1736          properties:
1737            name:
1738              type: string
1739        parameters:
1740          limit:
1741            type: integer
1742          offset:
1743            type: integer
1744"#;
1745        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1746        let op = &parsed.rest[0].operations[0];
1747        assert!(op.request_schema.is_some());
1748        assert!(op.parameters.contains_key("limit"));
1749        assert!(op.parameters.contains_key("offset"));
1750    }
1751
1752    #[test]
1753    fn circuit_breaker_rejects_typo_field() {
1754        let yaml = r#"
1755failure_threshhold: 5
1756open_duration_ms: 10000
1757"#;
1758        let result: Result<RouteDslCircuitBreaker, _> = serde_yml::from_str(yaml);
1759        assert!(
1760            result.is_err(),
1761            "typo 'failure_threshhold' must be rejected by deny_unknown_fields"
1762        );
1763    }
1764
1765    #[test]
1766    fn parse_rest_multiple_verbs_in_one_block() {
1767        let yaml = r#"
1768rest:
1769  - path: /api
1770    operations:
1771      - method: GET
1772        path: /items
1773        to: direct:getItems
1774      - method: POST
1775        path: /items
1776        to: direct:createItem
1777      - method: PUT
1778        path: /items/{id}
1779        to: direct:updateItem
1780      - method: DELETE
1781        path: /items/{id}
1782        to: direct:deleteItem
1783"#;
1784        let parsed: RouteDslRoutes = serde_yml::from_str(yaml).unwrap();
1785        let ops = &parsed.rest[0].operations;
1786        assert_eq!(ops.len(), 4);
1787        assert_eq!(ops[0].method, "GET");
1788        assert_eq!(ops[1].method, "POST");
1789        assert_eq!(ops[2].method, "PUT");
1790        assert_eq!(ops[3].method, "DELETE");
1791    }
1792}
1793
1794#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
1795#[derive(Deserialize, Debug, Clone)]
1796#[serde(deny_unknown_fields)]
1797pub struct DynamicRouterStep {
1798    pub dynamic_router: DynamicRouterData,
1799}
1800
1801#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
1802#[derive(Deserialize, Debug, Clone)]
1803#[serde(deny_unknown_fields)]
1804pub struct DynamicRouterData {
1805    #[serde(default)]
1806    pub simple: Option<String>,
1807    #[serde(default)]
1808    pub rhai: Option<String>,
1809    #[serde(default)]
1810    pub language: Option<String>,
1811    #[serde(default)]
1812    pub source: Option<String>,
1813    #[serde(default = "default_uri_delimiter")]
1814    pub uri_delimiter: String,
1815    #[serde(default = "default_cache_size")]
1816    pub cache_size: i32,
1817    #[serde(default)]
1818    pub ignore_invalid_endpoints: bool,
1819    #[serde(default = "default_max_iterations")]
1820    pub max_iterations: usize,
1821}
1822
1823fn default_uri_delimiter() -> String {
1824    ",".to_string()
1825}
1826
1827fn default_cache_size() -> i32 {
1828    1000
1829}
1830
1831fn default_max_iterations() -> usize {
1832    1000
1833}
1834
1835#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
1836#[derive(Deserialize, Debug, Clone)]
1837#[serde(deny_unknown_fields)]
1838pub struct RoutingSlipStep {
1839    pub routing_slip: RoutingSlipData,
1840}
1841
1842#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
1843#[derive(Deserialize, Debug, Clone)]
1844#[serde(deny_unknown_fields)]
1845pub struct RoutingSlipData {
1846    #[serde(default)]
1847    pub simple: Option<String>,
1848    #[serde(default)]
1849    pub rhai: Option<String>,
1850    #[serde(default)]
1851    pub language: Option<String>,
1852    #[serde(default)]
1853    pub source: Option<String>,
1854    #[serde(default = "default_uri_delimiter")]
1855    pub uri_delimiter: String,
1856    #[serde(default = "default_cache_size")]
1857    pub cache_size: i32,
1858    #[serde(default)]
1859    pub ignore_invalid_endpoints: bool,
1860}
1861
1862#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
1863#[derive(Deserialize, Debug, Clone)]
1864#[serde(deny_unknown_fields)]
1865pub struct RecipientListStep {
1866    pub recipient_list: RecipientListData,
1867}
1868
1869#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
1870#[derive(Deserialize, Debug, Clone)]
1871#[serde(deny_unknown_fields)]
1872pub struct RecipientListData {
1873    #[serde(default)]
1874    pub simple: Option<String>,
1875    #[serde(default)]
1876    pub rhai: Option<String>,
1877    #[serde(default)]
1878    pub language: Option<String>,
1879    #[serde(default)]
1880    pub source: Option<String>,
1881    #[serde(default = "default_uri_delimiter")]
1882    pub delimiter: String,
1883    #[serde(default)]
1884    pub parallel: bool,
1885    #[serde(default)]
1886    pub parallel_limit: Option<usize>,
1887    #[serde(default)]
1888    pub stop_on_exception: bool,
1889    #[serde(default)]
1890    pub strategy: Option<String>,
1891}
1892
1893#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
1894#[derive(Deserialize, Debug, Clone)]
1895#[serde(deny_unknown_fields)]
1896pub struct BeanStep {
1897    pub bean: BeanStepData,
1898}
1899
1900#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
1901#[derive(Deserialize, Debug, Clone)]
1902#[serde(deny_unknown_fields)]
1903pub struct BeanStepData {
1904    pub name: String,
1905    pub method: String,
1906}
1907
1908// ── Resequence step (Phase 3) ──
1909
1910#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
1911#[derive(Deserialize, Debug, Clone)]
1912#[serde(deny_unknown_fields)]
1913pub struct ResequenceStep {
1914    pub resequence: ResequenceData,
1915}
1916
1917#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
1918#[derive(Deserialize, Debug, Clone)]
1919#[serde(deny_unknown_fields)]
1920pub struct ResequenceData {
1921    #[serde(default)]
1922    pub batch: Option<ResequenceBatchYaml>,
1923    #[serde(default)]
1924    pub stream: Option<ResequenceStreamYaml>,
1925}
1926
1927#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
1928#[derive(Deserialize, Debug, Clone)]
1929#[serde(deny_unknown_fields)]
1930pub struct ResequenceBatchYaml {
1931    pub correlation: String,
1932    pub sort: String,
1933    pub completion: ResequenceCompletionYaml,
1934}
1935
1936#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
1937#[derive(Deserialize, Debug, Clone)]
1938#[serde(deny_unknown_fields)]
1939pub struct ResequenceCompletionYaml {
1940    #[serde(default)]
1941    pub size: Option<usize>,
1942    #[serde(default)]
1943    pub timeout: Option<u64>,
1944    #[serde(default)]
1945    pub size_or_timeout: Option<Vec<u64>>,
1946}
1947
1948// NOTE: ResequenceStreamYaml intentionally omitted — stream resequencing is
1949// not yet implemented (Task 3). Scaffolding will be added when Stream is ready.
1950
1951#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
1952#[derive(Deserialize, Debug, Clone)]
1953#[serde(deny_unknown_fields)]
1954pub struct ResequenceStreamYaml {
1955    pub sequence: String,
1956    #[serde(default = "default_stream_capacity")]
1957    pub capacity: usize,
1958    #[serde(default = "default_gap_timeout")]
1959    pub gap_timeout: u64,
1960    #[serde(default)]
1961    pub on_gap: StreamGapPolicyYaml,
1962    #[serde(default)]
1963    pub on_capacity_exceeded: StreamCapacityPolicyYaml,
1964    #[serde(default)]
1965    pub dedup: bool,
1966}
1967
1968fn default_stream_capacity() -> usize {
1969    1000
1970}
1971
1972fn default_gap_timeout() -> u64 {
1973    5000
1974}
1975
1976/// Stream resequencer gap policy (YAML representation).
1977#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
1978#[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)]
1979#[serde(rename_all = "snake_case")]
1980pub enum StreamGapPolicyYaml {
1981    #[default]
1982    EmitPartial,
1983    DropAndLog,
1984}
1985
1986/// Stream resequencer capacity policy (YAML representation).
1987#[cfg_attr(feature = "schema", derive(schemars::JsonSchema, ts_rs::TS))]
1988#[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)]
1989#[serde(rename_all = "snake_case")]
1990pub enum StreamCapacityPolicyYaml {
1991    #[default]
1992    LogAndDrop,
1993    DropOldest,
1994}