NAME
API::Docker::Error::HTTP - Error status returned by the Docker Engine on the status line
VERSION
version 0.004
SYNOPSIS
eval { $docker->containers->kill($id) };
if (my $err = $@) {
# Behaves exactly like the string it replaces ...
warn "kill failed: $err";
# ... and carries the status code, so 404 and 409 are told apart
# without matching on prose the engine is free to change.
if (ref $err && $err->isa('API::Docker::Error::HTTP')) {
return if $err->status == 404; # already gone
sleep 1 if $err->status == 409; # wrong state, retry
}
}
DESCRIPTION
API::Docker::Role::HTTP croaks with an object of this class whenever the engine answers a request with a status of 400 or above.
The reason it exists is that the croak text is not an interface. What the engine puts in the error body is engine-specific prose: a kill against a stopped container answers 409 with can only kill running containers. <id> is in state stopped: container state improper on rootless Podman 5.4.2, while Docker's own example for the same case is Container <id> is not running -- a different body shape and entirely different wording. A caller that has to tell "no such container" from "wrong state" apart had no choice but to match that prose. "status" is the same distinction as a number the engine documents.
It is still the string it replaces
Everything this class replaces was a plain croak of a string, and callers rely on that. It overloads stringification (with fallback => 1, so comparison, concatenation, sprintf and matching all work through it) and produces byte for byte what croak died with before: the same Docker API error (STATUS): REASON text, followed by Carp's own at FILE line N. location suffix, naming the same frame. Code written against the old behaviour keeps working unchanged:
eval { $docker->containers->inspect($id) };
if ($@) {
(my $reason = $@) =~ s/\s+at\s+\S+\s+line\s+\d+\.?//g; # still works
die "no good: $@"; # still works
warn $@ if $@ =~ /404/; # still works
}
Note that a substitution on $@ replaces the object in that scalar with a plain string, as it would with any overloaded object, so take a copy first if "status" is still wanted afterwards.
The boolean overload is explicit rather than derived from the string, so an engine message of 0 cannot make a live exception test false.
What it does not replace
Catching this class is not a reliable way to catch a failed operation, and the POD of the streaming methods still says to inspect $@ as a string. Two exception classes reach a caller and which one it is depends on the engine: a failure the daemon decides before it commits to a status arrives here, while one it decides after arrives as an API::Docker::Error::Stream inside a stream that was already answered with HTTP 200. ->status is the extra for a caller that has already established it is holding one of these, not the new recommended way to detect failure.
The response out-parameter of "get" in API::Docker::Role::HTTP is untouched by this class and is not superseded by it: it is the only way to the status of a request that did not fail -- a 304 Not Modified from starting an already-running container, or the X-Docker-Container-Path-Stat header a successful HEAD carries its whole payload in.
message
The reason on its own, without the location suffix: the same Docker API error (STATUS): REASON text the transport croaked before this class existed, where REASON is the engine's message field, its errorDetail.message, its flat error key or the raw body, in that order of preference.
location
Carp's location suffix ( at FILE line N.\n), captured at the point the error was raised so it names the same frame a plain croak would have named. Kept apart from "message" so a caller can have the reason without it.
status
The HTTP status code: 404, 409, 500. This is the whole point of the class -- the one part of an engine error that is documented per endpoint and identical across engines.
It arrives off the status line as a string of digits, exactly as $res{status} from API::Docker::Role::HTTP's response option does, so compare it numerically ($err->status == 404) rather than relying on a type.
reason
The status line's reason phrase as the engine sent it (Not Found, Conflict). Informational: it comes off the wire, not from a table, so it is no more of a stable interface than the error body's prose. Branch on "status".
body
The response body verbatim, before any decoding -- the bytes the engine sent. Empty string when it sent none.
data
The decoded body, or undef when there was nothing to decode or decoding failed. Usually the HashRef the engine's {"message":...} shape decodes to, which is where an engine-specific extra such as Podman's cause key can be read; an array-shaped body decodes to an ArrayRef, so check the ref before subscripting it.
as_string
my $text = $err->as_string; # same as "$err"
The message and the location suffix, concatenated. This is what the stringification overload returns.
SEE ALSO
API::Docker::Role::HTTP - Raises this error; see its
responseoption for the status of a request that did not failAPI::Docker::Error::Stream - Raised instead for a failure reported inside a stream the daemon already answered with HTTP 200
SUPPORT
Issues
Please report bugs and feature requests on GitHub at https://github.com/Getty/p5-api-docker/issues.
CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
AUTHOR
Torsten Raudssus <getty@cpan.org>
COPYRIGHT AND LICENSE
This software is copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.