File Coverage

blib/lib/MooX/Role/REST.pm
Criterion Covered Total %
statement 39 39 100.0
branch 14 22 63.6
condition n/a
subroutine 7 7 100.0
pod 2 2 100.0
total 62 70 88.5


line stmt bran cond sub pod time code
1             package MooX::Role::REST;
2 1     1   9321 use Moo::Role;
  1         2  
  1         6  
3              
4             with qw<
5             MooX::Role::HTTP::Tiny
6             MooX::Params::CompiledValidators
7             >;
8              
9 1     1   380 use JSON;
  1         2  
  1         8  
10 1     1   723 use Types::Standard qw< Enum HashRef InstanceOf Maybe Str >;
  1         116728  
  1         10  
11              
12             our $VERSION = '0.001';
13             our $DEBUG = 0;
14              
15             =head1 NAME
16              
17             MooX::Role::REST - Simple HTTP client for JSON-REST as a Moo::Role
18              
19             =head1 ATTRIBUTES
20              
21             These are inherited from L<MooX::Role::HTTP::Tiny>:
22              
23             =head2 base_uri [Required]
24              
25             The base URI for the REST-API.
26              
27             =head2 ua [Lazy]
28              
29             Instantiated L<HTTP::Tiny> instance. Will be created once needed.
30              
31             =head2 ua_options [Optional]
32              
33             The contents of this HashRef will be passed to the constructor for
34             L<HTTP::Tiny> (in case of I<lazy> construction).
35              
36             =cut
37              
38             =head1 SYNOPSIS
39              
40             package My::Client;
41             use Moo;
42             with qw< MooX::Role::REST >;
43             1;
44              
45             package My::API;
46             use Moo;
47             use Types::Standard qw< InstanceOf >;
48             has client => (
49             is => 'ro',
50             isa => InstanceOf(['My::Client']),
51             handles => [qw< call >],
52             required => 1,
53             );
54             sub search_release {
55             my $self = shift;
56             my ($query) = @_;
57             $query =~ s{ :: }{-}xg;
58             return $self->call(GET => 'release/_search', { q => $query });
59             }
60             1;
61              
62             package main;
63             use warnings;
64             use v5.14.0; # strict + feature
65             use Data::Dumper;
66              
67             # Show request/response on STDERR
68             { no warnings 'once'; $MooX::Role::REST::DEBUG = 1; }
69              
70             my $client = My::Client->new(base_uri => 'https://fastapi.metacpan.org/v1/');
71             my $api = My::API->new(client => $client);
72              
73             say Dumper($api->search_release('MooX::Params::CompiledValidators'));
74              
75             =head1 DESCRIPTION
76              
77             Helper role to implement a simple REST client with JSON.
78              
79             =head2 call
80              
81             Mandatory method that implements the actual HTTP stuff.
82              
83             =cut
84              
85             sub call {
86 3     3 1 1840 my $self = shift;
87 3         11 $self->validate_positional_parameters(
88             [
89             $self->parameter(http_method => $self->Required, { store => \my $hmethod }),
90             $self->parameter(call_path => $self->Required, { store => \my $cpath }),
91             $self->parameter(call_data => $self->Optional, { store => \my $cdata }),
92             ],
93             \@_
94             );
95              
96 3         3620 my $endpoint = $self->base_uri->clone;
97 3         202 (my $path = $endpoint->path) =~ s{/+$}{};
98 3 100       86 $path = $cpath =~ m{^ / }x ? $cpath : "$path/$cpath";
99 3         13 $endpoint->path($path);
100              
101 3         115 my @body;
102             # GET and DELETE have no body
103 3 100       14 if ($hmethod =~ m{^ (?: GET | DELETE ) $}x) {
104 2 50       46 my $params = $cdata ? $self->www_form_urlencode($cdata) : '';
105 2 50       608 $endpoint->query($params) if $params;
106             }
107             else {
108 1 50       11 @body = $cdata ? { content => encode_json($cdata) } : ();
109             }
110              
111 3 50       42 print STDERR ">>>$hmethod($endpoint)>>>@body<<<\n"
112             if $DEBUG;
113 3         14 my $response = $self->request($hmethod, $endpoint->as_string, @body);
114              
115 1     1   4159 use Data::Dumper; local($Data::Dumper::Indent, $Data::Dumper::Sortkeys) = (1, 1);
  1         7751  
  1         342  
  3         562  
116 3 50       8 print STDERR ">>>" . Dumper($response) . "<<<\n" if $DEBUG;
117              
118 3         19 my ($ct) = split(m{\s*;\s*}, $response->{headers}{'content-type'}, 2);
119 3 100       12 if (! $response->{success}) {
120 1         6 my $error = "$response->{status} ($response->{reason})";
121 1 50       6 if ($response->{content}) {
122             $error .= " - " . ($ct eq 'application/json'
123             ? decode_json($response->{content})
124 1 50       7 : $response->{content});
125             }
126 1         12 my (undef, $f, $l) = caller(0); # poor mans Carp
127 1         8 die "Error $hmethod($endpoint): $error at $f line $l\n";
128             }
129              
130             return $ct eq 'application/json'
131             ? decode_json($response->{content})
132 2 50       25 : $response->{content};
133             }
134              
135             =head2 ValidationTemplates
136              
137             Validation templates for the C<call()> method.
138              
139             =over
140              
141             =item http_method => Enum< GET POST PUT DELETE PATCH >
142              
143             =item call_path => Str
144              
145             =item call_data => Maybe[HashRef]
146              
147             =back
148              
149             =cut
150              
151             sub ValidationTemplates {
152             return {
153 18     18 1 9343 http_method => { type => Enum [qw< GET POST PUT DELETE PATCH >] },
154             call_path => { type => Str },
155             call_data => { type => Maybe [HashRef] },
156             };
157             }
158              
159 1     1   470 use namespace::autoclean;
  1         11517  
  1         4  
160             1;
161              
162             =head1 COPYRIGHT
163              
164             E<copy> MMXXIII - Abe Timmerman <abeltje@cpan.org>
165              
166             =head1 LICENSE
167              
168             This library is free software; you can redistribute it and/or modify
169             it under the same terms as Perl itself.
170              
171             This program is distributed in the hope that it will be useful,
172             but WITHOUT ANY WARRANTY; without even the implied warranty of
173             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
174              
175             =cut