1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
/// Contains traits and helper functions for creating servers
use std::collections::HashMap;

use dockertest::{waitfor::WaitFor, Composition, Image, RunningContainer, Source};

/// A configuration capable of configuring a [Server].
///
/// Types that implement this trait are intended to have a one-to-one
/// relationship with a [Server] as specified by [Server::Config]. The
/// implementation is left intentionally sparse in order to maximize the process
/// of creating a container [Composition]. The [generate_composition] helper
/// function is provided for creating a [Composition] from the usual required
/// configuration options.
///
/// See also [Test][crate::test::Test].
pub trait Config: Clone + Send + Sync {
    fn into_composition(self) -> Composition;
    fn handle(&self) -> &str;
}

/// A running instance of a specific container generated by a [Config].
///
/// Types that implement this trait are intended to have a one-to-one
/// relationship with a [Config] as specified by [Server::Config]. When a
/// [Test][crate::test::Test] is created it is passed one or more
/// [Configs][Config] which determine what containers are brought up during the
/// test. A [Server] represents a running container within the context of a
/// single test. This is reflected by the fact that a [Server] is created using
/// a [Config] as well as the runtime data provided by a [RunningContainer].
///
/// Types implementing this trait should provide as much utility to the end-user
/// as possible for interacting with the running container. For example, if the
/// container is a web server, this trait should provide functionality for
/// obtaining it's URL.
pub trait Server {
    type Config: Config + 'static;

    fn new(config: &Self::Config, container: &RunningContainer) -> Self;
}

/// A helper struct for creating [Compositions][Composition] from a set of
/// common configuration parameters.
///
/// This type can be freely cast into a [Composition]. It is only intended for
/// basic use-cases where limited control over how the [Composition] is
/// configured is acceptable.
pub struct ContainerConfig {
    pub args: Vec<String>,
    pub env: HashMap<String, String>,
    pub handle: String,
    pub name: String,
    pub source: Source,
    pub version: String,
    pub ports: Option<Vec<(u32, u32)>>,
    pub wait: Option<Box<dyn WaitFor>>,
}

#[allow(clippy::from_over_into)] // Only supports one-way casting
impl Into<Composition> for ContainerConfig {
    fn into(self) -> Composition {
        let image = Image::with_repository(self.name)
            .source(self.source)
            .tag(self.version);
        let mut comp = Composition::with_image(image);

        if let Some(p) = self.ports {
            for pair in p {
                comp.port_map(pair.0, pair.1);
            }
        };

        match self.wait {
            Some(w) => comp
                .with_cmd(self.args)
                .with_env(self.env)
                .with_wait_for(w)
                .with_container_name(self.handle),
            None => comp
                .with_cmd(self.args)
                .with_env(self.env)
                .with_container_name(self.handle),
        }
    }
}

/// A helper function for generating random handles.
///
/// The returned handle is a combination of the given name and a random 10
/// character string.
pub fn new_handle(name: &str) -> String {
    format!("{}{}", name, crate::common::rand_string(10))
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_new_handle() {
        let result = super::new_handle("test");
        assert_eq!(result.len(), 14);
    }
}