1use std::any::Any;
2use std::collections::HashMap;
3use std::sync::Arc;
4
5use opentelemetry::Context;
6use uuid::Uuid;
7
8use crate::error::CamelError;
9use crate::from_body::FromBody;
10use crate::message::Message;
11use crate::value::Value;
12
13pub const ORIGINAL_MESSAGE_EXTENSION: &str = "CamelOriginalMessage";
16
17pub const CAMEL_STOP: &str = "CamelStop";
22
23pub fn is_camel_stop(exchange: &Exchange) -> bool {
29 exchange
30 .property(CAMEL_STOP)
31 .and_then(|v| v.as_bool())
32 .unwrap_or(false)
33}
34
35pub const PROPERTY_EXCEPTION_MESSAGE: &str = "CamelExceptionMessage";
37pub const PROPERTY_EXCEPTION_KIND: &str = "CamelExceptionKind";
39pub const PROPERTY_EXCEPTION_CAUGHT: &str = "CamelExceptionCaught";
41pub const PROPERTY_EXCEPTION_HANDLED: &str = "CamelExceptionHandled";
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
47pub enum ExchangePattern {
48 #[default]
50 InOnly,
51 InOut,
53}
54
55#[derive(Debug)]
60pub struct Exchange {
61 pub input: Message,
63 pub output: Option<Message>,
65 pub properties: HashMap<String, Value>,
67 pub extensions: HashMap<String, Arc<dyn Any + Send + Sync>>,
70 pub error: Option<CamelError>,
72 pub pattern: ExchangePattern,
74 pub correlation_id: String,
76 pub otel_context: Context,
80 pub in_flight_claim: Option<crate::in_flight::InFlightClaim>,
89}
90
91impl Exchange {
92 pub fn new(input: Message) -> Self {
94 Self {
95 input,
96 output: None,
97 properties: HashMap::new(),
98 extensions: HashMap::new(),
99 error: None,
100 pattern: ExchangePattern::default(),
101 correlation_id: Uuid::new_v4().to_string(),
102 otel_context: Context::new(),
103 in_flight_claim: None,
104 }
105 }
106
107 pub fn new_in_out(input: Message) -> Self {
109 Self {
110 input,
111 output: None,
112 properties: HashMap::new(),
113 extensions: HashMap::new(),
114 error: None,
115 pattern: ExchangePattern::InOut,
116 correlation_id: Uuid::new_v4().to_string(),
117 otel_context: Context::new(),
118 in_flight_claim: None,
119 }
120 }
121
122 pub fn correlation_id(&self) -> &str {
124 &self.correlation_id
125 }
126
127 pub fn property(&self, key: &str) -> Option<&Value> {
129 self.properties.get(key)
130 }
131
132 pub fn set_property(&mut self, key: impl Into<String>, value: impl Into<Value>) {
134 self.properties.insert(key.into(), value.into());
135 }
136
137 pub fn has_error(&self) -> bool {
139 self.error.is_some()
140 }
141
142 pub fn set_error(&mut self, error: CamelError) {
155 if let CamelError::HttpOperationFailed {
156 status_code,
157 status_text,
158 ..
159 } = &error
160 {
161 self.input.set_header(
162 "CamelHttpResponseCode",
163 Value::Number((*status_code).into()),
164 );
165 self.input
166 .set_header("CamelHttpResponseText", Value::String(status_text.clone()));
167 }
168 let msg = error.to_string();
169 let kind = error.classify().to_string();
170 self.properties.insert(
171 PROPERTY_EXCEPTION_MESSAGE.to_string(),
172 Value::String(msg.clone()),
173 );
174 self.properties
175 .insert(PROPERTY_EXCEPTION_KIND.to_string(), Value::String(kind));
176 self.properties
177 .insert(PROPERTY_EXCEPTION_CAUGHT.to_string(), Value::String(msg));
178 self.error = Some(error);
179 }
180
181 pub fn clear_error(&mut self) {
186 self.error = None;
187 self.properties.remove(PROPERTY_EXCEPTION_MESSAGE);
188 self.properties.remove(PROPERTY_EXCEPTION_KIND);
189 self.properties.remove(PROPERTY_EXCEPTION_CAUGHT);
190 }
191
192 pub fn handle_error(&mut self) {
197 self.properties
198 .insert(PROPERTY_EXCEPTION_HANDLED.to_string(), Value::Bool(true));
199 self.clear_error();
200 }
201
202 pub fn set_extension(&mut self, key: impl Into<String>, value: Arc<dyn Any + Send + Sync>) {
204 self.extensions.insert(key.into(), value);
205 }
206
207 pub fn get_extension<T: Any>(&self, key: &str) -> Option<&T> {
210 self.extensions.get(key)?.downcast_ref::<T>()
211 }
212
213 pub fn body_as<T: FromBody>(&self) -> Result<T, CamelError> {
225 T::from_body(&self.input.body)
226 }
227
228 pub fn branch_clone(&self) -> Self {
233 let mut copy = self.clone();
234 copy.in_flight_claim = self.in_flight_claim.as_ref().map(|claim| claim.split());
235 copy
236 }
237}
238
239impl Clone for Exchange {
240 fn clone(&self) -> Self {
241 Self {
242 input: self.input.clone(),
243 output: self.output.clone(),
244 properties: self.properties.clone(),
245 extensions: self.extensions.clone(), error: self.error.clone(),
247 pattern: self.pattern,
248 correlation_id: self.correlation_id.clone(),
249 otel_context: self.otel_context.clone(),
250 in_flight_claim: None,
254 }
255 }
256}
257
258impl Default for Exchange {
259 fn default() -> Self {
260 Self::new(Message::default())
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267 use crate::Body;
268 use crate::in_flight::{InFlightClaim, InFlightGauge};
269 use serde_json::json;
270 use std::sync::Arc;
271
272 #[test]
273 fn test_exchange_new() {
274 let msg = Message::new("test");
275 let ex = Exchange::new(msg);
276 assert_eq!(ex.input.body.as_text(), Some("test"));
277 assert!(ex.output.is_none());
278 assert!(!ex.has_error());
279 assert_eq!(ex.pattern, ExchangePattern::InOnly);
280 }
281
282 #[test]
283 fn test_exchange_in_out() {
284 let ex = Exchange::new_in_out(Message::default());
285 assert_eq!(ex.pattern, ExchangePattern::InOut);
286 }
287
288 #[test]
289 fn test_exchange_properties() {
290 let mut ex = Exchange::default();
291 ex.set_property("key", Value::Bool(true));
292 assert_eq!(ex.property("key"), Some(&Value::Bool(true)));
293 assert_eq!(ex.property("missing"), None);
294 }
295
296 #[test]
297 fn test_exchange_error() {
298 let mut ex = Exchange::default();
299 assert!(!ex.has_error());
300 ex.set_error(CamelError::ProcessorError("test".into()));
301 assert!(ex.has_error());
302 }
303
304 #[test]
305 fn test_set_error_populates_properties() {
306 let mut ex = Exchange::default();
307 ex.set_error(CamelError::ProcessorError("boom".into()));
308
309 assert!(ex.has_error());
310 assert_eq!(
311 ex.properties.get(PROPERTY_EXCEPTION_MESSAGE),
312 Some(&Value::String("Processor error: boom".to_string()))
313 );
314 assert_eq!(
315 ex.properties.get(PROPERTY_EXCEPTION_KIND),
316 Some(&Value::String("processor".to_string()))
317 );
318 assert_eq!(
319 ex.properties.get(PROPERTY_EXCEPTION_CAUGHT),
320 Some(&Value::String("Processor error: boom".to_string()))
321 );
322 }
323
324 #[test]
325 fn test_set_error_http_failure_exposes_status_headers() {
326 let mut ex = Exchange::default();
327 ex.set_error(CamelError::HttpOperationFailed {
328 method: "GET".into(),
329 url: "http://example.test/x".into(),
330 status_code: 404,
331 status_text: "Not Found".into(),
332 response_body: None,
333 });
334
335 assert_eq!(
336 ex.input.header("CamelHttpResponseCode"),
337 Some(&Value::Number(404.into()))
338 );
339 assert_eq!(
340 ex.input.header("CamelHttpResponseText"),
341 Some(&Value::String("Not Found".to_string()))
342 );
343 }
344
345 #[test]
346 fn test_set_error_non_http_leaves_http_headers_untouched() {
347 let mut ex = Exchange::default();
348 ex.set_error(CamelError::ProcessorError("boom".into()));
349
350 assert!(ex.input.header("CamelHttpResponseCode").is_none());
351 assert!(ex.input.header("CamelHttpResponseText").is_none());
352 }
353
354 #[test]
355 fn test_clear_error_removes_properties() {
356 let mut ex = Exchange::default();
357 ex.set_error(CamelError::RouteError("fail".into()));
358 assert!(ex.has_error());
359 assert!(ex.properties.contains_key(PROPERTY_EXCEPTION_MESSAGE));
360
361 ex.clear_error();
362
363 assert!(!ex.has_error());
364 assert!(!ex.properties.contains_key(PROPERTY_EXCEPTION_MESSAGE));
365 assert!(!ex.properties.contains_key(PROPERTY_EXCEPTION_KIND));
366 assert!(!ex.properties.contains_key(PROPERTY_EXCEPTION_CAUGHT));
367 }
368
369 #[test]
370 fn test_exchange_lifecycle() {
371 let mut ex = Exchange::new(Message::new("input data"));
372 assert_eq!(ex.input.body.as_text(), Some("input data"));
373
374 ex.set_property("processed", Value::Bool(true));
376
377 ex.output = Some(Message::new("output data"));
379 assert!(ex.output.is_some());
380
381 assert!(!ex.has_error());
383 }
384
385 #[test]
386 fn test_exchange_otel_context_default() {
387 let ex = Exchange::default();
388 use opentelemetry::trace::TraceContextExt;
391 assert!(!ex.otel_context.span().span_context().is_valid());
392 }
393
394 #[test]
395 fn test_exchange_otel_context_propagates_in_clone() {
396 let ex = Exchange::default();
397 let cloned = ex.clone();
398 use opentelemetry::trace::TraceContextExt;
400 assert!(!cloned.otel_context.span().span_context().is_valid());
401 }
402
403 #[test]
404 fn test_set_and_get_extension() {
405 use std::sync::Arc;
406 let mut ex = Exchange::default();
407 ex.set_extension("my.key", Arc::new(42u32));
408 let val: Option<&u32> = ex.get_extension("my.key");
409 assert_eq!(val, Some(&42u32));
410 }
411
412 #[test]
413 fn test_get_extension_wrong_type_returns_none() {
414 use std::sync::Arc;
415 let mut ex = Exchange::default();
416 ex.set_extension("my.key", Arc::new(42u32));
417 let val: Option<&String> = ex.get_extension("my.key");
418 assert!(val.is_none());
419 }
420
421 #[test]
422 fn test_get_extension_missing_key_returns_none() {
423 let ex = Exchange::default();
424 let val: Option<&u32> = ex.get_extension("nope");
425 assert!(val.is_none());
426 }
427
428 #[test]
429 fn test_clone_shares_extension_arc() {
430 use std::sync::Arc;
431 let mut ex = Exchange::default();
432 ex.set_extension("shared", Arc::new(99u64));
433 let cloned = ex.clone();
434 assert_eq!(ex.get_extension::<u64>("shared"), Some(&99u64));
436 assert_eq!(cloned.get_extension::<u64>("shared"), Some(&99u64));
437 }
438
439 #[test]
440 fn test_body_as_string_from_text() {
441 let ex = Exchange::new(Message::new(Body::Text("hello".to_string())));
442
443 let result = ex.body_as::<String>();
444
445 assert_eq!(result.unwrap(), "hello");
446 }
447
448 #[test]
449 fn test_body_as_string_from_json_string() {
450 let ex = Exchange::new(Message::new(Body::Json(json!("hello"))));
451
452 let result = ex.body_as::<String>();
453
454 assert_eq!(result.unwrap(), "hello");
455 }
456
457 #[test]
458 fn test_body_as_json_value_from_json_number() {
459 let ex = Exchange::new(Message::new(Body::Json(json!(42))));
460
461 let result = ex.body_as::<serde_json::Value>();
462
463 assert_eq!(result.unwrap(), json!(42));
464 }
465
466 #[test]
467 fn test_body_as_vec_u8_from_bytes() {
468 let ex = Exchange::new(Message::new(Body::from(vec![1u8, 2, 3, 4])));
469
470 let result = ex.body_as::<Vec<u8>>();
471
472 assert_eq!(result.unwrap(), vec![1u8, 2, 3, 4]);
473 }
474
475 #[test]
476 fn test_body_as_string_from_empty_returns_err() {
477 let ex = Exchange::new(Message::new(Body::Empty));
478
479 let result = ex.body_as::<String>();
480
481 assert!(result.is_err());
482 }
483
484 #[test]
485 fn branch_clone_splits_claim_original_unaffected() {
486 let gauge = Arc::new(InFlightGauge::new());
487 let mut src = Exchange::new(Message::default());
488 src.in_flight_claim = Some(InFlightClaim::attach(&gauge));
489
490 let copy = src.branch_clone();
491
492 assert_eq!(gauge.total(), 2);
493 assert!(copy.in_flight_claim.is_some());
494 assert!(src.in_flight_claim.is_some());
495 }
496
497 #[test]
498 fn branch_clone_sibling_releases_independently() {
499 let gauge = Arc::new(InFlightGauge::new());
500 let mut src = Exchange::new(Message::default());
501 src.in_flight_claim = Some(InFlightClaim::attach(&gauge));
502
503 let copy = src.branch_clone();
504 drop(copy);
505 assert_eq!(gauge.total(), 1);
506
507 drop(src);
508 assert_eq!(gauge.total(), 0);
509 }
510
511 #[test]
512 fn plain_clone_carries_no_claim() {
513 let gauge = Arc::new(InFlightGauge::new());
514 let mut src = Exchange::new(Message::default());
515 src.in_flight_claim = Some(InFlightClaim::attach(&gauge));
516 assert_eq!(gauge.total(), 1);
517
518 let copy = src.clone();
519
520 assert_eq!(gauge.total(), 1);
521 assert!(copy.in_flight_claim.is_none());
522 assert!(src.in_flight_claim.is_some());
523 }
524}