dockertest_server/server.rs
1/// Contains traits and helper functions for creating servers
2use std::collections::HashMap;
3
4use dockertest::{waitfor::WaitFor, Composition, Image, RunningContainer, Source};
5
6/// A configuration capable of configuring a [Server].
7///
8/// Types that implement this trait are intended to have a one-to-one
9/// relationship with a [Server] as specified by [Server::Config]. The
10/// implementation is left intentionally sparse in order to maximize the process
11/// of creating a container [Composition]. The [generate_composition] helper
12/// function is provided for creating a [Composition] from the usual required
13/// configuration options.
14///
15/// See also [Test][crate::test::Test].
16pub trait Config: Clone + Send + Sync {
17 fn into_composition(self) -> Composition;
18 fn handle(&self) -> &str;
19}
20
21/// A running instance of a specific container generated by a [Config].
22///
23/// Types that implement this trait are intended to have a one-to-one
24/// relationship with a [Config] as specified by [Server::Config]. When a
25/// [Test][crate::test::Test] is created it is passed one or more
26/// [Configs][Config] which determine what containers are brought up during the
27/// test. A [Server] represents a running container within the context of a
28/// single test. This is reflected by the fact that a [Server] is created using
29/// a [Config] as well as the runtime data provided by a [RunningContainer].
30///
31/// Types implementing this trait should provide as much utility to the end-user
32/// as possible for interacting with the running container. For example, if the
33/// container is a web server, this trait should provide functionality for
34/// obtaining it's URL.
35pub trait Server {
36 type Config: Config + 'static;
37
38 fn new(config: &Self::Config, container: &RunningContainer) -> Self;
39}
40
41/// A helper struct for creating [Compositions][Composition] from a set of
42/// common configuration parameters.
43///
44/// This type can be freely cast into a [Composition]. It is only intended for
45/// basic use-cases where limited control over how the [Composition] is
46/// configured is acceptable.
47pub struct ContainerConfig {
48 pub args: Vec<String>,
49 pub env: HashMap<String, String>,
50 pub handle: String,
51 pub name: String,
52 pub source: Source,
53 pub version: String,
54 pub ports: Option<Vec<(u32, u32)>>,
55 pub wait: Option<Box<dyn WaitFor>>,
56 pub bind_mounts: HashMap<String, String>,
57}
58
59#[allow(clippy::from_over_into)] // Only supports one-way casting
60impl Into<Composition> for ContainerConfig {
61 fn into(self) -> Composition {
62 let image = Image::with_repository(self.name)
63 .source(self.source)
64 .tag(self.version);
65 let mut comp = Composition::with_image(image);
66
67 if let Some(p) = self.ports {
68 for pair in p {
69 comp.port_map(pair.0, pair.1);
70 }
71 };
72
73 let mut composition = match self.wait {
74 Some(w) => comp
75 .with_cmd(self.args)
76 .with_env(self.env)
77 .with_wait_for(w)
78 .with_container_name(self.handle),
79 None => comp
80 .with_cmd(self.args)
81 .with_env(self.env)
82 .with_container_name(self.handle),
83 };
84
85 for (container_path, host_path) in &self.bind_mounts {
86 composition.bind_mount(host_path, container_path);
87 }
88 composition
89 }
90}
91
92/// A helper function for generating random handles.
93///
94/// The returned handle is a combination of the given name and a random 10
95/// character string.
96pub fn new_handle(name: &str) -> String {
97 format!("{}{}", name, crate::common::rand_string(10))
98}
99
100#[cfg(test)]
101mod tests {
102 #[test]
103 fn test_new_handle() {
104 let result = super::new_handle("test");
105 assert_eq!(result.len(), 14);
106 }
107}