Skip to main content

camel_dsl/
model.rs

1pub use camel_api::{LanguageExpressionDef, StreamSplitConfig, ValueSourceDef};
2
3#[derive(Default)]
4pub struct SecurityCompileContext {
5    pub authenticator: Option<std::sync::Arc<dyn camel_auth::TokenAuthenticator>>,
6    pub registry: Option<std::sync::Arc<camel_auth::SecurityPolicyRegistry>>,
7    pub evaluator_registry: Option<std::sync::Arc<camel_auth::PermissionEvaluatorRegistry>>,
8}
9
10impl Clone for SecurityCompileContext {
11    fn clone(&self) -> Self {
12        Self {
13            authenticator: self.authenticator.clone(),
14            registry: self.registry.clone(),
15            evaluator_registry: self.evaluator_registry.clone(),
16        }
17    }
18}
19
20impl SecurityCompileContext {
21    pub fn new(
22        authenticator: Option<std::sync::Arc<dyn camel_auth::TokenAuthenticator>>,
23        registry: Option<std::sync::Arc<camel_auth::SecurityPolicyRegistry>>,
24    ) -> Self {
25        Self {
26            authenticator,
27            registry,
28            evaluator_registry: None,
29        }
30    }
31
32    pub fn with_evaluator_registry(
33        mut self,
34        registry: std::sync::Arc<camel_auth::PermissionEvaluatorRegistry>,
35    ) -> Self {
36        self.evaluator_registry = Some(registry);
37        self
38    }
39
40    pub fn with_security_policy_registry(
41        mut self,
42        registry: std::sync::Arc<camel_auth::SecurityPolicyRegistry>,
43    ) -> Self {
44        self.registry = Some(registry);
45        self
46    }
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub enum DeclarativeConcurrency {
51    Sequential,
52    Concurrent { max: Option<usize> },
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct DeclarativeCircuitBreaker {
57    pub failure_threshold: u32,
58    pub open_duration_ms: u64,
59}
60
61#[derive(Debug, Clone, PartialEq)]
62pub enum DeclarativeSecurityPolicy {
63    Roles {
64        roles: Vec<String>,
65        all_required: bool,
66        trust_upstream_principal: bool,
67    },
68    Scopes {
69        scopes: Vec<String>,
70        all_required: bool,
71        trust_upstream_principal: bool,
72    },
73    Ref {
74        name: String,
75    },
76    /// WASM security policy reference. The `path` field is the registry name
77    /// of a policy registered via `[security.policies.wasm.<name>]` in Camel.toml.
78    /// Per-route `config` is not supported (registry is instance-based, not
79    /// factory-based โ€” see ADR-0014 ยง4 closure bd rc-0te).
80    Wasm {
81        /// Registry name of the WASM policy (from `[security.policies.wasm.<name>]` in Camel.toml).
82        path: String,
83        /// Reserved โ€” must be empty. Use Camel.toml `[security.policies.wasm.<name>.config]` instead.
84        config: std::collections::HashMap<String, String>,
85    },
86    Permission {
87        policy: String,
88        resource: camel_auth::PermissionValueSource,
89        action: camel_auth::PermissionValueSource,
90        scopes: Vec<String>,
91        context: camel_auth::PermissionContextConfig,
92        cache_ttl_secs: Option<u64>,
93        cache_negative_ttl_secs: Option<u64>,
94    },
95}
96
97#[derive(Debug, Clone, PartialEq)]
98pub struct DeclarativeRedeliveryPolicy {
99    pub max_attempts: u32,
100    pub initial_delay_ms: u64,
101    pub multiplier: f64,
102    pub max_delay_ms: u64,
103    pub jitter_factor: f64,
104    pub handled_by: Option<String>,
105}
106
107#[derive(Debug, Clone, PartialEq)]
108pub struct DeclarativeOnException {
109    pub kind: Option<String>,
110    pub message_contains: Option<String>,
111    pub retry: Option<DeclarativeRedeliveryPolicy>,
112    pub steps: Vec<DeclarativeStep>,
113    pub handled: Option<bool>,
114    pub continued: Option<bool>,
115}
116
117#[derive(Debug, Clone, PartialEq, Default)]
118pub struct DeclarativeErrorHandler {
119    pub dead_letter_channel: Option<String>,
120    pub retry: Option<DeclarativeRedeliveryPolicy>,
121    pub on_exceptions: Option<Vec<DeclarativeOnException>>,
122    pub use_original_message: bool,
123}
124
125#[derive(Debug, Clone)]
126pub struct DeclarativeRoute {
127    pub from: String,
128    pub route_id: String,
129    pub auto_startup: bool,
130    pub startup_order: i32,
131    pub concurrency: Option<DeclarativeConcurrency>,
132    pub error_handler: Option<DeclarativeErrorHandler>,
133    pub circuit_breaker: Option<DeclarativeCircuitBreaker>,
134    pub security_policy: Option<DeclarativeSecurityPolicy>,
135    pub unit_of_work: Option<camel_api::UnitOfWorkConfig>,
136    pub steps: Vec<DeclarativeStep>,
137}
138
139#[derive(Debug, Clone, PartialEq, Eq)]
140pub struct ToStepDef {
141    pub uri: String,
142}
143
144impl ToStepDef {
145    pub fn new(uri: impl Into<String>) -> Self {
146        Self { uri: uri.into() }
147    }
148}
149
150#[derive(Debug, Clone, PartialEq, Eq)]
151pub enum LogLevelDef {
152    Trace,
153    Debug,
154    Info,
155    Warn,
156    Error,
157}
158
159// Note: `Eq` is not derived because `ValueSourceDef` contains `serde_json::Value`
160// which does not implement `Eq` (due to floating-point fields).
161#[derive(Debug, Clone, PartialEq)]
162pub struct LogStepDef {
163    pub message: ValueSourceDef,
164    pub level: LogLevelDef,
165}
166
167impl LogStepDef {
168    pub fn info(message: impl Into<String>) -> Self {
169        Self {
170            message: ValueSourceDef::Literal(serde_json::Value::String(message.into())),
171            level: LogLevelDef::Info,
172        }
173    }
174}
175
176#[derive(Debug, Clone, PartialEq)]
177pub struct SetHeaderStepDef {
178    pub key: String,
179    pub value: ValueSourceDef,
180}
181
182impl SetHeaderStepDef {
183    pub fn literal(key: impl Into<String>, value: impl Into<String>) -> Self {
184        Self {
185            key: key.into(),
186            value: ValueSourceDef::Literal(serde_json::Value::String(value.into())),
187        }
188    }
189}
190
191#[derive(Debug, Clone, PartialEq)]
192pub struct SetPropertyStepDef {
193    pub key: String,
194    pub value: ValueSourceDef,
195}
196
197impl SetPropertyStepDef {
198    pub fn literal(key: impl Into<String>, value: impl Into<String>) -> Self {
199        Self {
200            key: key.into(),
201            value: ValueSourceDef::Literal(serde_json::Value::String(value.into())),
202        }
203    }
204}
205
206#[derive(Debug, Clone, PartialEq)]
207pub struct SetBodyStepDef {
208    pub value: ValueSourceDef,
209}
210
211#[derive(Debug, Clone, PartialEq)]
212pub struct FilterStepDef {
213    pub predicate: LanguageExpressionDef,
214    pub steps: Vec<DeclarativeStep>,
215}
216
217#[derive(Debug, Clone, PartialEq, Eq)]
218pub struct FunctionStepDef {
219    pub runtime: String,
220    pub source: String,
221    pub timeout_ms: Option<u64>,
222}
223
224#[derive(Debug, Clone, PartialEq)]
225pub struct WhenStepDef {
226    pub predicate: LanguageExpressionDef,
227    pub steps: Vec<DeclarativeStep>,
228}
229
230#[derive(Debug, Clone, PartialEq)]
231pub struct ChoiceStepDef {
232    pub whens: Vec<WhenStepDef>,
233    pub otherwise: Option<Vec<DeclarativeStep>>,
234}
235
236#[derive(Debug, Clone, PartialEq)]
237pub enum SplitExpressionDef {
238    BodyLines,
239    BodyJsonArray,
240    Language(LanguageExpressionDef),
241    Stream(StreamSplitConfig),
242}
243
244#[derive(Debug, Clone, PartialEq, Eq)]
245pub enum SplitAggregationDef {
246    LastWins,
247    CollectAll,
248    Original,
249}
250
251#[derive(Debug, Clone, PartialEq)]
252pub struct SplitStepDef {
253    pub expression: SplitExpressionDef,
254    pub aggregation: SplitAggregationDef,
255    pub parallel: bool,
256    pub parallel_limit: Option<usize>,
257    pub stop_on_exception: bool,
258    pub steps: Vec<DeclarativeStep>,
259}
260
261#[derive(Debug, Clone, PartialEq, Eq)]
262pub enum AggregateStrategyDef {
263    CollectAll,
264}
265
266#[derive(Debug, Clone, PartialEq)]
267pub struct AggregateStepDef {
268    pub header: String,
269    pub correlation_key: Option<String>,
270    pub completion_size: Option<usize>,
271    pub completion_timeout_ms: Option<u64>,
272    pub completion_predicate: Option<LanguageExpressionDef>,
273    pub strategy: AggregateStrategyDef,
274    pub max_buckets: Option<usize>,
275    pub bucket_ttl_ms: Option<u64>,
276    pub force_completion_on_stop: Option<bool>,
277    pub discard_on_timeout: Option<bool>,
278}
279
280#[derive(Debug, Clone, PartialEq, Eq)]
281pub struct WireTapStepDef {
282    pub uri: String,
283}
284
285#[derive(Debug, Clone, PartialEq, Eq)]
286pub struct BeanStepDef {
287    pub name: String,
288    pub method: String,
289}
290
291impl BeanStepDef {
292    pub fn new(name: impl Into<String>, method: impl Into<String>) -> Self {
293        Self {
294            name: name.into(),
295            method: method.into(),
296        }
297    }
298}
299
300#[derive(Debug, Clone, PartialEq, Eq, Default)]
301pub enum ThrottleStrategyDef {
302    #[default]
303    Delay,
304    Reject,
305    Drop,
306}
307
308#[derive(Debug, Clone, PartialEq)]
309pub struct ThrottleStepDef {
310    pub max_requests: usize,
311    pub period_ms: u64,
312    pub strategy: ThrottleStrategyDef,
313    pub steps: Vec<DeclarativeStep>,
314}
315
316#[derive(Debug, Clone, PartialEq, Eq, Default)]
317pub enum LoadBalanceStrategyDef {
318    #[default]
319    RoundRobin,
320    Random,
321    Failover,
322    Weighted {
323        distribution_ratio: String,
324    },
325}
326
327#[derive(Debug, Clone, PartialEq)]
328pub struct LoadBalanceStepDef {
329    pub strategy: LoadBalanceStrategyDef,
330    pub steps: Vec<DeclarativeStep>,
331}
332
333#[derive(Debug, Clone, PartialEq, Eq)]
334pub struct DynamicRouterStepDef {
335    pub expression: LanguageExpressionDef,
336    pub uri_delimiter: String,
337    pub cache_size: i32,
338    pub ignore_invalid_endpoints: bool,
339    pub max_iterations: usize,
340}
341
342#[derive(Debug, Clone, PartialEq, Eq)]
343pub struct RoutingSlipStepDef {
344    pub expression: LanguageExpressionDef,
345    pub uri_delimiter: String,
346    pub cache_size: i32,
347    pub ignore_invalid_endpoints: bool,
348}
349
350#[derive(Debug, Clone, PartialEq, Eq)]
351pub struct RecipientListStepDef {
352    pub expression: LanguageExpressionDef,
353    pub delimiter: String,
354    pub parallel: bool,
355    pub parallel_limit: Option<usize>,
356    pub stop_on_exception: bool,
357    pub aggregation: MulticastAggregationDef,
358}
359
360#[derive(Debug, Clone, PartialEq, Eq)]
361pub enum MulticastAggregationDef {
362    LastWins,
363    CollectAll,
364    Original,
365}
366
367#[derive(Debug, Clone, PartialEq)]
368pub struct MulticastStepDef {
369    pub steps: Vec<DeclarativeStep>,
370    pub parallel: bool,
371    pub parallel_limit: Option<usize>,
372    pub stop_on_exception: bool,
373    pub timeout_ms: Option<u64>,
374    pub aggregation: MulticastAggregationDef,
375}
376
377#[derive(Debug, Clone, PartialEq, Eq)]
378pub struct ScriptStepDef {
379    pub expression: LanguageExpressionDef,
380}
381
382#[derive(Debug, Clone, PartialEq, Eq)]
383pub enum BodyTypeDef {
384    Text,
385    Json,
386    Bytes,
387    Xml,
388    Empty,
389}
390
391#[derive(Debug, Clone, PartialEq)]
392pub struct DataFormatDef {
393    pub format: String,
394    /// Optional JSON Schema for request-body validation (REST DSL
395    /// `request_schema`). When present, the compiled UnmarshalService is
396    /// wrapped with a `JsonSchemaValidateService`.
397    pub schema: Option<serde_json::Value>,
398}
399
400#[derive(Debug, Clone, PartialEq, Eq)]
401pub struct DelayStepDef {
402    pub delay_ms: u64,
403    pub dynamic_header: Option<String>,
404}
405
406#[derive(Debug, Clone, PartialEq)]
407pub struct LoopStepDef {
408    pub count: Option<usize>,
409    pub while_predicate: Option<LanguageExpressionDef>,
410    pub steps: Vec<DeclarativeStep>,
411}
412
413#[derive(Debug, Clone, PartialEq, Eq)]
414pub struct StreamCacheStepDef {
415    pub threshold: Option<usize>,
416}
417
418#[derive(Debug, Clone, PartialEq, Eq)]
419pub struct ValidateStepDef {
420    pub predicate: LanguageExpressionDef,
421}
422
423/// Claim Check EIP step definition.
424///
425/// Stashes/retrieves the message body from a `ClaimCheckRepository` by key.
426#[derive(Debug, Clone, PartialEq, Eq)]
427pub struct ClaimCheckStepDef {
428    /// Name of the registered `ClaimCheckRepository` (e.g. `"memory"`).
429    pub repository: String,
430    /// Operation: "set", "get", "get_and_remove", "push", "pop".
431    pub operation: String,
432    /// Expression that extracts the claim-check key from the exchange.
433    pub key: LanguageExpressionDef,
434    /// Optional filter string for selective merge-back during checkout operations.
435    pub filter: Option<String>,
436}
437
438/// Idempotent Consumer EIP step definition.
439///
440/// Wraps a child sub-pipeline that runs only when the exchange's message-id
441/// is NOT already present in the named `repository`. See ADR-0023.
442#[derive(Debug, Clone, PartialEq)]
443pub struct IdempotentConsumerStepDef {
444    /// Name of the registered `IdempotentRepository` (e.g. `"memory"`).
445    pub repository: String,
446    /// Expression that extracts the message-id key from the exchange.
447    pub expression: LanguageExpressionDef,
448    /// Child sub-pipeline executed on first-time (non-duplicate) exchanges.
449    pub steps: Vec<DeclarativeStep>,
450    /// If `true`, reserve the key in the repository BEFORE running the child
451    /// (eager mode). Default `false` (lazy: add only after the child completes).
452    pub eager: Option<bool>,
453    /// If `true` and `eager` is `true`, remove the key from the repository
454    /// when the child returns `Failed`. Default `false`.
455    pub remove_on_failure: Option<bool>,
456}
457
458/// Sampling EIP step definition.
459///
460/// Passes 1 of every N exchanges (counter-based, deterministic).
461#[derive(Debug, Clone, PartialEq, Eq)]
462pub struct SamplingStepDef {
463    /// Sampling period: 1 of every `period` exchanges passes.
464    pub period: usize,
465}
466
467/// Sort EIP step definition.
468///
469/// Orders a body collection by extracting a sort key from each element
470/// via a language expression.
471#[derive(Debug, Clone, PartialEq, Eq)]
472pub struct SortStepDef {
473    /// Expression that produces the sort key for each element.
474    pub expression: LanguageExpressionDef,
475    /// Reverse (descending) sort when true. Default false (ascending).
476    pub reverse: bool,
477}
478
479/// Resequence EIP step definition (Phase 3).
480///
481/// Supports batch and stream modes.
482#[derive(Debug, Clone, PartialEq, Eq)]
483pub struct ResequenceStepDef {
484    pub mode: ResequenceModeDef,
485}
486
487/// Resequence mode selection โ€” batch or stream.
488#[derive(Debug, Clone, PartialEq, Eq)]
489pub enum ResequenceModeDef {
490    Batch {
491        correlation: String,
492        sort: String,
493        completion: camel_api::resequencer::BatchCompletion,
494    },
495    Stream {
496        sequence: String,
497        capacity: usize,
498        gap_timeout: u64,
499        on_gap: camel_api::resequencer::GapPolicy,
500        on_capacity_exceeded: camel_api::resequencer::CapacityPolicy,
501        dedup: bool,
502    },
503}
504
505#[derive(Debug, Clone, PartialEq, Eq)]
506pub struct EnrichStepDef {
507    pub uri: String,
508    pub strategy: Option<String>,
509    pub timeout_ms: Option<u64>,
510}
511
512#[derive(Debug, Clone, PartialEq)]
513pub struct DoTryCatchClauseDef {
514    pub exception: Option<Vec<String>>,
515    pub when: Option<LanguageExpressionDef>,
516    pub on_when: Option<LanguageExpressionDef>,
517    pub disposition: camel_api::error_handler::ExceptionDisposition,
518    pub steps: Vec<DeclarativeStep>,
519}
520
521#[derive(Debug, Clone, PartialEq)]
522pub struct DoTryFinallyDef {
523    pub on_when: Option<LanguageExpressionDef>,
524    pub steps: Vec<DeclarativeStep>,
525}
526
527#[derive(Debug, Clone, PartialEq)]
528pub enum DeclarativeStep {
529    To(ToStepDef),
530    SetHeader(SetHeaderStepDef),
531    SetProperty(SetPropertyStepDef),
532    SetBody(SetBodyStepDef),
533    ConvertBodyTo(BodyTypeDef),
534    DynamicRouter(DynamicRouterStepDef),
535    Filter(FilterStepDef),
536    Function(FunctionStepDef),
537    LoadBalance(LoadBalanceStepDef),
538    Log(LogStepDef),
539    Choice(ChoiceStepDef),
540    Split(SplitStepDef),
541    Aggregate(AggregateStepDef),
542    WireTap(WireTapStepDef),
543    Multicast(MulticastStepDef),
544    RoutingSlip(RoutingSlipStepDef),
545    RecipientList(RecipientListStepDef),
546    Stop,
547    Throttle(ThrottleStepDef),
548    Script(ScriptStepDef),
549    StreamCache(StreamCacheStepDef),
550    Marshal(DataFormatDef),
551    Unmarshal(DataFormatDef),
552    Validate(ValidateStepDef),
553    Bean(BeanStepDef),
554    Delay(DelayStepDef),
555    Loop(LoopStepDef),
556    Enrich(EnrichStepDef),
557    PollEnrich(EnrichStepDef),
558    IdempotentConsumer(IdempotentConsumerStepDef),
559    ClaimCheck(ClaimCheckStepDef),
560    Sampling(SamplingStepDef),
561    Sort(SortStepDef),
562    Resequence(ResequenceStepDef),
563    DoTry {
564        steps: Vec<DeclarativeStep>,
565        catch: Vec<DoTryCatchClauseDef>,
566        finally: Option<DoTryFinallyDef>,
567    },
568}
569
570impl DeclarativeStep {
571    pub fn kind(&self) -> crate::contract::DeclarativeStepKind {
572        match self {
573            DeclarativeStep::To(_) => crate::contract::DeclarativeStepKind::To,
574            DeclarativeStep::Log(_) => crate::contract::DeclarativeStepKind::Log,
575            DeclarativeStep::SetHeader(_) => crate::contract::DeclarativeStepKind::SetHeader,
576            DeclarativeStep::SetProperty(_) => crate::contract::DeclarativeStepKind::SetProperty,
577            DeclarativeStep::SetBody(_) => crate::contract::DeclarativeStepKind::SetBody,
578            DeclarativeStep::ConvertBodyTo(_) => {
579                crate::contract::DeclarativeStepKind::ConvertBodyTo
580            }
581            DeclarativeStep::DynamicRouter(_) => {
582                crate::contract::DeclarativeStepKind::DynamicRouter
583            }
584            DeclarativeStep::Filter(_) => crate::contract::DeclarativeStepKind::Filter,
585            DeclarativeStep::Function(_) => crate::contract::DeclarativeStepKind::Function,
586            DeclarativeStep::LoadBalance(_) => crate::contract::DeclarativeStepKind::LoadBalance,
587            DeclarativeStep::Choice(_) => crate::contract::DeclarativeStepKind::Choice,
588            DeclarativeStep::Split(_) => crate::contract::DeclarativeStepKind::Split,
589            DeclarativeStep::Aggregate(_) => crate::contract::DeclarativeStepKind::Aggregate,
590            DeclarativeStep::WireTap(_) => crate::contract::DeclarativeStepKind::WireTap,
591            DeclarativeStep::Multicast(_) => crate::contract::DeclarativeStepKind::Multicast,
592            DeclarativeStep::RoutingSlip(_) => crate::contract::DeclarativeStepKind::RoutingSlip,
593            DeclarativeStep::RecipientList(_) => {
594                crate::contract::DeclarativeStepKind::RecipientList
595            }
596            DeclarativeStep::Stop => crate::contract::DeclarativeStepKind::Stop,
597            DeclarativeStep::Throttle(_) => crate::contract::DeclarativeStepKind::Throttle,
598            DeclarativeStep::Script(_) => crate::contract::DeclarativeStepKind::Script,
599            DeclarativeStep::StreamCache(_) => crate::contract::DeclarativeStepKind::StreamCache,
600            DeclarativeStep::Marshal(_) => crate::contract::DeclarativeStepKind::Marshal,
601            DeclarativeStep::Unmarshal(_) => crate::contract::DeclarativeStepKind::Unmarshal,
602            DeclarativeStep::Validate(_) => crate::contract::DeclarativeStepKind::Validate,
603            DeclarativeStep::Bean(_) => crate::contract::DeclarativeStepKind::Bean,
604            DeclarativeStep::Delay(_) => crate::contract::DeclarativeStepKind::Delay,
605            DeclarativeStep::Loop(_) => crate::contract::DeclarativeStepKind::Loop,
606            DeclarativeStep::Enrich(_) => crate::contract::DeclarativeStepKind::Enrich,
607            DeclarativeStep::PollEnrich(_) => crate::contract::DeclarativeStepKind::PollEnrich,
608            DeclarativeStep::IdempotentConsumer(_) => {
609                crate::contract::DeclarativeStepKind::IdempotentConsumer
610            }
611            DeclarativeStep::ClaimCheck(_) => crate::contract::DeclarativeStepKind::ClaimCheck,
612            DeclarativeStep::Sampling(_) => crate::contract::DeclarativeStepKind::Sampling,
613            DeclarativeStep::Sort(_) => crate::contract::DeclarativeStepKind::Sort,
614            DeclarativeStep::Resequence(_) => crate::contract::DeclarativeStepKind::Resequence,
615            DeclarativeStep::DoTry { .. } => crate::contract::DeclarativeStepKind::DoTry,
616        }
617    }
618}
619
620#[cfg(test)]
621mod tests {
622    use super::*;
623
624    #[test]
625    fn to_step_def_new() {
626        let def = ToStepDef::new("direct:a");
627        assert_eq!(def.uri, "direct:a");
628    }
629
630    #[test]
631    fn log_step_def_info() {
632        let def = LogStepDef::info("hello");
633        assert_eq!(def.level, LogLevelDef::Info);
634        match def.message {
635            ValueSourceDef::Literal(v) => assert_eq!(v, serde_json::Value::String("hello".into())),
636            _ => panic!("expected literal"),
637        }
638    }
639
640    #[test]
641    fn set_header_literal() {
642        let def = SetHeaderStepDef::literal("key", "value");
643        assert_eq!(def.key, "key");
644        match def.value {
645            ValueSourceDef::Literal(v) => assert_eq!(v, serde_json::Value::String("value".into())),
646            _ => panic!("expected literal"),
647        }
648    }
649
650    #[test]
651    fn bean_step_def_new() {
652        let def = BeanStepDef::new("myBean", "process");
653        assert_eq!(def.name, "myBean");
654        assert_eq!(def.method, "process");
655    }
656
657    #[test]
658    fn throttle_strategy_default() {
659        assert_eq!(ThrottleStrategyDef::default(), ThrottleStrategyDef::Delay);
660    }
661
662    #[test]
663    fn load_balance_strategy_default() {
664        assert_eq!(
665            LoadBalanceStrategyDef::default(),
666            LoadBalanceStrategyDef::RoundRobin
667        );
668    }
669
670    #[test]
671    fn concurrency_variants_equality() {
672        assert_eq!(
673            DeclarativeConcurrency::Sequential,
674            DeclarativeConcurrency::Sequential
675        );
676        assert_ne!(
677            DeclarativeConcurrency::Sequential,
678            DeclarativeConcurrency::Concurrent { max: None }
679        );
680    }
681
682    #[test]
683    fn body_type_variants() {
684        assert_eq!(BodyTypeDef::Text, BodyTypeDef::Text);
685        assert_ne!(BodyTypeDef::Text, BodyTypeDef::Json);
686    }
687
688    #[test]
689    fn data_format_def() {
690        let def = DataFormatDef {
691            format: "protobuf".into(),
692            schema: None,
693        };
694        assert_eq!(def.format, "protobuf");
695        assert!(def.schema.is_none());
696    }
697
698    #[test]
699    fn stream_cache_step_def() {
700        let def = StreamCacheStepDef {
701            threshold: Some(1024),
702        };
703        assert_eq!(def.threshold, Some(1024));
704    }
705
706    #[test]
707    fn delay_step_def() {
708        let def = DelayStepDef {
709            delay_ms: 500,
710            dynamic_header: Some("X-Delay".into()),
711        };
712        assert_eq!(def.delay_ms, 500);
713        assert_eq!(def.dynamic_header.as_deref(), Some("X-Delay"));
714    }
715
716    #[test]
717    fn circuit_breaker_def() {
718        let cb = DeclarativeCircuitBreaker {
719            failure_threshold: 3,
720            open_duration_ms: 5000,
721        };
722        assert_eq!(cb.failure_threshold, 3);
723        assert_eq!(cb.open_duration_ms, 5000);
724    }
725}