camel-api 0.61.0

Core traits and interfaces for rust-camel
Documentation
//! Route lifecycle management types.
//!
//! This module provides the [`RouteController`] trait for managing route lifecycle
//! operations (start, stop, suspend, resume) plus [`RouteStatus`] and [`RouteAction`]
//! enums used by runtime-facing APIs.

use crate::CamelError;
use async_trait::async_trait;

/// Represents the current lifecycle status of a route.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum RouteStatus {
    /// Route is stopped and not running.
    Stopped,
    /// Route is in the process of starting.
    Starting,
    /// Route is running and processing messages.
    Started,
    /// Route is in the process of stopping.
    Stopping,
    /// Route is suspended (temporarily paused).
    Suspended,
    /// Route has failed with an error message.
    Failed(String),
}

/// Represents actions that can be performed on a route.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum RouteAction {
    /// Start the route.
    Start,
    /// Stop the route.
    Stop,
    /// Suspend the route (pause without full shutdown).
    Suspend,
    /// Resume a suspended route.
    Resume,
    /// Restart the route (stop then start).
    Restart,
    /// Get the current status of the route.
    Status,
}

/// Trait for managing route lifecycle operations.
///
/// Implementations provide imperative execution operations only; canonical
/// lifecycle state is exposed via runtime query APIs.
#[async_trait]
pub trait RouteController: Send + Sync {
    /// Start a specific route by its ID.
    ///
    /// # Errors
    ///
    /// Returns a `CamelError` if the route cannot be started.
    async fn start_route(&mut self, route_id: &str) -> Result<(), CamelError>;

    /// Stop a specific route by its ID.
    ///
    /// # Errors
    ///
    /// Returns a `CamelError` if the route cannot be stopped.
    async fn stop_route(&mut self, route_id: &str) -> Result<(), CamelError>;

    /// Stop a route with a graceful drain budget.
    ///
    /// Implementations may bound their drain phase by `budget`. The default
    /// delegates to [`Self::stop_route`], ignoring the budget.
    ///
    /// # Errors
    ///
    /// Returns a `CamelError` if the route cannot be stopped.
    async fn stop_route_with_budget(
        &mut self,
        route_id: &str,
        _budget: std::time::Duration,
    ) -> Result<(), CamelError> {
        self.stop_route(route_id).await
    }

    /// Restart a specific route by its ID.
    ///
    /// # Errors
    ///
    /// Returns a `CamelError` if the route cannot be restarted.
    async fn restart_route(&mut self, route_id: &str) -> Result<(), CamelError>;

    /// Suspend a specific route by its ID.
    ///
    /// # Errors
    ///
    /// Returns a `CamelError` if the route cannot be suspended.
    async fn suspend_route(&mut self, route_id: &str) -> Result<(), CamelError>;

    /// Resume a suspended route by its ID.
    ///
    /// # Errors
    ///
    /// Returns a `CamelError` if the route cannot be resumed.
    async fn resume_route(&mut self, route_id: &str) -> Result<(), CamelError>;

    /// Start all routes in the context.
    ///
    /// # Errors
    ///
    /// Returns a `CamelError` if any route cannot be started.
    async fn start_all_routes(&mut self) -> Result<(), CamelError>;

    /// Stop all routes in the context.
    ///
    /// # Errors
    ///
    /// Returns a `CamelError` if any route cannot be stopped.
    async fn stop_all_routes(&mut self) -> Result<(), CamelError>;
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::{Arc, Mutex};

    struct RecordingController {
        stopped: Arc<Mutex<Vec<String>>>,
    }

    #[async_trait]
    impl RouteController for RecordingController {
        async fn start_route(&mut self, _route_id: &str) -> Result<(), CamelError> {
            unimplemented!()
        }

        async fn stop_route(&mut self, route_id: &str) -> Result<(), CamelError> {
            self.stopped
                .lock()
                .expect("stopped lock poisoned")
                .push(route_id.to_string());
            Ok(())
        }

        async fn restart_route(&mut self, _route_id: &str) -> Result<(), CamelError> {
            unimplemented!()
        }

        async fn suspend_route(&mut self, _route_id: &str) -> Result<(), CamelError> {
            unimplemented!()
        }

        async fn resume_route(&mut self, _route_id: &str) -> Result<(), CamelError> {
            unimplemented!()
        }

        async fn start_all_routes(&mut self) -> Result<(), CamelError> {
            unimplemented!()
        }

        async fn stop_all_routes(&mut self) -> Result<(), CamelError> {
            unimplemented!()
        }
    }

    #[tokio::test]
    async fn stop_route_with_budget_default_delegates() {
        let stopped = Arc::new(Mutex::new(Vec::new()));
        let mut controller = RecordingController {
            stopped: stopped.clone(),
        };

        let result = controller
            .stop_route_with_budget("r1", std::time::Duration::from_secs(10))
            .await;

        assert!(result.is_ok(), "default impl must return Ok");
        assert_eq!(*stopped.lock().expect("stopped lock poisoned"), vec!["r1"]);
    }
}