Skip to main content

rightsize_modules/
redis.rs

1//! A single-node Redis container.
2
3use rightsize::{Container, ContainerGuard, Result, Wait};
4
5/// A single-node Redis container. Readiness is anchored on Redis's own
6/// "Ready to accept connections" log line rather than a TCP probe: on a loaded
7/// host the port forwarder can accept and hold a connection in the window
8/// between Redis binding its socket and actually serving, which a bare
9/// listening-port check cannot see through.
10pub struct RedisContainer(Container);
11
12impl RedisContainer {
13    /// The guest port Redis listens on.
14    const PORT: u16 = 6379;
15
16    /// Builds a container from the pinned default image (`redis:8.6-alpine`).
17    pub fn new() -> Self {
18        Self::with_image("redis:8.6-alpine")
19    }
20
21    /// Builds a container from a caller-chosen image.
22    pub fn with_image(image: &str) -> Self {
23        Self(
24            Container::new(image)
25                .with_exposed_ports(&[Self::PORT])
26                .waiting_for(Wait::for_log_message(".*Ready to accept connections.*", 1)),
27        )
28    }
29
30    /// Boots the container.
31    pub async fn start(self) -> Result<RedisGuard> {
32        crate::register_default_backends();
33        Ok(RedisGuard(self.0.start().await?))
34    }
35}
36
37impl Default for RedisContainer {
38    fn default() -> Self {
39        Self::new()
40    }
41}
42
43/// The running guard for a [`RedisContainer`].
44pub struct RedisGuard(ContainerGuard);
45
46impl RedisGuard {
47    /// A `redis://` connection URI for the running container.
48    pub fn uri(&self) -> String {
49        format!(
50            "redis://{}:{}",
51            self.0.host(),
52            self.0.get_mapped_port(RedisContainer::PORT).unwrap()
53        )
54    }
55
56    /// Stops and removes the container, releasing its host port.
57    pub async fn stop(self) -> Result<()> {
58        self.0.stop().await
59    }
60}
61
62impl std::ops::Deref for RedisGuard {
63    type Target = ContainerGuard;
64    fn deref(&self) -> &ContainerGuard {
65        &self.0
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn with_image_exposes_the_redis_port_and_waits_for_a_listening_port() {
75        // No direct field access from outside this module — this is a smoke check that
76        // construction doesn't panic and picks the documented default image via `new`.
77        let _ = RedisContainer::new();
78        let _ = RedisContainer::with_image("redis:7-alpine");
79    }
80}