Files
rustybeds/src/services/amqp/mod.rs
gramps e8fdb39ea2 Promote service modules to services/ directory; add AmqpConnection + async IPL
- Flat src/amqp.rs, src/mongo.rs, src/mariadb.rs promoted to src/services/{amqp,mongo,mariadb}/
- services/amqp/connection.rs: AmqpConnection struct with connect() and declare_exchange()
- services/amqp/error.rs: AmqpError type (thiserror, wraps lapin::Error)
- ipl() made async; #[tokio::main] added to main()
- IPL step 3b: authenticate to RabbitMQ + declare beds.events topic exchange (durable)
- Added lapin = "2" and tokio = { version = "1", features = ["full"] } to Cargo.toml
- 12 unit tests pass
- Docs: README, CLAUDE.md, wiki/04-ipl.md, wiki/06-queue-topology.md updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 16:52:18 -07:00

87 lines
2.7 KiB
Rust

//! # 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());
}
}