NAME

API::Docker::Role::Using - A resource class clone that bounds a run of calls

VERSION

version 0.004

SYNOPSIS

my $docker = API::Docker->new(read_timeout => 30);

# the rule, for this client
$docker->containers->list;

# the exception, for these calls
my $quick = $docker->containers->using(read_timeout => 5);
$quick->list;
$quick->inspect($id);

# or in passing
$docker->images->using(connect_timeout => 2, read_timeout => 60)
  ->pull(fromImage => 'alpine');

DESCRIPTION

"read_timeout" in API::Docker::Role::HTTP and "connect_timeout" in API::Docker::Role::HTTP are attributes of the client, so they are set once and hold for every request it makes. That is the right level for a rule and the wrong one for an exception: a client bounded at 30 seconds is no help to the one call that must give up after 2, and a client bounded at 2 cannot pull an image.

using is the exception. It returns a clone of the resource class carrying the options, and every request made through that clone is given them:

$docker->containers->using(read_timeout => 5)->list;

Only the two transport bounds may be carried. Everything else a request needs -- query parameters, the body, a streaming callback -- is an argument of the method that builds it, and a value carried past that method could only either overwrite what it built or be overwritten by it.

The clone and the client

The clone holds the same client, on the same terms: the resource classes hold it as a weak_ref, and the clone does too. So a clone never keeps a client alive that the caller has let go, and never becomes the reason a client outlives its scope.

The other side of that is the footgun this distribution already has:

my $quick = API::Docker->new->containers->using(read_timeout => 5);
$quick->list;   # dies: the client was gone at the end of the first line

Keep the client in a variable. That is not new here: the entity classes hold it weakly for the same reason, so API::Docker->new->images->list has always handed back images whose client was already gone.

Nothing else is shared: the options live on the clone, the original resource class is not touched, and two clones of one resource class know nothing of each other.

my $r = $docker->containers;
my $a = $r->using(read_timeout => 5);
my $b = $r->using(read_timeout => 60);
# $r is still unbounded, $a is 5, $b is 60

Chaining merges, key by key

$docker->images->using(connect_timeout => 2)->using(read_timeout => 60)

carries both, and a repeated key takes the later value:

->using(read_timeout => 60)->using(read_timeout => 0)   # 0 wins

Merging rather than replacing, because the two bounds are independent: a helper that hands out a resource class with a connect bound already on it, and a caller that then tightens the read bound, are both saying what they mean -- and a using that dropped the other half would do it silently, which is the one outcome neither of them could have wanted.

What it refuses

Both are croaks rather than a shrug, because the failure they replace is invisible: an option this role kept and no request read would leave the caller believing a bound is in force that is not.

  • An unknown option. ->using(read_timout => 5) is a typo, and carrying it would bound nothing while looking exactly like a call that does.

  • No options at all. ->using() asks for a clone that differs from the original in nothing. Where the pairs are computed rather than written, decide it at the call:

    my $r = %bounds ? $docker->containers->using(%bounds) : $docker->containers;

An odd number of arguments croaks too, before the pairs are read.

What has no clone of its own

The entity classes. $container->logs and its neighbours are one-line delegations to the resource class, and they hold the container's own daemon fields -- every one of them, verbatim -- rather than a call surface, so a clone would have to copy a record whose shape is the daemon's. The bound belongs where the request is built:

$docker->containers->using(read_timeout => 5)->logs($container->id);

The client. $docker->using(...) would be a second client sharing one connection state and one negotiated API version with the first. The client already takes both bounds as constructor arguments, which is the level it works at.

using

my $bounded = $docker->containers->using(read_timeout => 5);

Returns a clone of the resource class that hands read_timeout and connect_timeout to every request made through it. Takes those two options and no others; an unknown one, an odd number of arguments, and an empty call all croak.

What it carries is held in _request_options, which is private and composed into the resource classes alongside it: {} on a resource class nobody called using on, and spliced by each resource method into the option list it hands the transport.

SEE ALSO

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.