NAME

UserAgent::Any::Wrapper – Helpers to write higher level client libraries on top of UserAgent::Any, while retaining its benefit (support of any user agent, all HTTP verbs, multiple asynchronous models, etc.).

SYNOPSIS

wrap_method('bar' => 'foo', sub ($self, @args) { make_args_for_foo($@args) });

DESCRIPTION

This section describes how to write higher level client libraries, on top of UserAgent::Any, while retaining its benefit (support for any user agents and multiple asynchronous models).

You should first read the documentation of the "UserAgent::Any::get() method"|UserAgent::Any/get> to understand the asynchronous models implemented by UserAgent::Any.

Wrapping a class method

wrap_method($name => $delegate, sub ($self, ...) { ... }, sub ($self, $res, ...));

The call above will generate in your class a set of methods named $name, $name_cb, and $name_p. These methods will each execute the first sub that was passed to wrap_method() and then pass the results to the method named with $delegate and a suffix matching that of the function called. The result of that call if passed to the second sub, if it was provided, in the right async context (in a callback or a then method of a promise).

For example, if you have a class that can already handle methods foo, foo_cb, and foo_p with the same semantics as the user agent methods above (this will typically be the methods for UserAgent::Any itself) and you want to expose a method bar that depends on foo you can write:

wrap_method('bar' => 'foo', sub ($self, @args) { make_args_for_foo($@args) });

And this will expose in your package a set of bar, bar_cb, and bar_p methods with the same semantics that will use the provided method reference to build the arguments to foo. For the synchronous case, the method from the example above will be equivalent to:

sub bar ($self, @args) { $self->foo($self, make_args_for_foo(@args))}

You can optionally pass a second callback that will be called with the response from the wrapped method:

wrap_method($name => $delegate, $cb,
            sub ($self, $res, @args) { process_results($res) });

The second callback will be called with the current object, the response from the wrapped method and the arguments that were passed to the wrappers (the same that were already passed to the first callback). The wrapped method will be called in list context. If it returns exactly 1 result, then that result is passed as-is to the second callback; if it returns 0 result, then the callback will receive undef; otherwise the callback will receive an array reference with the result of the call.

The call above is equivalent to the following code (for the synchronous case):

sub bar ($self, @args) {
  process_results($self->foo($self, make_args_for_foo(@args)));
}

If you don’t pass a second callback, then the callback, promise or method will return the default result from the invoked method, without any transformation.

Wrapping a method of a class member

wrap_method($name => \&getter, $delegate, $cb1[, $cb2]);

Alternatively to the above, wrap_method can be used to wrap a method of a class member. Instead of calling the method named $delegate in your class, the call above will call the method named $delegate on the reference returned by the call to &getter (that must be a class method).

Wrapping all the UserAgent::Any methods

wrap_all_methods(\&getter, $cb1[, $cb2]);

This call will generates in your package all the methods exposed by UserAgent::Any (get(), post(), etc. and their asynchronous variants) by wrapping the similarly named methods of the object returned by getter().

The two callbacks $cb1 and $cb2 are used in the same way as described above for all the calls.

Alternatively, you can use the following call:

wrap_all_methods($package, $cb1[, $cb2]);

That one will also generate the same set of methods wrapping methods of the object itself coming from a base class given by $package (so ${package}::get, ${package}::post_cb, etc.). The package will typically name a base class of your class.

In addition, there are two other functions wrap_post_like_methods() and wrap_get_like_methods() with the same signature (also accepting a getter or a package name) which will generate a subset of all the user agent methods, respectively for the methods that not expecting a request body (like GET) and for those that are expecting a request body (like POST), in case you need to use different callbacks for these two scenarios.

Example

Here is are two minimal example on how to create a client library for a hypothetical service exposing a create call using the POST method.

package MyPackage;

use 5.036;

use Moo;
use UserAgent::Any 'wrap_method';

use namespace::clean;

extends 'UserAgent::Any';

wrap_method(create_document => 'post', sub ($self, %opts) {
  return ('https://example.com/create/'.$opts{document_id}, $opts{content});
});

Or, if you don’t want to re-expose the UserAgent::Any method in your class directly (possibly because you want to re-use the same names), you can do:

package MyPackage;

use 5.036;

use Moo;
use UserAgent::Any 'wrap_method';

use namespace::clean;

has ua => (
  is => 'ro',
  coerce => sub { UserAgent::Any->new($_[0]) },
  required => 1,
);

wrap_method(create_document => \&ua => 'post', sub ($self, %opts) {
  return ('https://example.com/create/'.$opts{document_id}, $opts{content});
});

AUTHOR

Mathias Kende <mathias@cpan.org>

COPYRIGHT AND LICENSE

Copyright 2024 Mathias Kende

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

SEE ALSO