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 ORIGINAL_CONTENT_TYPE_INHERITED_EXTENSION: &str = "CamelOriginalContentTypeInherited";
22
23pub struct OriginalContentTypeInherited(pub bool);
26
27pub const PROPERTY_HTTP_CONTENT_TYPE_INHERITED: &str = "CamelHttpContentTypeInherited";
34
35pub const CAMEL_STOP: &str = "CamelStop";
40
41pub fn is_camel_stop(exchange: &Exchange) -> bool {
47 exchange
48 .property(CAMEL_STOP)
49 .and_then(|v| v.as_bool())
50 .unwrap_or(false)
51}
52
53pub const PROPERTY_EXCEPTION_MESSAGE: &str = "CamelExceptionMessage";
55pub const PROPERTY_EXCEPTION_KIND: &str = "CamelExceptionKind";
57pub const PROPERTY_EXCEPTION_CAUGHT: &str = "CamelExceptionCaught";
59pub const PROPERTY_EXCEPTION_HANDLED: &str = "CamelExceptionHandled";
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
65pub enum ExchangePattern {
66 #[default]
68 InOnly,
69 InOut,
71}
72
73#[derive(Debug)]
78pub struct Exchange {
79 pub input: Message,
81 pub output: Option<Message>,
83 pub properties: HashMap<String, Value>,
85 pub extensions: HashMap<String, Arc<dyn Any + Send + Sync>>,
88 pub error: Option<CamelError>,
90 pub pattern: ExchangePattern,
92 pub correlation_id: String,
94 pub otel_context: Context,
98 pub in_flight_claim: Option<crate::in_flight::InFlightClaim>,
107}
108
109impl Exchange {
110 pub fn new(input: Message) -> Self {
112 Self {
113 input,
114 output: None,
115 properties: HashMap::new(),
116 extensions: HashMap::new(),
117 error: None,
118 pattern: ExchangePattern::default(),
119 correlation_id: Uuid::new_v4().to_string(),
120 otel_context: Context::new(),
121 in_flight_claim: None,
122 }
123 }
124
125 pub fn new_in_out(input: Message) -> Self {
127 Self {
128 input,
129 output: None,
130 properties: HashMap::new(),
131 extensions: HashMap::new(),
132 error: None,
133 pattern: ExchangePattern::InOut,
134 correlation_id: Uuid::new_v4().to_string(),
135 otel_context: Context::new(),
136 in_flight_claim: None,
137 }
138 }
139
140 pub fn correlation_id(&self) -> &str {
142 &self.correlation_id
143 }
144
145 pub fn property(&self, key: &str) -> Option<&Value> {
147 self.properties.get(key)
148 }
149
150 pub fn set_property(&mut self, key: impl Into<String>, value: impl Into<Value>) {
152 self.properties.insert(key.into(), value.into());
153 }
154
155 pub fn remove_property(&mut self, key: &str) {
158 self.properties.remove(key);
159 }
160
161 pub fn has_error(&self) -> bool {
163 self.error.is_some()
164 }
165
166 pub fn set_error(&mut self, error: CamelError) {
179 if let CamelError::HttpOperationFailed {
180 status_code,
181 status_text,
182 ..
183 } = &error
184 {
185 self.input.set_header(
186 "CamelHttpResponseCode",
187 Value::Number((*status_code).into()),
188 );
189 self.input
190 .set_header("CamelHttpResponseText", Value::String(status_text.clone()));
191 }
192 let msg = error.to_string();
193 let kind = error.classify().to_string();
194 self.properties.insert(
195 PROPERTY_EXCEPTION_MESSAGE.to_string(),
196 Value::String(msg.clone()),
197 );
198 self.properties
199 .insert(PROPERTY_EXCEPTION_KIND.to_string(), Value::String(kind));
200 self.properties
201 .insert(PROPERTY_EXCEPTION_CAUGHT.to_string(), Value::String(msg));
202 self.error = Some(error);
203 }
204
205 pub fn clear_error(&mut self) {
210 self.error = None;
211 self.properties.remove(PROPERTY_EXCEPTION_MESSAGE);
212 self.properties.remove(PROPERTY_EXCEPTION_KIND);
213 self.properties.remove(PROPERTY_EXCEPTION_CAUGHT);
214 }
215
216 pub fn handle_error(&mut self) {
221 self.properties
222 .insert(PROPERTY_EXCEPTION_HANDLED.to_string(), Value::Bool(true));
223 self.clear_error();
224 }
225
226 pub fn set_extension(&mut self, key: impl Into<String>, value: Arc<dyn Any + Send + Sync>) {
228 self.extensions.insert(key.into(), value);
229 }
230
231 pub fn get_extension<T: Any>(&self, key: &str) -> Option<&T> {
234 self.extensions.get(key)?.downcast_ref::<T>()
235 }
236
237 pub fn body_as<T: FromBody>(&self) -> Result<T, CamelError> {
249 T::from_body(&self.input.body)
250 }
251
252 pub fn branch_clone(&self) -> Self {
257 let mut copy = self.clone();
258 copy.in_flight_claim = self.in_flight_claim.as_ref().map(|claim| claim.split());
259 copy
260 }
261}
262
263impl Clone for Exchange {
264 fn clone(&self) -> Self {
265 Self {
266 input: self.input.clone(),
267 output: self.output.clone(),
268 properties: self.properties.clone(),
269 extensions: self.extensions.clone(), error: self.error.clone(),
271 pattern: self.pattern,
272 correlation_id: self.correlation_id.clone(),
273 otel_context: self.otel_context.clone(),
274 in_flight_claim: None,
278 }
279 }
280}
281
282impl Default for Exchange {
283 fn default() -> Self {
284 Self::new(Message::default())
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291 use crate::Body;
292 use crate::in_flight::{InFlightClaim, InFlightGauge};
293 use serde_json::json;
294 use std::sync::Arc;
295
296 #[test]
297 fn remove_property_removes_and_is_idempotent() {
298 let mut ex = Exchange::new(Message::default());
299 ex.set_property(PROPERTY_HTTP_CONTENT_TYPE_INHERITED, Value::Bool(true));
300 assert_eq!(
301 ex.property(PROPERTY_HTTP_CONTENT_TYPE_INHERITED),
302 Some(&Value::Bool(true))
303 );
304
305 ex.remove_property(PROPERTY_HTTP_CONTENT_TYPE_INHERITED);
306 assert_eq!(ex.property(PROPERTY_HTTP_CONTENT_TYPE_INHERITED), None);
307
308 ex.remove_property(PROPERTY_HTTP_CONTENT_TYPE_INHERITED);
309 assert_eq!(ex.property(PROPERTY_HTTP_CONTENT_TYPE_INHERITED), None);
310 }
311
312 #[test]
313 fn remove_property_absent_is_noop() {
314 let mut ex = Exchange::new(Message::default());
315 assert_eq!(ex.property(PROPERTY_HTTP_CONTENT_TYPE_INHERITED), None);
316
317 ex.remove_property(PROPERTY_HTTP_CONTENT_TYPE_INHERITED);
318
319 assert_eq!(ex.property(PROPERTY_HTTP_CONTENT_TYPE_INHERITED), None);
320 }
321
322 #[test]
323 fn original_content_type_snapshot_roundtrips() {
324 let mut ex = Exchange::new(Message::default());
325 ex.set_extension(
326 ORIGINAL_CONTENT_TYPE_INHERITED_EXTENSION,
327 Arc::new(OriginalContentTypeInherited(true)),
328 );
329
330 let snapshot = ex
331 .get_extension::<OriginalContentTypeInherited>(
332 ORIGINAL_CONTENT_TYPE_INHERITED_EXTENSION,
333 )
334 .expect("snapshot stored under its extension key");
335 assert!(snapshot.0);
336
337 let fresh = Exchange::new(Message::default());
338 assert!(
339 fresh
340 .get_extension::<OriginalContentTypeInherited>(
341 ORIGINAL_CONTENT_TYPE_INHERITED_EXTENSION
342 )
343 .is_none()
344 );
345 }
346
347 #[test]
348 fn test_exchange_new() {
349 let msg = Message::new("test");
350 let ex = Exchange::new(msg);
351 assert_eq!(ex.input.body.as_text(), Some("test"));
352 assert!(ex.output.is_none());
353 assert!(!ex.has_error());
354 assert_eq!(ex.pattern, ExchangePattern::InOnly);
355 }
356
357 #[test]
358 fn test_exchange_in_out() {
359 let ex = Exchange::new_in_out(Message::default());
360 assert_eq!(ex.pattern, ExchangePattern::InOut);
361 }
362
363 #[test]
364 fn test_exchange_properties() {
365 let mut ex = Exchange::default();
366 ex.set_property("key", Value::Bool(true));
367 assert_eq!(ex.property("key"), Some(&Value::Bool(true)));
368 assert_eq!(ex.property("missing"), None);
369 }
370
371 #[test]
372 fn test_exchange_error() {
373 let mut ex = Exchange::default();
374 assert!(!ex.has_error());
375 ex.set_error(CamelError::ProcessorError("test".into()));
376 assert!(ex.has_error());
377 }
378
379 #[test]
380 fn test_set_error_populates_properties() {
381 let mut ex = Exchange::default();
382 ex.set_error(CamelError::ProcessorError("boom".into()));
383
384 assert!(ex.has_error());
385 assert_eq!(
386 ex.properties.get(PROPERTY_EXCEPTION_MESSAGE),
387 Some(&Value::String("Processor error: boom".to_string()))
388 );
389 assert_eq!(
390 ex.properties.get(PROPERTY_EXCEPTION_KIND),
391 Some(&Value::String("processor".to_string()))
392 );
393 assert_eq!(
394 ex.properties.get(PROPERTY_EXCEPTION_CAUGHT),
395 Some(&Value::String("Processor error: boom".to_string()))
396 );
397 }
398
399 #[test]
400 fn test_set_error_http_failure_exposes_status_headers() {
401 let mut ex = Exchange::default();
402 ex.set_error(CamelError::HttpOperationFailed {
403 method: "GET".into(),
404 url: "http://example.test/x".into(),
405 status_code: 404,
406 status_text: "Not Found".into(),
407 response_body: None,
408 });
409
410 assert_eq!(
411 ex.input.header("CamelHttpResponseCode"),
412 Some(&Value::Number(404.into()))
413 );
414 assert_eq!(
415 ex.input.header("CamelHttpResponseText"),
416 Some(&Value::String("Not Found".to_string()))
417 );
418 }
419
420 #[test]
421 fn test_set_error_non_http_leaves_http_headers_untouched() {
422 let mut ex = Exchange::default();
423 ex.set_error(CamelError::ProcessorError("boom".into()));
424
425 assert!(ex.input.header("CamelHttpResponseCode").is_none());
426 assert!(ex.input.header("CamelHttpResponseText").is_none());
427 }
428
429 #[test]
430 fn test_clear_error_removes_properties() {
431 let mut ex = Exchange::default();
432 ex.set_error(CamelError::RouteError("fail".into()));
433 assert!(ex.has_error());
434 assert!(ex.properties.contains_key(PROPERTY_EXCEPTION_MESSAGE));
435
436 ex.clear_error();
437
438 assert!(!ex.has_error());
439 assert!(!ex.properties.contains_key(PROPERTY_EXCEPTION_MESSAGE));
440 assert!(!ex.properties.contains_key(PROPERTY_EXCEPTION_KIND));
441 assert!(!ex.properties.contains_key(PROPERTY_EXCEPTION_CAUGHT));
442 }
443
444 #[test]
445 fn test_exchange_lifecycle() {
446 let mut ex = Exchange::new(Message::new("input data"));
447 assert_eq!(ex.input.body.as_text(), Some("input data"));
448
449 ex.set_property("processed", Value::Bool(true));
451
452 ex.output = Some(Message::new("output data"));
454 assert!(ex.output.is_some());
455
456 assert!(!ex.has_error());
458 }
459
460 #[test]
461 fn test_exchange_otel_context_default() {
462 let ex = Exchange::default();
463 use opentelemetry::trace::TraceContextExt;
466 assert!(!ex.otel_context.span().span_context().is_valid());
467 }
468
469 #[test]
470 fn test_exchange_otel_context_propagates_in_clone() {
471 let ex = Exchange::default();
472 let cloned = ex.clone();
473 use opentelemetry::trace::TraceContextExt;
475 assert!(!cloned.otel_context.span().span_context().is_valid());
476 }
477
478 #[test]
479 fn test_set_and_get_extension() {
480 use std::sync::Arc;
481 let mut ex = Exchange::default();
482 ex.set_extension("my.key", Arc::new(42u32));
483 let val: Option<&u32> = ex.get_extension("my.key");
484 assert_eq!(val, Some(&42u32));
485 }
486
487 #[test]
488 fn test_get_extension_wrong_type_returns_none() {
489 use std::sync::Arc;
490 let mut ex = Exchange::default();
491 ex.set_extension("my.key", Arc::new(42u32));
492 let val: Option<&String> = ex.get_extension("my.key");
493 assert!(val.is_none());
494 }
495
496 #[test]
497 fn test_get_extension_missing_key_returns_none() {
498 let ex = Exchange::default();
499 let val: Option<&u32> = ex.get_extension("nope");
500 assert!(val.is_none());
501 }
502
503 #[test]
504 fn test_clone_shares_extension_arc() {
505 use std::sync::Arc;
506 let mut ex = Exchange::default();
507 ex.set_extension("shared", Arc::new(99u64));
508 let cloned = ex.clone();
509 assert_eq!(ex.get_extension::<u64>("shared"), Some(&99u64));
511 assert_eq!(cloned.get_extension::<u64>("shared"), Some(&99u64));
512 }
513
514 #[test]
515 fn test_body_as_string_from_text() {
516 let ex = Exchange::new(Message::new(Body::Text("hello".to_string())));
517
518 let result = ex.body_as::<String>();
519
520 assert_eq!(result.unwrap(), "hello");
521 }
522
523 #[test]
524 fn test_body_as_string_from_json_string() {
525 let ex = Exchange::new(Message::new(Body::Json(json!("hello"))));
526
527 let result = ex.body_as::<String>();
528
529 assert_eq!(result.unwrap(), "hello");
530 }
531
532 #[test]
533 fn test_body_as_json_value_from_json_number() {
534 let ex = Exchange::new(Message::new(Body::Json(json!(42))));
535
536 let result = ex.body_as::<serde_json::Value>();
537
538 assert_eq!(result.unwrap(), json!(42));
539 }
540
541 #[test]
542 fn test_body_as_vec_u8_from_bytes() {
543 let ex = Exchange::new(Message::new(Body::from(vec![1u8, 2, 3, 4])));
544
545 let result = ex.body_as::<Vec<u8>>();
546
547 assert_eq!(result.unwrap(), vec![1u8, 2, 3, 4]);
548 }
549
550 #[test]
551 fn test_body_as_string_from_empty_returns_err() {
552 let ex = Exchange::new(Message::new(Body::Empty));
553
554 let result = ex.body_as::<String>();
555
556 assert!(result.is_err());
557 }
558
559 #[test]
560 fn branch_clone_splits_claim_original_unaffected() {
561 let gauge = Arc::new(InFlightGauge::new());
562 let mut src = Exchange::new(Message::default());
563 src.in_flight_claim = Some(InFlightClaim::attach(&gauge));
564
565 let copy = src.branch_clone();
566
567 assert_eq!(gauge.total(), 2);
568 assert!(copy.in_flight_claim.is_some());
569 assert!(src.in_flight_claim.is_some());
570 }
571
572 #[test]
573 fn branch_clone_sibling_releases_independently() {
574 let gauge = Arc::new(InFlightGauge::new());
575 let mut src = Exchange::new(Message::default());
576 src.in_flight_claim = Some(InFlightClaim::attach(&gauge));
577
578 let copy = src.branch_clone();
579 drop(copy);
580 assert_eq!(gauge.total(), 1);
581
582 drop(src);
583 assert_eq!(gauge.total(), 0);
584 }
585
586 #[test]
587 fn plain_clone_carries_no_claim() {
588 let gauge = Arc::new(InFlightGauge::new());
589 let mut src = Exchange::new(Message::default());
590 src.in_flight_claim = Some(InFlightClaim::attach(&gauge));
591 assert_eq!(gauge.total(), 1);
592
593 let copy = src.clone();
594
595 assert_eq!(gauge.total(), 1);
596 assert!(copy.in_flight_claim.is_none());
597 assert!(src.in_flight_claim.is_some());
598 }
599}