//! # services/amqp/mod.rs — AMQP Transport Module //! //! RabbitMQ transport layer. Provides IPL reachability validation and the //! `AmqpConnection` struct for authenticated broker sessions. //! //! ## Public Surface //! - `validate()` — TCP reachability check (IPL step 3) //! - `AmqpConnection` — authenticated connection + channel (IPL step 3b) //! - `EXCHANGE_NAME` — the canonical `beds.events` exchange name constant //! //! **Author:** mks //! **Version:** 1.0 //! //! ## History //! * `2026-04-02` - mks - original coding (flat amqp.rs) //! * `2026-04-04` - mks - promoted to services/amqp/, added AmqpConnection struct pub mod connection; pub mod error; pub use connection::{AmqpConnection, EXCHANGE_NAME}; pub use error::AmqpError; // will be used by broker pool error handling use std::net::TcpStream; use std::time::Duration; use crate::config::BrokerServicesConfig; /// Validates that the RabbitMQ broker is reachable via TCP. /// /// Opens a TCP connection to the configured broker host and port. Does not /// authenticate or open an AMQP channel — reachability only. The connection /// is closed immediately after a successful connect. /// /// Called at IPL step 3 as a fast pre-flight check before the more expensive /// `AmqpConnection::connect()` authentication step. /// /// # Arguments /// /// * `cfg` — broker services configuration block from `BedsConfig` /// /// # Returns /// /// `Ok(())` if the TCP handshake succeeds within 5 seconds. /// `Err(String)` with a descriptive message if the broker cannot be reached. /// /// # History /// /// * `2026-04-02` - mks - original coding pub fn validate(cfg: &BrokerServicesConfig) -> Result<(), String> { let addr_str = format!("{}:{}", cfg.app_server.host, cfg.app_server.port); let addr: std::net::SocketAddr = addr_str .parse() .map_err(|e| format!("Invalid broker address {}: {}", addr_str, e))?; TcpStream::connect_timeout(&addr, Duration::from_secs(5)) .map_err(|e| format!("RabbitMQ unreachable at {}: {}", addr_str, e))?; Ok(()) } #[cfg(test)] mod tests { use super::*; use crate::config::load_from; fn test_cfg() -> crate::config::BedsConfig { load_from("tests/fixtures/beds_test.toml", "") .expect("test fixture beds_test.toml failed to load") } #[test] fn validate_err_on_closed_port() { let mut cfg = test_cfg(); cfg.broker_services.app_server.port = 1; assert!(validate(&cfg.broker_services).is_err()); } #[test] fn validate_err_on_bad_address() { let mut cfg = test_cfg(); cfg.broker_services.app_server.host = "not_a_valid_host!!!".to_string(); assert!(validate(&cfg.broker_services).is_err()); } }