API-Docker
view release on metacpan or search on metacpan
lib/API/Docker/Role/Filters.pm view on Meta::CPAN
The transport JSON-encodes a HashRef C<params> value on its own, so the
I<encoding> was never the problem. The I<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 C<filters> agree on it and document it by pointing here.
=head2 What it does
=over
=item * A value that is not an ArrayRef is wrapped into a one-element one, so
C<< { dangling => 'true' } >> means what it looks like it means.
=item * Each element is stringified, so C<< { stars => [3] } >> reaches the
wire as C<"3"> rather than as the number C<3>.
=item * A JSON boolean object (C<< JSON->true >>, C<< JSON->false >>) and the
ScalarRef form this distribution uses for JSON request bodies (C<\1>, C<\0>)
become the strings C<'true'> and C<'false'>.
=item * Anything else -- another ref, C<undef>, an empty string -- croaks.
=back
The result is a fresh HashRef; the caller's is never modified.
=head2 Why the boolean rewrite is bound to the type and not to the value
A helper that rewrote every true-ish value to C<'true'> would be wrong more
often than it was right. C<< { exited => [0] } >> asks for containers that
exited with status 0, C<< { stars => [0] } >> for images with no stars, and
C<< { label => [1] } >> for a label whose value is C<1> -- rewriting any of
those to C<'false'>/C<'true'> would silently ask a different question.
Binding it to the filter I<name> instead would need a table of which names
are boolean, per endpoint, kept in step with the daemon -- see
L</"What it deliberately does not do">.
So the rewrite is bound to the value's B<type>: a plain Perl C<1> carries no
claim of being a boolean and becomes the string C<"1">, while
C<< JSON->true >> and C<\1> carry exactly that claim, and are also the two
forms C<encode_json> would otherwise turn into a JSON C<true> -- which the
daemon rejects outright.
That leaves one form this role cannot recognise: perl 5.36's core booleans,
where C<< !!1 >> and C<< $x == $y >> produce a boolean the JSON encoder also
writes as C<true>. Stringified, those are C<"1"> and C<""> -- and C<"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.
=head2 What it deliberately does not do
It does not check filter B<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
C</containers/json> (C<bogusname is an invalid filter>), C</images/json>
(C<invalid image filter "danglin">), C</volumes>, C</networks> and
C</events>, and Docker validates C</plugins> the same way -- which is how the
Engine API reference's documented C<enable> turns out to be a hard error
where the daemon wants C<enabled> (see
L<API::Docker::API::Plugins/list>). 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. C<'yes'> for
C<dangling> is a well-formed filter that the daemon rejects
(C<strconv.ParseBool: parsing "yes">), and C<< { label => ['nope'] } >> is a
well-formed filter that simply matches nothing. Both are the caller's
question to get right.
=head2 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, C<< 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.
=head1 METHODS
C<_normalise_filters($filters)> is private and composed into the resource
classes. It takes what the caller passed as C<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.
=head1 SEE ALSO
=over
=item * L<API::Docker::API::Containers> - C<list>, C<prune>
=item * L<API::Docker::API::Images> - C<list>, C<search>, C<prune>,
C<build_prune>
=item * L<API::Docker::API::Networks> - C<list>, C<prune>
=item * L<API::Docker::API::Volumes> - C<list>, C<prune>
=item * L<API::Docker::API::System> - C<events>
=item * L<API::Docker::API::Secrets> - C<list>
=item * L<API::Docker::API::Configs> - C<list>
=item * L<API::Docker::API::Plugins> - C<list>, whose filter names the daemon
validates
=back
=head1 SUPPORT
=head2 Issues
Please report bugs and feature requests on GitHub at
L<https://github.com/Getty/p5-api-docker/issues>.
=head1 CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
=head1 AUTHOR
Torsten Raudssus <getty@cpan.org>
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> L<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.
=cut
( run in 1.317 second using v1.01-cache-2.11-cpan-8dfa8b56332 )