forked from loafle/openapi-generator-original
- No more presuming 'WWW::' is at the beginning (default is now "WWW::SwaggerClient" vs "SwaggerClient" in order to accomodate) - Test that module names Like::This write to the filesystem and in the mustache templates properly
385 lines
12 KiB
Plaintext
385 lines
12 KiB
Plaintext
package {{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 {{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;
|
|
}
|
|
|
|
sub _cfg {'{{moduleName}}::Configuration'}
|
|
|
|
# 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} || ${{moduleName}}::Configuration::http_timeout);
|
|
$self->{ua}->agent($self->{http_user_agent} || ${{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("{{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, $key_name) = @_;
|
|
|
|
my $api_key = ${{moduleName}}::Configuration::api_key->{$key_name};
|
|
|
|
return unless $api_key;
|
|
|
|
my $prefix = ${{moduleName}}::Configuration::api_key_prefix->{$key_name};
|
|
return $prefix ? "$prefix $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) = @_;
|
|
|
|
return $self->_global_auth_setup($header_params, $query_params)
|
|
unless $auth_settings && @$auth_settings;
|
|
|
|
# one endpoint can have more than 1 auth settings
|
|
foreach my $auth (@$auth_settings) {
|
|
# determine which one to use
|
|
if (!defined($auth)) {
|
|
# TODO show warning about auth setting not defined
|
|
}
|
|
{{#authMethods}}elsif ($auth eq '{{name}}') {
|
|
{{#isApiKey}}{{#isKeyInHeader}}
|
|
my $api_key = $self->get_api_key_with_prefix('{{keyParamName}}');
|
|
if ($api_key) {
|
|
$header_params->{'{{keyParamName}}'} = $api_key;
|
|
}{{/isKeyInHeader}}{{#isKeyInQuery}}
|
|
my $api_key = $self->get_api_key_with_prefix('{{keyParamName}}');
|
|
if ($api_key) {
|
|
$query_params->{'{{keyParamName}}'} = $api_key;
|
|
}{{/isKeyInQuery}}{{/isApiKey}}{{#isBasic}}
|
|
if (${{moduleName}}::Configuration::username || ${{moduleName}}::Configuration::password) {
|
|
$header_params->{'Authorization'} = 'Basic ' . encode_base64(${{moduleName}}::Configuration::username . ":" . ${{moduleName}}::Configuration::password);
|
|
}{{/isBasic}}{{#isOAuth}}
|
|
if (${{moduleName}}::Configuration::access_token) {
|
|
$header_params->{'Authorization'} = 'Bearer ' . ${{moduleName}}::Configuration::access_token;
|
|
}{{/isOAuth}}
|
|
}
|
|
{{/authMethods}}
|
|
else {
|
|
# TODO show warning about security definition not found
|
|
}
|
|
}
|
|
}
|
|
|
|
# The endpoint API class has not found any settings for auth. This may be deliberate,
|
|
# in which case update_params_for_auth() will be a no-op. But it may also be that the
|
|
# OpenAPI Spec does not describe the intended authorization. So we check in the config for any
|
|
# auth tokens and if we find any, we use them for all endpoints;
|
|
sub _global_auth_setup {
|
|
my ($self, $header_params, $query_params) = @_;
|
|
|
|
my $tokens = $self->_cfg->get_tokens;
|
|
return unless keys %$tokens;
|
|
|
|
# basic
|
|
if (my $uname = delete $tokens->{username}) {
|
|
my $pword = delete $tokens->{password};
|
|
$header_params->{'Authorization'} = 'Basic '.encode_base64($uname.":".$pword);
|
|
}
|
|
|
|
# oauth
|
|
if (my $access_token = delete $tokens->{access_token}) {
|
|
$header_params->{'Authorization'} = 'Bearer ' . $access_token;
|
|
}
|
|
|
|
# other keys
|
|
foreach my $token_name (keys %$tokens) {
|
|
my $in = $tokens->{$token_name}->{in};
|
|
my $token = $self->get_api_key_with_prefix($token_name);
|
|
if ($in eq 'head') {
|
|
$header_params->{$token_name} = $token;
|
|
}
|
|
elsif ($in eq 'query') {
|
|
$query_params->{$token_name} = $token;
|
|
}
|
|
else {
|
|
die "Don't know where to put token '$token_name' ('$in' is not 'head' or 'query')";
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
1;
|