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        Ok(RedisGuard(self.0.start().await?))
33    }
34}
35
36impl Default for RedisContainer {
37    fn default() -> Self {
38        Self::new()
39    }
40}
41
42/// The running guard for a [`RedisContainer`].
43pub struct RedisGuard(ContainerGuard);
44
45impl RedisGuard {
46    /// A `redis://` connection URI for the running container.
47    pub fn uri(&self) -> String {
48        format!(
49            "redis://{}:{}",
50            self.0.host(),
51            self.0.get_mapped_port(RedisContainer::PORT).unwrap()
52        )
53    }
54
55    /// Stops and removes the container, releasing its host port.
56    pub async fn stop(self) -> Result<()> {
57        self.0.stop().await
58    }
59}
60
61impl std::ops::Deref for RedisGuard {
62    type Target = ContainerGuard;
63    fn deref(&self) -> &ContainerGuard {
64        &self.0
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn with_image_exposes_the_redis_port_and_waits_for_a_listening_port() {
74        // No direct field access from outside this module — this is a smoke check that
75        // construction doesn't panic and picks the documented default image via `new`.
76        let _ = RedisContainer::new();
77        let _ = RedisContainer::with_image("redis:7-alpine");
78    }
79}