///|
/// Every fallible testcontainers operation raises `TestContainerError`.
///
/// The payloads carry enough context to debug a failed test without re-running
/// it: the exact docker command, the exit status, the container's output and,
/// for wait timeouts, the container logs.
pub(all) suberror TestContainerError {
/// The `docker` executable could not be spawned at all. This usually means
/// Docker is not installed or not on `PATH`.
DockerNotAvailable(String)
/// A docker command exited with a non-zero status.
DockerCommandFailed(command~ : String, code~ : Int32, stderr~ : String)
/// Pulling the image failed.
ImagePullFailed(image~ : String, stderr~ : String)
/// The container never became ready before the wait timeout expired.
WaitTimeout(strategy~ : String, timeout_ms~ : Int32, logs~ : String)
/// The container port was not published, so no host port could be resolved.
PortNotMapped(container_port~ : Int32, container_id~ : String)
/// The container request is malformed (bad mount, bad port, ...).
InvalidRequest(String)
/// The container was expected to be running but is not.
ContainerNotRunning(container_id~ : String, status~ : String)
/// Output of a docker command could not be parsed.
UnexpectedOutput(String)
/// A filesystem operation outside docker failed (e.g. creating a temp dir).
IoFailed(String)
} derive(Debug)
///|
pub impl Show for TestContainerError with fn to_string(self) {
match self {
DockerNotAvailable(message) =>
"Docker is not available: \{message}\n" +
"Install Docker and make sure the `docker` CLI is on your PATH:\n" +
" https://docs.docker.com/get-docker/"
DockerCommandFailed(command~, code~, stderr~) =>
"docker command failed with exit code \{code}:\n" +
" $\{command}\n" +
(if stderr == "" { "(no output)" } else { stderr })
ImagePullFailed(image~, stderr~) =>
"failed to pull image `\{image}`:\n" + stderr
WaitTimeout(strategy~, timeout_ms~, logs~) =>
"container did not become ready within \{timeout_ms} ms " +
"(wait strategy: \{strategy}).\n" +
"Last container logs:\n" +
(if logs == "" { "(no logs)" } else { logs })
PortNotMapped(container_port~, container_id~) =>
"container \{short_id(container_id)} does not publish port \{container_port}. " +
"Add it with `ContainerRequest::with_port(\{container_port})`."
InvalidRequest(message) => "invalid container request: \{message}"
ContainerNotRunning(container_id~, status~) =>
"container \{short_id(container_id)} is not running (status: \{status})"
UnexpectedOutput(message) => "unexpected docker output: \{message}"
IoFailed(message) => "I/O error: \{message}"
}
}
///|
/// Promote the trait methods to inherent methods so `err.to_string()` works
/// without importing the traits.
pub extend TestContainerError with Show::{to_string, output}
///|
pub extend TestContainerError with Debug::{to_repr}
///|
/// Abbreviate a container id the way docker does (first 12 characters).
pub fn short_id(id : String) -> String {
if id.length() <= 12 {
return id
}
let chars = id.to_array()
let out : Array[Char] = []
for i = 0; i < 12; i = i + 1 {
out.push(chars[i])
}
String::from_array(out)
}