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