NAME
API::Docker::Role::Filters - The filters query parameter, normalised into the one shape the engine reads
VERSION
version 0.004
SYNOPSIS
package API::Docker::API::Whatever;
use Moo;
with 'API::Docker::Role::Filters';
sub list {
my ($self, %opts) = @_;
my %params;
$params{filters} = $self->_normalise_filters($opts{filters})
if defined $opts{filters};
return $self->client->get('/whatever', params => \%params);
}
DESCRIPTION
Every list and prune endpoint of the Engine API takes a filters query parameter, and every one of them wants the same thing: a JSON map of string to array of string.
filters => { dangling => ['true'] } correct
filters => { dangling => 'true' } wrong -- not an array
filters => { dangling => 1 } wrong -- not an array
filters => { dangling => [1] } wrong -- a number, not a string
filters => { dangling => [\1] } wrong -- a JSON boolean
The transport JSON-encodes a HashRef params value on its own, so the encoding was never the problem. The shape is, and it is the thing clients get wrong, because Perl has no notion of "array of string" and a HashRef literal will happily hold whatever the caller typed.
This role normalises that shape in one place, so the twelve methods that accept filters agree on it and document it by pointing here.
What it does
A value that is not an ArrayRef is wrapped into a one-element one, so
{ dangling => 'true' }means what it looks like it means.Each element is stringified, so
{ stars => [3] }reaches the wire as"3"rather than as the number3.A JSON boolean object (
JSON->true,JSON->false) and the ScalarRef form this distribution uses for JSON request bodies (\1,\0) become the strings'true'and'false'.Anything else -- another ref,
undef, an empty string -- croaks.
The result is a fresh HashRef; the caller's is never modified.
Why the boolean rewrite is bound to the type and not to the value
A helper that rewrote every true-ish value to 'true' would be wrong more often than it was right. { exited => [0] } asks for containers that exited with status 0, { stars => [0] } for images with no stars, and { label => [1] } for a label whose value is 1 -- rewriting any of those to 'false'/'true' would silently ask a different question.
Binding it to the filter name instead would need a table of which names are boolean, per endpoint, kept in step with the daemon -- see "What it deliberately does not do".
So the rewrite is bound to the value's type: a plain Perl 1 carries no claim of being a boolean and becomes the string "1", while JSON->true and \1 carry exactly that claim, and are also the two forms encode_json would otherwise turn into a JSON true -- which the daemon rejects outright.
That leaves one form this role cannot recognise: perl 5.36's core booleans, where !!1 and $x == $y produce a boolean the JSON encoder also writes as true. Stringified, those are "1" and "" -- and "1" is a value the daemon reads as true, so only the false one needs help. It is the reason an empty string croaks here rather than travelling on.
What it deliberately does not do
It does not check filter names. Doing so would need one accepted-name table per endpoint, and the daemon already has them: measured against Podman 5.x (API 1.41), an unknown name is refused with HTTP 500 by /containers/json (bogusname is an invalid filter), /images/json (invalid image filter "danglin"), /volumes, /networks and /events, and Docker validates /plugins the same way -- which is how the Engine API reference's documented enable turns out to be a hard error where the daemon wants enabled (see "list" in API::Docker::API::Plugins). A client-side table would duplicate that check, and the first time it lagged the daemon it would refuse a filter the daemon accepts. That is a worse failure than the one it prevents.
It also does not check that a value makes sense for its filter. 'yes' for dangling is a well-formed filter that the daemon rejects (strconv.ParseBool: parsing "yes"), and { label => ['nope'] } is a well-formed filter that simply matches nothing. Both are the caller's question to get right.
The daemon's side of each rule
Every rule above is a measured response, not a reading of the reference. Against Podman on API 1.41, GET /images/json:
{"dangling":["true"]} 200, the dangling images
{"dangling":"true"} 500 json: cannot unmarshal string into Go value
of type []string
{"dangling":true} 500 json: cannot unmarshal bool into Go value of
type []string
{"dangling":[true]} 500 json: cannot unmarshal bool into Go value of
type string
{"dangling":[1]} 500 json: cannot unmarshal number into Go value
of type string
{"dangling":[null]} 500 non-boolean value for filter:
strconv.ParseBool: parsing ""
{"dangling":[""]} 500 the same -- Go reads a JSON null into a
string as ""
{"dangling":["1"]} 200, and so do "0", "true" and "false"
So a wrong shape is not silent on this engine -- it is a 500 carrying a Go type error, one round trip later, naming neither the option nor the key the caller got wrong. What this role changes is where that is said: at the call, in terms of the argument, and for the recoverable shapes not at all, because they are repaired instead.
METHODS
_normalise_filters($filters) is private and composed into the resource classes. It takes what the caller passed as filters and returns the HashRef to hand to the transport as a query parameter; it croaks rather than sending a shape the daemon will refuse.
SEE ALSO
API::Docker::API::Containers -
list,pruneAPI::Docker::API::Images -
list,search,prune,build_pruneAPI::Docker::API::Networks -
list,pruneAPI::Docker::API::Volumes -
list,pruneAPI::Docker::API::System -
eventsAPI::Docker::API::Secrets -
listAPI::Docker::API::Configs -
listAPI::Docker::API::Plugins -
list, whose filter names the daemon validates
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.