Current section
Files
Jump to
Current section
Files
src/testcontainers_gleam.gleam
//// Gleam wrapper for the Elixir
//// [testcontainers](https://hex.pm/packages/testcontainers) library.
////
//// Provides functions to start and stop Docker containers in tests.
//// Use `testcontainers_gleam/container` to build a container definition,
//// then pass it to `start_container` to run it.
////
//// ## Example
////
//// ```gleam
//// import testcontainers_gleam
//// import testcontainers_gleam/container
////
//// let c =
//// container.new("redis:7.4-alpine")
//// |> container.with_exposed_port(6379)
////
//// let assert Ok(running) = testcontainers_gleam.start_container(c)
//// let id = container.container_id(running)
//// let assert Ok(port) = container.mapped_port(running, 6379)
////
//// let assert Ok(Nil) = testcontainers_gleam.stop_container(id)
//// ```
import gleam/dynamic.{type Dynamic}
import gleam/dynamic/decode
import gleam/erlang/atom
import gleam/result
import internal/testcontainers_ffi
import testcontainers_gleam/container.{type Container}
/// Result alias for testcontainers operations.
pub type ContainerResult(a) =
Result(a, ContainerError)
/// Error returned by testcontainers operations.
pub type ContainerError {
/// HTTP error from the Docker API with the status code.
HttpError(status: Int)
/// Failed to pull the container image.
FailedToPullImage
/// Failed to create the container.
FailedToCreateContainer
/// Failed to start the container.
FailedToStartContainer
/// Failed to inspect a running container.
FailedToGetContainer
/// A wait strategy timed out or failed.
WaitStrategyFailed
/// An unexpected error from the Elixir library.
Unknown
}
/// Start the Testcontainers GenServer.
///
/// This is called automatically by `start_container`, so you only
/// need to call it directly if you want to manage the lifecycle yourself.
/// Returns `Ok(Nil)` if the GenServer was started or is already running.
pub fn start_link() -> ContainerResult(Nil) {
case testcontainers_ffi.start_link() {
Ok(_) -> Ok(Nil)
Error(raw) -> {
let tag_decoder = decode.at([0], atom.decoder())
case decode.run(raw, tag_decoder) {
Ok(tag) ->
case atom.to_string(tag) {
"already_started" -> Ok(Nil)
_ -> Error(decode_error(raw))
}
Error(_) -> Error(decode_error(raw))
}
}
}
}
/// Start a container and wait until it is ready.
///
/// Automatically starts the Testcontainers GenServer if it is not
/// already running. Returns the started container on success, which
/// can be queried with `container.container_id` and `container.mapped_port`.
///
/// ## Example
///
/// ```gleam
/// let c =
/// container.new("redis:7.4-alpine")
/// |> container.with_exposed_port(6379)
///
/// let assert Ok(running) = testcontainers_gleam.start_container(c)
/// ```
pub fn start_container(container: Container) -> ContainerResult(Container) {
// Ensure the GenServer is running; ignore already-started errors.
let _ = testcontainers_ffi.start_link()
container
|> testcontainers_ffi.start()
|> result.map_error(decode_error)
}
/// Stop a running container by its container ID.
///
/// ## Example
///
/// ```gleam
/// let assert Ok(Nil) = testcontainers_gleam.stop_container(id)
/// ```
pub fn stop_container(container_id: String) -> ContainerResult(Nil) {
let raw = testcontainers_ffi.stop_container(container_id)
let decoded =
raw
|> decode.run(atom.decoder())
|> result.try(fn(tag) {
case atom.to_string(tag) {
"ok" -> Ok(Nil)
_ -> Error([])
}
})
case decoded {
Ok(_) -> Ok(Nil)
Error(_) -> Error(decode_error(raw))
}
}
/// Decode an error tuple from the Elixir library into a `ContainerError`.
fn decode_error(raw: Dynamic) -> ContainerError {
let tag_decoder = decode.at([0], atom.decoder())
case decode.run(raw, tag_decoder) {
Ok(tag) ->
case atom.to_string(tag) {
"http_error" -> {
let status_decoder = decode.at([1], decode.int)
let status = case decode.run(raw, status_decoder) {
Ok(s) -> s
Error(_) -> 0
}
HttpError(status)
}
"failed_to_pull_image" -> FailedToPullImage
"failed_to_create_container" -> FailedToCreateContainer
"failed_to_start_container" -> FailedToStartContainer
"failed_to_get_container" -> FailedToGetContainer
"log_wait_strategy" -> WaitStrategyFailed
"command_wait_strategy" -> WaitStrategyFailed
"port_wait_strategy" -> WaitStrategyFailed
_ -> Unknown
}
Error(_) -> Unknown
}
}