| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package WWW::ORCID::API; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 4 |  |  | 4 |  | 53563 | use strict; | 
|  | 4 |  |  |  |  | 7 |  | 
|  | 4 |  |  |  |  | 129 |  | 
| 4 | 4 |  |  | 4 |  | 21 | use warnings; | 
|  | 4 |  |  |  |  | 8 |  | 
|  | 4 |  |  |  |  | 166 |  | 
| 5 |  |  |  |  |  |  |  | 
| 6 |  |  |  |  |  |  | our $VERSION = 0.02_01; | 
| 7 |  |  |  |  |  |  |  | 
| 8 | 4 |  |  | 4 |  | 979 | use Class::Load qw(try_load_class); | 
|  | 4 |  |  |  |  | 36381 |  | 
|  | 4 |  |  |  |  | 216 |  | 
| 9 | 4 |  |  | 4 |  | 720 | use JSON qw(decode_json); | 
|  | 4 |  |  |  |  | 13132 |  | 
|  | 4 |  |  |  |  | 18 |  | 
| 10 | 4 |  |  | 4 |  | 1498 | use Sub::Quote qw(quote_sub); | 
|  | 4 |  |  |  |  | 15677 |  | 
|  | 4 |  |  |  |  | 199 |  | 
| 11 | 4 |  |  | 4 |  | 26 | use Carp; | 
|  | 4 |  |  |  |  | 9 |  | 
|  | 4 |  |  |  |  | 192 |  | 
| 12 | 4 |  |  | 4 |  | 261 | use Moo::Role; | 
|  | 4 |  |  |  |  | 9705 |  | 
|  | 4 |  |  |  |  | 29 |  | 
| 13 | 4 |  |  | 4 |  | 1946 | use namespace::clean; | 
|  | 4 |  |  |  |  | 4077 |  | 
|  | 4 |  |  |  |  | 41 |  | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | with 'WWW::ORCID::Base'; | 
| 16 |  |  |  |  |  |  |  | 
| 17 |  |  |  |  |  |  | requires 'ops'; | 
| 18 |  |  |  |  |  |  | requires '_build_api_url'; | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  | has sandbox           => (is => 'ro',); | 
| 21 |  |  |  |  |  |  | has client_id         => (is => 'ro', required => 1); | 
| 22 |  |  |  |  |  |  | has client_secret     => (is => 'ro', required => 1); | 
| 23 |  |  |  |  |  |  | has api_url           => (is => 'lazy',); | 
| 24 |  |  |  |  |  |  | has oauth_url         => (is => 'lazy'); | 
| 25 |  |  |  |  |  |  | has read_public_token => (is => 'lazy'); | 
| 26 |  |  |  |  |  |  | has transport         => (is => 'lazy',); | 
| 27 |  |  |  |  |  |  | has last_error => ( | 
| 28 |  |  |  |  |  |  | is       => 'rwp', | 
| 29 |  |  |  |  |  |  | init_arg => undef, | 
| 30 |  |  |  |  |  |  | clearer  => '_clear_last_error', | 
| 31 |  |  |  |  |  |  | trigger  => 1 | 
| 32 |  |  |  |  |  |  | ); | 
| 33 |  |  |  |  |  |  | has _t => (is => 'lazy',); | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | sub _build_oauth_url { | 
| 36 | 0 | 0 |  | 0 |  | 0 | $_[0]->sandbox | 
| 37 |  |  |  |  |  |  | ? 'https://sandbox.orcid.org/oauth' | 
| 38 |  |  |  |  |  |  | : 'https://orcid.org/oauth'; | 
| 39 |  |  |  |  |  |  | } | 
| 40 |  |  |  |  |  |  |  | 
| 41 |  |  |  |  |  |  | sub _build_read_public_token { | 
| 42 | 0 |  |  | 0 |  | 0 | $_[0]->access_token( | 
| 43 |  |  |  |  |  |  | grant_type => 'client_credentials', | 
| 44 |  |  |  |  |  |  | scope      => '/read-public' | 
| 45 |  |  |  |  |  |  | ); | 
| 46 |  |  |  |  |  |  | } | 
| 47 |  |  |  |  |  |  |  | 
| 48 |  |  |  |  |  |  | sub access_token { | 
| 49 | 0 |  |  | 0 | 0 | 0 | my $self = shift; | 
| 50 | 0 |  |  |  |  | 0 | $self->_clear_last_error; | 
| 51 | 0 | 0 |  |  |  | 0 | my $opts = ref $_[0] ? $_[0] : {@_}; | 
| 52 | 0 |  |  |  |  | 0 | $opts->{client_id}     = $self->client_id; | 
| 53 | 0 |  |  |  |  | 0 | $opts->{client_secret} = $self->client_secret; | 
| 54 | 0 |  |  |  |  | 0 | my $url = join('/', $self->oauth_url, 'token'); | 
| 55 | 0 |  |  |  |  | 0 | my $headers = {'Accept' => 'application/json'}; | 
| 56 | 0 |  |  |  |  | 0 | my $res = $self->_t->post_form($url, $opts, $headers); | 
| 57 |  |  |  |  |  |  |  | 
| 58 | 0 | 0 |  |  |  | 0 | if ($res->[0] eq '200') { | 
| 59 | 0 |  |  |  |  | 0 | return decode_json($res->[2]); | 
| 60 |  |  |  |  |  |  | } | 
| 61 | 0 |  |  |  |  | 0 | $self->_set_last_error($res); | 
| 62 | 0 |  |  |  |  | 0 | return; | 
| 63 |  |  |  |  |  |  | } | 
| 64 |  |  |  |  |  |  |  | 
| 65 |  |  |  |  |  |  | sub authorize_url { | 
| 66 | 0 |  |  | 0 | 0 | 0 | my $self = shift; | 
| 67 | 0 | 0 |  |  |  | 0 | my $opts = ref $_[0] ? $_[0] : {@_}; | 
| 68 | 0 |  |  |  |  | 0 | $opts->{client_id} = $self->client_id; | 
| 69 | 0 |  |  |  |  | 0 | $self->_param_url(join('/', $self->oauth_url, 'authorize'), $opts); | 
| 70 |  |  |  |  |  |  | } | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | sub record_url { | 
| 73 | 0 |  |  | 0 | 0 | 0 | my ($self, $orcid) = @_; | 
| 74 | 0 | 0 |  |  |  | 0 | $self->sandbox | 
| 75 |  |  |  |  |  |  | ? "http://sandbox.orcid.org/$orcid" | 
| 76 |  |  |  |  |  |  | : "http://orcid.org/$orcid"; | 
| 77 |  |  |  |  |  |  | } | 
| 78 |  |  |  |  |  |  |  | 
| 79 |  |  |  |  |  |  | sub _build_transport { | 
| 80 | 0 |  |  | 0 |  | 0 | 'LWP'; | 
| 81 |  |  |  |  |  |  | } | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | sub _build__t { | 
| 84 | 0 |  |  | 0 |  | 0 | my ($self)          = @_; | 
| 85 | 0 |  |  |  |  | 0 | my $transport       = $self->transport; | 
| 86 | 0 |  |  |  |  | 0 | my $transport_class = "WWW::ORCID::Transport::${transport}"; | 
| 87 | 0 | 0 |  |  |  | 0 | try_load_class($transport_class) | 
| 88 |  |  |  |  |  |  | or croak("Could not load $transport_class: $!"); | 
| 89 | 0 |  |  |  |  | 0 | $transport_class->new; | 
| 90 |  |  |  |  |  |  | } | 
| 91 |  |  |  |  |  |  |  | 
| 92 |  |  |  |  |  |  | sub _trigger_last_error { | 
| 93 | 0 |  |  | 0 |  | 0 | my ($self, $res) = @_; | 
| 94 | 0 | 0 |  |  |  | 0 | $self->log->errorf("%s", $res) if $self->log->is_error; | 
| 95 |  |  |  |  |  |  | } | 
| 96 |  |  |  |  |  |  |  | 
| 97 |  |  |  |  |  |  | sub _url { | 
| 98 | 0 |  |  | 0 |  | 0 | my ($host, $path, $opts) = @_; | 
| 99 | 0 | 0 |  |  |  | 0 | $path = join('/', @$path) if ref $path; | 
| 100 | 0 |  |  |  |  | 0 | $path =~ s|_summary$|/summary|; | 
| 101 | 0 |  |  |  |  | 0 | $path =~ s|_|-|g; | 
| 102 | 0 | 0 |  |  |  | 0 | if (defined(my $orcid = $opts->{orcid})) { | 
| 103 | 0 |  |  |  |  | 0 | $path = "$orcid/$path"; | 
| 104 |  |  |  |  |  |  | } | 
| 105 | 0 | 0 |  |  |  | 0 | if (defined(my $put_code = $opts->{put_code})) { | 
| 106 | 0 | 0 |  |  |  | 0 | $put_code = join(',', @$put_code) if ref $put_code; | 
| 107 | 0 |  |  |  |  | 0 | $path = "$path/$put_code"; | 
| 108 |  |  |  |  |  |  | } | 
| 109 | 0 |  |  |  |  | 0 | join('/', $host, $path); | 
| 110 |  |  |  |  |  |  | } | 
| 111 |  |  |  |  |  |  |  | 
| 112 |  |  |  |  |  |  | sub _headers { | 
| 113 | 0 |  |  | 0 |  | 0 | my ($opts, $add_accept, $add_content_type) = @_; | 
| 114 | 0 |  |  |  |  | 0 | my $token = $opts->{token}; | 
| 115 | 0 | 0 |  |  |  | 0 | $token = $token->{access_token} if ref $token; | 
| 116 | 0 |  |  |  |  | 0 | my $headers = {'Authorization' => "Bearer $token",}; | 
| 117 | 0 | 0 |  |  |  | 0 | if ($add_accept) { | 
| 118 | 0 |  |  |  |  | 0 | $headers->{'Accept'} = 'application/vnd.orcid+json'; | 
| 119 |  |  |  |  |  |  | } | 
| 120 | 0 | 0 |  |  |  | 0 | if ($add_content_type) { | 
| 121 | 0 |  |  |  |  | 0 | $headers->{'Content-Type'} = 'application/vnd.orcid+json'; | 
| 122 |  |  |  |  |  |  | } | 
| 123 | 0 |  |  |  |  | 0 | $headers; | 
| 124 |  |  |  |  |  |  | } | 
| 125 |  |  |  |  |  |  |  | 
| 126 |  |  |  |  |  |  | sub _clean { | 
| 127 | 0 |  |  | 0 |  | 0 | my ($opts) = @_; | 
| 128 | 0 |  |  |  |  | 0 | delete $opts->{$_} for qw(orcid token put_code); | 
| 129 | 0 |  |  |  |  | 0 | $opts; | 
| 130 |  |  |  |  |  |  | } | 
| 131 |  |  |  |  |  |  |  | 
| 132 |  |  |  |  |  |  | sub client { | 
| 133 | 0 |  |  | 0 | 0 | 0 | my $self = shift; | 
| 134 | 0 |  |  |  |  | 0 | $self->_clear_last_error; | 
| 135 | 0 | 0 |  |  |  | 0 | my $opts = ref $_[0] ? $_[0] : {@_}; | 
| 136 | 0 |  | 0 |  |  | 0 | $opts->{token} ||= $self->read_public_token; | 
| 137 | 0 |  |  |  |  | 0 | my $url = join('/', $self->api_url, 'client', $self->client_id); | 
| 138 | 0 |  |  |  |  | 0 | my $res = $self->_t->get($url, undef, _headers($opts, 1, 0)); | 
| 139 | 0 | 0 |  |  |  | 0 | if ($res->[0] eq '200') { | 
| 140 | 0 |  |  |  |  | 0 | return decode_json($res->[2]); | 
| 141 |  |  |  |  |  |  | } | 
| 142 | 0 |  |  |  |  | 0 | $self->_set_last_error($res); | 
| 143 | 0 |  |  |  |  | 0 | return; | 
| 144 |  |  |  |  |  |  | } | 
| 145 |  |  |  |  |  |  |  | 
| 146 |  |  |  |  |  |  | sub search { | 
| 147 | 0 |  |  | 0 | 0 | 0 | shift->get('search', @_); | 
| 148 |  |  |  |  |  |  | } | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | sub get { | 
| 151 | 0 |  |  | 0 | 0 | 0 | my $self = shift; | 
| 152 | 0 |  |  |  |  | 0 | $self->_clear_last_error; | 
| 153 | 0 |  |  |  |  | 0 | my $path = shift; | 
| 154 | 0 | 0 |  |  |  | 0 | my $opts = ref $_[0] ? $_[0] : {@_}; | 
| 155 | 0 |  | 0 |  |  | 0 | $opts->{token} ||= $self->read_public_token; | 
| 156 | 0 |  |  |  |  | 0 | my $url = _url($self->api_url, $path, $opts); | 
| 157 | 0 |  |  |  |  | 0 | my $headers = _headers($opts, 1, 0); | 
| 158 | 0 |  |  |  |  | 0 | my $res = $self->_t->get($url, _clean($opts), $headers); | 
| 159 |  |  |  |  |  |  |  | 
| 160 | 0 | 0 |  |  |  | 0 | if ($res->[0] eq '200') { | 
| 161 | 0 |  |  |  |  | 0 | return decode_json($res->[2]); | 
| 162 |  |  |  |  |  |  | } | 
| 163 | 0 |  |  |  |  | 0 | $self->_set_last_error($res); | 
| 164 | 0 |  |  |  |  | 0 | return; | 
| 165 |  |  |  |  |  |  | } | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  | sub install_helper_methods { | 
| 168 | 2 |  |  | 2 | 0 | 6 | my $class = $_[0]; | 
| 169 | 2 |  |  |  |  | 6 | my $ops   = $class->ops; | 
| 170 | 2 |  |  |  |  | 25 | for my $op (sort keys %$ops) { | 
| 171 | 51 |  |  |  |  | 1170 | my $spec = $ops->{$op}; | 
| 172 | 51 |  |  |  |  | 61 | my $sym  = $op; | 
| 173 | 51 |  |  |  |  | 126 | $sym =~ s|[-/]|_|g; | 
| 174 |  |  |  |  |  |  |  | 
| 175 | 51 | 50 | 66 |  |  | 133 | if ($spec->{get} || $spec->{get_pc} || $spec->{get_pc_bulk}) { | 
|  |  |  | 33 |  |  |  |  | 
| 176 | 51 |  |  |  |  | 170 | quote_sub("${class}::${sym}", qq|shift->get('${op}', \@_)|); | 
| 177 |  |  |  |  |  |  | } | 
| 178 |  |  |  |  |  |  |  | 
| 179 | 51 | 100 |  |  |  | 5062 | if ($spec->{add}) { | 
| 180 | 11 |  |  |  |  | 38 | quote_sub("${class}::add_${sym}", qq|shift->add('${op}', \@_)|); | 
| 181 |  |  |  |  |  |  | } | 
| 182 |  |  |  |  |  |  |  | 
| 183 | 51 | 100 |  |  |  | 1093 | if ($spec->{update}) { | 
| 184 | 11 |  |  |  |  | 33 | quote_sub("${class}::update_${sym}", | 
| 185 |  |  |  |  |  |  | qq|shift->update('${op}', \@_)|); | 
| 186 |  |  |  |  |  |  | } | 
| 187 |  |  |  |  |  |  |  | 
| 188 | 51 | 100 |  |  |  | 1094 | if ($spec->{delete}) { | 
| 189 | 12 |  |  |  |  | 45 | quote_sub("${class}::delete_${sym}", | 
| 190 |  |  |  |  |  |  | qq|shift->delete('${op}', \@_)|); | 
| 191 |  |  |  |  |  |  | } | 
| 192 |  |  |  |  |  |  | } | 
| 193 |  |  |  |  |  |  |  | 
| 194 | 2 |  |  |  |  | 36 | 1; | 
| 195 |  |  |  |  |  |  | } | 
| 196 |  |  |  |  |  |  |  | 
| 197 |  |  |  |  |  |  | 1; |