Skip to main content

camel_dsl/
model.rs

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