API-Docker
view release on metacpan or search on metacpan
lib/API/Docker/Role/Filters.pm view on Meta::CPAN
package API::Docker::Role::Filters;
# ABSTRACT: The filters query parameter, normalised into the one shape the engine reads
our $VERSION = '0.004';
use Moo::Role;
use Carp qw( croak );
use namespace::clean;
# The boolean classes JSON::MaybeXS hands back across its backends. Named
# rather than duck-typed: a blessed object that merely overloads bool is not
# a claim of being a JSON boolean.
my %BOOLEAN_CLASS = map { $_ => 1 } qw(
JSON::PP::Boolean
Types::Serialiser::Boolean
);
sub _normalise_filters {
my ($self, $filters) = @_;
croak __PACKAGE__ . '->_normalise_filters filters must be a HashRef of '
. 'filter name to value, e.g. { dangling => [\'true\'] }'
unless ref $filters eq 'HASH';
my %normalised;
for my $name (sort keys %$filters) {
croak __PACKAGE__ . '->_normalise_filters filter name must not be empty'
unless length $name;
my $value = $filters->{$name};
# A bare value is one value, not a mistake worth refusing -- the engine
# is the one that insists on the list.
my @values = ref $value eq 'ARRAY' ? @$value : ($value);
$normalised{$name} = [ map { $self->_normalise_filter_value($name, $_) } @values ];
}
return \%normalised;
}
sub _normalise_filter_value {
my ($self, $name, $value) = @_;
my $where = __PACKAGE__ . '->_normalise_filters filter \'' . $name . '\' ';
croak $where . 'has an undefined value; the engine reads a JSON null into '
. 'a string as the empty string and rejects it there'
unless defined $value;
my $ref = ref $value;
return $value ? 'true' : 'false' if $BOOLEAN_CLASS{$ref};
if ($ref eq 'SCALAR') {
croak $where . 'is a ScalarRef to something other than 1 or 0; \\1 and '
. '\\0 are read as the booleans \'true\' and \'false\''
unless $$value eq '1' || $$value eq '0';
return $$value ? 'true' : 'false';
}
croak $where . 'has a ' . $ref . ' reference as a value; filter values are '
. 'strings, or an ArrayRef of them' if $ref;
croak $where . 'has an empty value; the engine rejects it. A Perl boolean '
. 'stringifies to \'\' when false -- the engine wants the string '
. '\'false\''
unless length $value;
# Stringify a copy: a scalar carrying a number would otherwise be
# JSON-encoded as one, and the engine's filter values are strings.
return "$value";
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
API::Docker::Role::Filters - The filters query parameter, normalised into the one shape the engine reads
=head1 VERSION
version 0.004
=head1 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);
}
=head1 DESCRIPTION
Every C<list> and C<prune> endpoint of the Engine API takes a C<filters>
query parameter, and every one of them wants the same thing: a JSON B<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 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
( run in 0.933 second using v1.01-cache-2.11-cpan-54e63673c56 )