Group
Extension

MooX-Role-HTTP-Tiny/lib/MooX/Role/REST.pm

package MooX::Role::REST;
use Moo::Role;

with qw<
    MooX::Role::HTTP::Tiny
    MooX::Params::CompiledValidators
>;

use JSON;
use Types::Standard qw< Enum HashRef InstanceOf Maybe Str >;

our $VERSION = '0.001';
our $DEBUG = 0;

=head1 NAME

MooX::Role::REST - Simple HTTP client for JSON-REST as a Moo::Role

=head1 ATTRIBUTES

These are inherited from L<MooX::Role::HTTP::Tiny>:

=head2 base_uri [Required]

The base URI for the REST-API.

=head2 ua [Lazy]

Instantiated L<HTTP::Tiny> instance. Will be created once needed.

=head2 ua_options [Optional]

The contents of this HashRef will be passed to the constructor for
L<HTTP::Tiny> (in case of I<lazy> construction).

=cut

=head1 SYNOPSIS

    package My::Client;
    use Moo;
    with qw< MooX::Role::REST >;
    1;

    package My::API;
    use Moo;
    use Types::Standard qw< InstanceOf >;
    has client => (
        is       => 'ro',
        isa      => InstanceOf(['My::Client']),
        handles  => [qw< call >],
        required => 1,
    );
    sub search_release {
        my $self = shift;
        my ($query) = @_;
        $query =~ s{ :: }{-}xg;
        return $self->call(GET => 'release/_search', { q => $query });
    }
    1;

    package main;
    use warnings;
    use v5.14.0; # strict + feature
    use Data::Dumper;

    # Show request/response on STDERR
    { no warnings 'once'; $MooX::Role::REST::DEBUG = 1; }

    my $client = My::Client->new(base_uri => 'https://fastapi.metacpan.org/v1/');
    my $api = My::API->new(client => $client);

    say Dumper($api->search_release('MooX::Params::CompiledValidators'));

=head1 DESCRIPTION

Helper role to implement a simple REST client with JSON.

=head2 call

Mandatory method that implements the actual HTTP stuff.

=cut

sub call {
    my $self = shift;
    $self->validate_positional_parameters(
        [
            $self->parameter(http_method => $self->Required, { store => \my $hmethod }),
            $self->parameter(call_path   => $self->Required, { store => \my $cpath }),
            $self->parameter(call_data   => $self->Optional, { store => \my $cdata }),
        ],
        \@_
    );

    my $endpoint = $self->base_uri->clone;
    (my $path = $endpoint->path) =~ s{/+$}{};
    $path = $cpath =~ m{^ / }x ? $cpath : "$path/$cpath";
    $endpoint->path($path);

    my @body;
    # GET and DELETE have no body
    if ($hmethod =~ m{^ (?: GET | DELETE ) $}x) {
        my $params = $cdata ? $self->www_form_urlencode($cdata) : '';
        $endpoint->query($params) if $params;
    }
    else {
        @body = $cdata ? { content => encode_json($cdata) } : ();
    }

    print STDERR ">>>$hmethod($endpoint)>>>@body<<<\n"
        if $DEBUG;
    my $response = $self->request($hmethod, $endpoint->as_string, @body);

    use Data::Dumper; local($Data::Dumper::Indent, $Data::Dumper::Sortkeys) = (1, 1);
    print STDERR ">>>" . Dumper($response) . "<<<\n" if $DEBUG;

    my ($ct) = split(m{\s*;\s*}, $response->{headers}{'content-type'}, 2);
    if (! $response->{success}) {
        my $error = "$response->{status} ($response->{reason})";
        if ($response->{content}) {
            $error .= " - " . ($ct eq 'application/json'
                ? decode_json($response->{content})
                : $response->{content});
        }
        my (undef, $f, $l) = caller(0); # poor mans Carp
        die "Error $hmethod($endpoint): $error at $f line $l\n";
    }

    return $ct eq 'application/json'
        ? decode_json($response->{content})
        : $response->{content};
}

=head2 ValidationTemplates

Validation templates for the C<call()> method.

=over

=item http_method => Enum< GET POST PUT DELETE PATCH >

=item call_path => Str

=item call_data => Maybe[HashRef]

=back

=cut

sub ValidationTemplates {
    return {
        http_method => { type => Enum [qw< GET POST PUT DELETE PATCH >] },
        call_path   => { type => Str },
        call_data   => { type => Maybe [HashRef] },
    };
}

use namespace::autoclean;
1;

=head1 COPYRIGHT

E<copy> MMXXIII - Abe Timmerman <abeltje@cpan.org>

=head1 LICENSE

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

=cut


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.