package WWW::{{moduleName}}::ApiClient; use strict; use warnings; use utf8; use MIME::Base64; use LWP::UserAgent; use HTTP::Headers; use HTTP::Response; use HTTP::Request::Common qw(DELETE POST GET HEAD PUT); use HTTP::Status; use URI::Query; use JSON; use URI::Escape; use Scalar::Util; use Log::Any qw($log); use Carp; use Module::Runtime qw(use_module); use WWW::{{moduleName}}::Configuration; use base 'Class::Singleton'; sub _new_instance { my $class = shift; my (%args) = ( 'ua' => LWP::UserAgent->new, 'base_url' => '{{basePath}}', @_ ); return bless \%args, $class; } # Set the user agent of the API client # # @param string $user_agent The user agent of the API client # sub set_user_agent { my ($self, $user_agent) = @_; $self->{http_user_agent}= $user_agent; } # Set timeout # # @param integer $seconds Number of seconds before timing out [set to 0 for no timeout] # sub set_timeout { my ($self, $seconds) = @_; if (!looks_like_number($seconds)) { croak('Timeout variable must be numeric.'); } $self->{http_timeout} = $seconds; } # make the HTTP request # @param string $resourcePath path to method endpoint # @param string $method method to call # @param array $queryParams parameters to be place in query URL # @param array $postData parameters to be placed in POST body # @param array $headerParams parameters to be place in request header # @return mixed sub call_api { my $self = shift; my ($resource_path, $method, $query_params, $post_params, $header_params, $body_data, $auth_settings) = @_; # update parameters based on authentication settings $self->update_params_for_auth($header_params, $query_params, $auth_settings); my $_url = $self->{base_url} . $resource_path; # build query if (%$query_params) { $_url = ($_url . '?' . eval { URI::Query->new($query_params)->stringify }); } # body data $body_data = to_json($body_data->to_hash) if defined $body_data && $body_data->can('to_hash'); # model to json string my $_body_data = %$post_params ? $post_params : $body_data; # Make the HTTP request my $_request; if ($method eq 'POST') { # multipart $header_params->{'Content-Type'} = lc $header_params->{'Content-Type'} eq 'multipart/form' ? 'form-data' : $header_params->{'Content-Type'}; $_request = POST($_url, %$header_params, Content => $_body_data); } elsif ($method eq 'PUT') { # multipart $header_params->{'Content-Type'} = lc $header_params->{'Content-Type'} eq 'multipart/form' ? 'form-data' : $header_params->{'Content-Type'}; $_request = PUT($_url, %$header_params, Content => $_body_data); } elsif ($method eq 'GET') { my $headers = HTTP::Headers->new(%$header_params); $_request = GET($_url, %$header_params); } elsif ($method eq 'HEAD') { my $headers = HTTP::Headers->new(%$header_params); $_request = HEAD($_url,%$header_params); } elsif ($method eq 'DELETE') { #TODO support form data my $headers = HTTP::Headers->new(%$header_params); $_request = DELETE($_url, %$headers); } elsif ($method eq 'PATCH') { #TODO } else { } $self->{ua}->timeout($self->{http_timeout} || $WWW::{{moduleName}}::Configuration::http_timeout); $self->{ua}->agent($self->{http_user_agent} || $WWW::{{moduleName}}::Configuration::http_user_agent); $log->debugf("REQUEST: %s", $_request->as_string); my $_response = $self->{ua}->request($_request); $log->debugf("RESPONSE: %s", $_response->as_string); unless ($_response->is_success) { croak(sprintf "API Exception(%s): %s\n%s", $_response->code, $_response->message, $_response->content); } return $_response->content; } # Take value and turn it into a string suitable for inclusion in # the path, by url-encoding. # @param string $value a string which will be part of the path # @return string the serialized object sub to_path_value { my ($self, $value) = @_; return uri_escape($self->to_string($value)); } # Take value and turn it into a string suitable for inclusion in # the query, by imploding comma-separated if it's an object. # If it's a string, pass through unchanged. It will be url-encoded # later. # @param object $object an object to be serialized to a string # @return string the serialized object sub to_query_value { my ($self, $object) = @_; if (ref($object) eq 'ARRAY') { return join(',', @$object); } else { return $self->to_string($object); } } # Take value and turn it into a string suitable for inclusion in # the header. If it's a string, pass through unchanged # If it's a datetime object, format it in ISO8601 # @param string $value a string which will be part of the header # @return string the header string sub to_header_value { my ($self, $value) = @_; return $self->to_string($value); } # Take value and turn it into a string suitable for inclusion in # the http body (form parameter). If it's a string, pass through unchanged # If it's a datetime object, format it in ISO8601 # @param string $value the value of the form parameter # @return string the form string sub to_form_value { my ($self, $value) = @_; return $self->to_string($value); } # Take value and turn it into a string suitable for inclusion in # the parameter. If it's a string, pass through unchanged # If it's a datetime object, format it in ISO8601 # @param string $value the value of the parameter # @return string the header string sub to_string { my ($self, $value) = @_; if (ref($value) eq "DateTime") { # datetime in ISO8601 format return $value->datetime(); } else { return $value; } } # Deserialize a JSON string into an object # # @param string $class class name is passed as a string # @param string $data data of the body # @return object an instance of $class sub deserialize { my ($self, $class, $data) = @_; $log->debugf("deserializing %s for %s", $data, $class); if (not defined $data) { return undef; } elsif ( (substr($class, 0, 5)) eq 'HASH[') { #hash if ($class =~ /^HASH\[(.*),(.*)\]$/) { my ($key_type, $type) = ($1, $2); my %hash; my $decoded_data = decode_json $data; foreach my $key (keys %$decoded_data) { if (ref $decoded_data->{$key} eq 'HASH') { $hash{$key} = $self->deserialize($type, encode_json $decoded_data->{$key}); } else { $hash{$key} = $self->deserialize($type, $decoded_data->{$key}); } } return \%hash; } else { #TODO log error } } elsif ( (substr($class, 0, 6)) eq 'ARRAY[' ) { # array of data return $data if $data eq '[]'; # return if empty array my $_sub_class = substr($class, 6, -1); my $_json_data = decode_json $data; my @_values = (); foreach my $_value (@$_json_data) { if (ref $_value eq 'ARRAY') { push @_values, $self->deserialize($_sub_class, encode_json $_value); } else { push @_values, $self->deserialize($_sub_class, $_value); } } return \@_values; } elsif ($class eq 'DateTime') { return DateTime->from_epoch(epoch => str2time($data)); } elsif (grep /^$class$/, ('string', 'int', 'float', 'bool', 'object')) { return $data; } else { # model my $_instance = use_module("WWW::{{moduleName}}::Object::$class")->new; if (ref $data eq "HASH") { return $_instance->from_hash($data); } else { # string, need to json decode first return $_instance->from_hash(decode_json $data); } } } # return 'Accept' based on an array of accept provided # @param [Array] header_accept_array Array fo 'Accept' # @return String Accept (e.g. application/json) sub select_header_accept { my ($self, @header) = @_; if (@header == 0 || (@header == 1 && $header[0] eq '')) { return undef; } elsif (grep(/^application\/json$/i, @header)) { return 'application/json'; } else { return join(',', @header); } } # return the content type based on an array of content-type provided # @param [Array] content_type_array Array fo content-type # @return String Content-Type (e.g. application/json) sub select_header_content_type { my ($self, @header) = @_; if (@header == 0 || (@header == 1 && $header[0] eq '')) { return 'application/json'; # default to application/json } elsif (grep(/^application\/json$/i, @header)) { return 'application/json'; } else { return join(',', @header); } } # Get API key (with prefix if set) # @param string key name # @return string API key with the prefix sub get_api_key_with_prefix { my ($self, $api_key) = @_; if ($WWW::{{moduleName}}::Configuration::api_key_prefix->{$api_key}) { return $WWW::{{moduleName}}::Configuration::api_key_prefix->{$api_key}." ".$WWW::{{moduleName}}::Configuration::api_key->{$api_key}; } else { return $WWW::{{moduleName}}::Configuration::api_key->{$api_key}; } } # update header and query param based on authentication setting # # @param array $headerParams header parameters (by ref) # @param array $queryParams query parameters (by ref) # @param array $authSettings array of authentication scheme (e.g ['api_key']) sub update_params_for_auth { my ($self, $header_params, $query_params, $auth_settings) = @_; # we can defer to the application if ($self->{auth_setup_handler_object}) { $self->{auth_setup_handler_object}->auth_setup_handler( api_client => $self, header_params => $header_params, query_params => $query_params, auth_settings => $auth_settings, # presumably this won't be defined if we're doing it this way ); return; } return if (!defined($auth_settings) || scalar(@$auth_settings) == 0); # one endpoint can have more than 1 auth settings foreach my $auth (@$auth_settings) { # determine which one to use if (!defined($auth)) { } {{#authMethods}}elsif ($auth eq '{{name}}') { {{#isApiKey}}{{#isKeyInHeader}}$header_params->{'{{keyParamName}}'} = $self->get_api_key_with_prefix('{{keyParamName}}');{{/isKeyInHeader}}{{#isKeyInQuery}}$query_params->{'{{keyParamName}}'} = $self->get_api_key_with_prefix('{{keyParamName}}');{{/isKeyInQuery}}{{/isApiKey}}{{#isBasic}}$header_params->{'Authorization'} = 'Basic '.encode_base64($WWW::{{moduleName}}::Configuration::username.":".$WWW::{{moduleName}}::Configuration::password);{{/isBasic}} {{#isOAuth}}$header_params->{'Authorization'} = 'Bearer ' . $WWW::{{moduleName}}::Configuration::access_token;{{/isOAuth}} } {{/authMethods}} else { # TODO show warning about security definition not found } } } 1;