API-Client
view release on metacpan or search on metacpan
lib/API/Client.pm view on Meta::CPAN
if (!ref $args->{url}) {
$args->{url} = Mojo::URL->new($args->{url}) if $args->{url};
}
return $args;
}
method build_self($args) {
if (!$self->{url} && $self->can('base')) {
$self->{url} = Mojo::URL->new(join('/', @{$self->base($args)}));
}
return $self;
}
# METHODS
method create(Any %args) {
return $self->dispatch(%args, method => 'post');
}
method delete(Any %args) {
return $self->dispatch(%args, method => 'delete');
}
method dispatch(Str :$method = 'get', Any %args) {
my $log = $self->logger->info("@{[uc($method)]} @{[$self->url->to_string]}");
my $result = $self->execute(%args, method => $method);
$log->end;
return $result;
}
method fetch(Any %args) {
return $self->dispatch(%args, method => 'get');
}
method patch(Any %args) {
return $self->dispatch(%args, method => 'patch');
}
method update(Any %args) {
return $self->dispatch(%args, method => 'put');
}
method prepare(Object $ua, Object $tx, Any %args) {
$self->set_auth($ua, $tx, %args);
$self->set_headers($ua, $tx, %args);
$self->set_identity($ua, $tx, %args);
return $self;
}
method process(Object $ua, Object $tx, Any %args) {
return $self;
}
method resource(Str @segments) {
my $url;
if (@segments) {
$url = $self->url->clone;
$url->path->merge(
join '/', '', @{$self->url->path->parts}, @segments
);
}
my $object = ref($self)->new(
%{$self->serialize}, ($url ? ('url', $url) : ())
);
return $object;
}
method serialize() {
return {
debug => $self->debug,
fatal => $self->fatal,
name => $self->name,
retries => $self->retries,
timeout => $self->timeout,
url => $self->url->to_string,
};
}
method set_auth($ua, $tx, %args) {
if ($self->can('auth')) {
$tx->req->url->userinfo(join ':', @{$self->auth});
}
return $self;
}
method set_headers($ua, $tx, %args) {
if ($self->can('headers')) {
$tx->req->headers->header(@$_) for @{$self->headers};
} else {
$tx->req->headers->header('Content-Type' => 'application/json');
}
return $self;
}
method set_identity($ua, $tx, %args) {
$tx->req->headers->header('User-Agent' => $self->name);
return $self;
}
method execute(Str :$method = 'get', Str :$path = '', Any %args) {
delete $args{method};
my $ua = $self->user_agent;
my $url = $self->url->clone;
my $query = $args{query} || {};
my $headers = $args{headers} || {};
$url->path(join '/', $url->path, $path) if $path;
$url->query($url->query->merge(%$query)) if keys %$query;
my @args;
# data handlers
for my $type (sort keys %{$ua->transactor->generators}) {
push @args, $type, delete $args{$type} if $args{$type};
}
# handle raw body value
push @args, delete $args{body} if exists $args{body};
# transaction prepare hook
$ua->on(prepare => fun ($ua, $tx) {
$self->prepare($ua, $tx, %args);
});
# client timeouts
$ua->max_redirects(0);
$ua->connect_timeout($self->timeout);
$ua->request_timeout($self->timeout);
# transaction
my ($ok, $tx, $req, $res);
# times to retry failures
my $retries = $self->retries;
# transaction retry loop
for (my $i = 0; $i < ($retries || 1); $i++) {
# execute transaction
$tx = $ua->start($ua->build_tx($method, $url, $headers, @args));
$self->process($ua, $tx, %args);
# transaction objects
$req = $tx->req;
$res = $tx->res;
# determine success/failure
$ok = $res->code ? $res->code !~ /(4|5)\d\d/ : 0;
# log activity
if ($req && $res) {
my $log = $self->logger;
my $msg = join " ", "attempt", ("#".($i+1)), ": $method", $url->to_string;
$log->debug("req: $msg")->data({
request => $req->to_string =~ s/\s*$/\n\n\n/r
});
$log->debug("res: $msg")->data({
response => $res->to_string =~ s/\s*$/\n\n\n/r
});
# output to the console where applicable
$log->info("res: $msg [@{[$res->code]}]");
$log->output if $self->debug;
}
# no retry necessary
last if $ok;
}
# throw exception if fatal is truthy
if ($req && $res && $self->fatal && !$ok) {
my $code = $res->code;
$self->stash(tx => $tx);
$self->throw([$code, uc "${code}_http_response"]);
}
# return transaction
return $tx;
}
1;
=encoding utf8
=head1 NAME
API::Client
=cut
=head1 ABSTRACT
HTTP API Thin-Client Abstraction
=cut
=head1 SYNOPSIS
lib/API/Client.pm view on Meta::CPAN
# is equivalent to
my $tx2 = $client->resource('delete')->dispatch(
method => 'delete',
json => {active => 1}
);
[$tx1, $tx2]
This example illustrates how you might delete a new API resource.
=cut
=head2 fetching
# given: synopsis
my $tx1 = $client->resource('get')->fetch(
query => {active => 1}
);
# is equivalent to
my $tx2 = $client->resource('get')->dispatch(
method => 'get',
query => {active => 1}
);
[$tx1, $tx2]
This example illustrates how you might fetch an API resource.
=cut
=head2 subclassing
package Hookbin;
use Data::Object::Class;
extends 'API::Client';
sub auth {
['admin', 'secret']
}
sub headers {
[['Accept', '*/*']]
}
sub base {
['https://httpbin.org/get']
}
package main;
my $hookbin = Hookbin->new;
This package was designed to be subclassed and provides hooks into the client
building and request dispatching processes. Specifically, there are three
useful hooks (i.e. methods, which if present are used to build up the client
object and requests), which are, the C<auth> hook, which should return a
C<Tuple[Str, Str]> which is used to configure the basic auth header, the
C<base> hook which should return a C<Tuple[Str]> which is used to configure the
base URL, and the C<headers> hook, which should return a
C<ArrayRef[Tuple[Str, Str]]> which are used to configure the HTTP request
headers.
=cut
=head2 transacting
# given: synopsis
my $tx1 = $client->resource('patch')->patch(
json => {active => 1}
);
# is equivalent to
my $tx2 = $client->resource('patch')->dispatch(
method => 'patch',
json => {active => 1}
);
[$tx1, $tx2]
An HTTP request is only issued when the L</dispatch> method is called, directly
or indirectly. Those calls return a L<Mojo::Transaction> object which provides
access to the C<request> and C<response> objects.
=cut
=head2 updating
# given: synopsis
my $tx1 = $client->resource('put')->update(
json => {active => 1}
);
# is equivalent to
my $tx2 = $client->resource('put')->dispatch(
method => 'put',
json => {active => 1}
);
[$tx1, $tx2]
This example illustrates how you might update a new API resource.
=cut
=head1 ATTRIBUTES
This package has the following attributes:
=cut
lib/API/Client.pm view on Meta::CPAN
object.
=over 4
=item fetch example #1
# given: synopsis
$client->resource('get')->fetch;
=back
=cut
=head2 patch
patch(Any %args) : InstanceOf["Mojo::Transaction"]
The patch method issues a C<PATCH> request to the API resource represented by
the object.
=over 4
=item patch example #1
# given: synopsis
$client->resource('patch')->patch(
json => {active => 1}
);
=back
=cut
=head2 prepare
prepare(Object $ua, Object $tx, Any %args) : Object
The prepare method acts as a C<before> hook triggered before each request where
you can modify the transactor objects.
=over 4
=item prepare example #1
# given: synopsis
require Mojo::UserAgent;
require Mojo::Transaction::HTTP;
$client->prepare(
Mojo::UserAgent->new,
Mojo::Transaction::HTTP->new
);
=back
=cut
=head2 process
process(Object $ua, Object $tx, Any %args) : Object
The process method acts as an C<after> hook triggered after each response where
you can modify the transactor objects.
=over 4
=item process example #1
# given: synopsis
require Mojo::UserAgent;
require Mojo::Transaction::HTTP;
$client->process(
Mojo::UserAgent->new,
Mojo::Transaction::HTTP->new
);
=back
=cut
=head2 resource
resource(Str @segments) : Object
The resource method returns a new instance of the object for the API resource
endpoint specified.
=over 4
=item resource example #1
# given: synopsis
$client->resource('status', 200);
=back
=cut
=head2 serialize
serialize() : HashRef
The serialize method serializes and returns the object as a C<hashref>.
=over 4
=item serialize example #1
# given: synopsis
$client->serialize;
=back
=cut
=head2 update
update(Any %args) : InstanceOf["Mojo::Transaction"]
The update method issues a C<PUT> request to the API resource represented by
the object.
=over 4
=item update example #1
# given: synopsis
$client->resource('put')->update(
json => {active => 1}
( run in 2.214 seconds using v1.01-cache-2.11-cpan-cdf2f3d4e48 )