Keyboard shortcuts

Press โ† or โ†’ to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Custom component

A custom component connects rust-camel to a system the built-in components do not cover. You implement the Component trait, wrap it in a ComponentBundle, and register the bundle against a TOML config key.

Implement the Component and Endpoint

pub struct EchoComponent {
    prefix: String,
}

impl EchoComponent {
    pub fn new(prefix: impl Into<String>) -> Self {
        Self {
            prefix: prefix.into(),
        }
    }
}

impl Component for EchoComponent {
    fn scheme(&self) -> &str {
        "echo"
    }

    fn create_endpoint(
        &self,
        uri: &str,
        _ctx: &dyn ComponentContext,
    ) -> Result<Box<dyn Endpoint>, CamelError> {
        Ok(Box::new(EchoEndpoint {
            uri: uri.to_string(),
            prefix: self.prefix.clone(),
        }))
    }
}
struct EchoEndpoint {
    uri: String,
    prefix: String,
}

impl Endpoint for EchoEndpoint {
    fn uri(&self) -> &str {
        &self.uri
    }

    fn create_consumer(
        &self,
        _rt: Arc<dyn camel_component_api::RuntimeObservability>,
    ) -> Result<Box<dyn Consumer>, CamelError> {
        Err(CamelError::RouteError(
            "echo component is producer-only".into(),
        ))
    }

    fn create_producer(
        &self,
        _rt: Arc<dyn camel_component_api::RuntimeObservability>,
        _ctx: &ProducerContext,
    ) -> Result<BoxProcessor, CamelError> {
        let prefix = self.prefix.clone();
        // Log the exchange body with the configured prefix.
        Ok(BoxProcessor::from_fn(move |exchange| {
            let prefix = prefix.clone();
            Box::pin(async move {
                let body = exchange
                    .input
                    .body
                    .as_text()
                    .unwrap_or("<non-text body>")
                    .to_string();
                tracing::info!("{}{}", prefix, body);
                Ok(exchange)
            })
        }))
    }
}

The contract layers from factory to worker. A Component is a factory for one URI scheme. create_endpoint builds an Endpoint for a specific URI. The Endpoint creates a Consumer for inbound traffic or a Producer for outbound traffic. A Producer is a Service<Exchange> that does the actual work.

EchoComponent::scheme returns "echo", so the runtime resolves any echo:... URI to this component. create_endpoint stamps the configured prefix onto each EchoEndpoint. This endpoint is producer-only. create_consumer returns an error to signal that inbound traffic is unsupported. create_producer returns a BoxProcessor that logs the exchange body with the prefix. The exchange passes through unchanged.

Wrap the component in a bundle

pub struct EchoBundle {
    config: EchoConfig,
}

impl ComponentBundle for EchoBundle {
    fn config_key() -> &'static str {
        "echo"
    }

    fn from_toml(raw: toml::Value) -> Result<Self, CamelError> {
        let config: EchoConfig = raw
            .try_into()
            .map_err(|e: toml::de::Error| CamelError::Config(e.to_string()))?;
        Ok(Self { config })
    }

    fn register_all(self, registrar: &mut dyn ComponentRegistrar) {
        registrar.register_component_dyn(Arc::new(EchoComponent::new(self.config.prefix)));
    }
}

A ComponentBundle owns one TOML config key and registers every scheme the bundle owns. config_key returns "echo", which maps to [components.echo] in Camel.toml. from_toml deserializes the raw TOML block into EchoConfig. register_all receives a ComponentRegistrar and calls register_component_dyn for each component the bundle owns.

Register and use the component

    if let Some(raw) = config.components.raw.get(EchoBundle::config_key()).cloned() {
        EchoBundle::from_toml(raw)?.register_all(&mut ctx);
    } else {
        // No config block โ†’ use defaults
        EchoBundle {
            config: EchoConfig::default(),
        }
        .register_all(&mut ctx);
    }
- route:
    id: echo-demo
    from: timer:tick?period=2000
    steps:
      - to: echo:hello

In main, read the config block from CamelConfig and call register_all. Fall back to defaults when the block is absent. The route references echo:hello like any built-in scheme. The timer fires every two seconds, the producer logs the body, and the exchange continues down the pipeline.

Reference: Component SPI ยท Example source