Skip to main content

camel_component_api/
endpoint.rs

1use camel_api::{BodyType, BoxProcessor, CamelError, Exchange};
2
3use crate::ProducerContext;
4use crate::consumer::Consumer;
5
6/// A polling consumer receives messages on demand (pull model) rather than
7/// being event-driven (push model).
8///
9/// Implement this trait on endpoints that support synchronous pull-based
10/// consumption (e.g., file, FTP, JMS). Components that are purely
11/// event-driven (e.g., HTTP server, Kafka) can leave the default
12/// [`Endpoint::polling_consumer`] returning `None`.
13pub trait PollingConsumer: Send + Sync {
14    /// Receive the next available exchange, or `None` if no message is pending.
15    fn receive(&self) -> Result<Option<Exchange>, CamelError>;
16}
17
18/// An Endpoint represents a source or destination in a route URI.
19pub trait Endpoint: Send + Sync {
20    /// The URI that identifies this endpoint.
21    fn uri(&self) -> &str;
22
23    /// Create a consumer that reads from this endpoint.
24    fn create_consumer(&self) -> Result<Box<dyn Consumer>, CamelError>;
25
26    /// Create a producer that writes to this endpoint.
27    fn create_producer(&self, ctx: &ProducerContext) -> Result<BoxProcessor, CamelError>;
28
29    /// Optional body type contract for the producer.
30    ///
31    /// When `Some(t)`, the pipeline will coerce the body to `t` before calling
32    /// the producer. Default: `None` (accept any body variant, zero overhead).
33    fn body_contract(&self) -> Option<BodyType> {
34        None
35    }
36
37    /// Return a polling consumer for this endpoint, if supported.
38    ///
39    /// Polling consumers use a pull model — callers invoke
40    /// [`PollingConsumer::receive`] to retrieve the next message.
41    /// Endpoints that only support push-based consumption should leave
42    /// this default (returns `None`).
43    fn polling_consumer(&self) -> Option<Box<dyn PollingConsumer>> {
44        None
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use crate::ComponentContext;
52
53    /// A minimal mock endpoint for testing default trait methods.
54    struct MockEndpoint {
55        uri: String,
56    }
57
58    impl MockEndpoint {
59        fn new(uri: &str) -> Self {
60            Self {
61                uri: uri.to_string(),
62            }
63        }
64    }
65
66    impl Endpoint for MockEndpoint {
67        fn uri(&self) -> &str {
68            &self.uri
69        }
70
71        fn create_consumer(&self) -> Result<Box<dyn Consumer>, CamelError> {
72            Err(CamelError::EndpointCreationFailed("mock".into()))
73        }
74
75        fn create_producer(&self, _ctx: &ProducerContext) -> Result<BoxProcessor, CamelError> {
76            Err(CamelError::ProcessorError("mock".into()))
77        }
78    }
79
80    #[test]
81    fn mock_endpoint_polling_consumer_returns_none() {
82        let ep = MockEndpoint::new("mock://test");
83        assert!(ep.polling_consumer().is_none());
84    }
85
86    #[test]
87    fn mock_endpoint_body_contract_default_is_none() {
88        let ep = MockEndpoint::new("mock://test");
89        assert!(ep.body_contract().is_none());
90    }
91
92    #[test]
93    fn mock_endpoint_uri() {
94        let ep = MockEndpoint::new("mock://test");
95        assert_eq!(ep.uri(), "mock://test");
96    }
97
98    #[test]
99    fn mock_endpoint_create_consumer_errors() {
100        let ep = MockEndpoint::new("mock://test");
101        let result = ep.create_consumer();
102        assert!(result.is_err());
103    }
104
105    #[test]
106    fn mock_endpoint_create_producer_errors() {
107        let ep = MockEndpoint::new("mock://test");
108        let ctx = ProducerContext::new();
109        let result = ep.create_producer(&ctx);
110        assert!(result.is_err());
111    }
112
113    /// Verify ComponentContext can be constructed (via NoOpComponentContext).
114    #[test]
115    fn component_context_noop_can_be_constructed() {
116        let _ctx = crate::NoOpComponentContext;
117    }
118
119    #[test]
120    fn component_context_noop_resolve_returns_none() {
121        let ctx = crate::NoOpComponentContext;
122        assert!(ctx.resolve_component("anything").is_none());
123        assert!(ctx.resolve_language("anything").is_none());
124    }
125}