IO-Iron/lib/IO/Iron/Common.pm
package IO::Iron::Common;
## no critic (Documentation::RequirePodAtEnd)
## no critic (Documentation::RequirePodSections)
## no critic (Subroutines::RequireArgUnpacking)
use 5.010_000;
use strict;
use warnings;
# Global creator
BEGIN {
# No exports
}
# Global destructor
END {
}
# ABSTRACT: Common routines for Client Libraries to Iron services IronCache, IronMQ and IronWorker.
our $VERSION = '0.14'; # VERSION: generated by DZP::OurPkgVersion
use Path::Tiny qw{path};
use Try::Tiny;
use Log::Any qw{$log};
require JSON::MaybeXS;
use File::Spec ();
use File::HomeDir ();
use Hash::Util 0.06 qw{lock_keys unlock_keys};
use Carp::Assert::More;
use English '-no_match_vars';
use Params::Validate qw(:all);
sub IRON_CONFIG_KEYS {
return (
# Iron.io standard:
'project_id', # The ID of the project to use for requests.
'token', # The OAuth token that should be used to authenticate requests. Can be found in the HUD.
'host', # The domain name the API can be located at. Defaults to a product-specific value, but always using Amazon's cloud.
'protocol'
, # The protocol that will be used to communicate with the API. Defaults to "https", which should be sufficient for 99% of users.
'port', # The port to connect to the API through. Defaults to 443, which should be sufficient for 99% of users.
'api_version'
, # The version of the API to connect through. Defaults to the version supported by the client. End-users should probably never change this. Except: IronMQ service upgraded from v2 to v3 in 2015!
# IO::Iron additions:
'timeout'
, # REST client timeout (for REST calls accessing IronMQ). N.B. This is not a IronMQ config option! It only configures client this client.
'policies', # Filename of JSON file containing policies.
);
}
sub IRON_CLIENT_PARAMETERS {
return (
IRON_CONFIG_KEYS(),
'config', # The config file name.
'connector', # Reference to a preinitiated connector object.
# 'policy', # Reference to a preinitiated policy hash.
);
}
sub get_config { ## no critic (Subroutines::RequireArgUnpacking)
my %params = validate(
@_,
{
map { $_ => { type => SCALAR, optional => 1 }, } IRON_CONFIG_KEYS(), ## no critic (ValuesAndExpressions::ProhibitCommaSeparatedStatements)
'config' => { type => SCALAR, optional => 1, },
}
);
$log->tracef( 'Entering get_config(%s)', \%params );
my %config = ( map { $_ => undef } IRON_CONFIG_KEYS() ); ## preset config keys.
lock_keys( %config, IRON_CONFIG_KEYS() );
_read_iron_config_file( \%config, File::Spec->catfile( File::HomeDir->my_home, '.iron.json' ) ); # Homedir
_read_iron_config_env_vars( \%config ); # Global envs
_read_iron_config_file( \%config, File::Spec->catfile( File::Spec->curdir(), 'iron.json' ) ); # current dir
if ( defined $params{'config'} ) { # config file specified when creating the class, if given.
_read_iron_config_file( \%config,
File::Spec->file_name_is_absolute( $params{'config'} )
? $params{'config'}
: File::Spec->catfile( File::Spec->curdir(), $params{'config'} ) );
}
# The parameters given when the object was created, except 'config'
my @copy_param_keys = grep { !/^config$/msx } keys %params;
@config{@copy_param_keys} = @params{@copy_param_keys};
$log->tracef( 'Exiting get_config: %s', \%config );
return \%config;
}
# Replace the existing values in $config if new environment variables found.
# Vars:
# $config->{'project_id'} = $ENV{'IRON_PROJECT_ID'}
# $config->{'token'} = $ENV{'IRON_TOKEN'}
# $config->{'host'} = $ENV{'IRON_HOST'}
# $config->{'protocol'} = $ENV{'IRON_PROTOCOL'}
# $config->{'port'} = $ENV{'IRON_PORT'}
# $config->{'api_version'} = $ENV{'IRON_API_VERSION'}
# $config->{'timeout'} = $ENV{'IRON_TIMEOUT'}
sub _read_iron_config_env_vars {
my ($config) = @_;
$log->tracef( 'Entering _read_iron_config_env_vars(%s)', $config );
foreach my $config_key ( keys %{$config} ) {
if ( defined $ENV{ 'IRON_' . uc $config_key } ) {
$config->{$config_key} = $ENV{ 'IRON_' . uc $config_key };
}
}
$log->tracef( 'Exiting _read_iron_config_env_vars: %s', $config );
return $config;
}
# Try to read the file given as second parameter. (if undef, fail).
# If fails, gracefully return 0; if succeed, change configuration (first parameter) and return 1.
sub _read_iron_config_file {
my ( $config, $full_path_name ) = @_;
$log->tracef( 'Entering _read_iron_config_file(%s, %s)', $full_path_name, $config );
assert_nonblank( $full_path_name, 'full_path_name is not defined or is blank.' );
my $read_config;
my $rval;
my $file = path($full_path_name);
if ( $file->is_file ) {
$log->tracef( 'File %s exists', $full_path_name );
my $file_contents;
try { $file_contents = $file->slurp_utf8 };
if ($file_contents) {
$log->tracef( 'Slurped file %s', $full_path_name );
my $json = JSON::MaybeXS->new( utf8 => 1, pretty => 1 );
$read_config = $json->decode($file_contents);
foreach my $config_key ( keys %{$config} ) {
if ( defined $read_config->{$config_key} ) {
$config->{$config_key} = $read_config->{$config_key};
}
}
$rval = 1;
}
else {
$log->debugf( 'Could not read file %s', $full_path_name );
$rval = 0;
}
}
else {
$log->tracef( 'File %s does not exist', $full_path_name );
$rval = 0;
}
$log->tracef( 'Exiting _read_iron_config_file: %s', $config );
return $rval;
}
#my $GEN_DELIMS = q{!} . q{$} . q{&} . q{'} . q{(} . q{)}
# . q{*} . q{+} . q{,} . q{;} . q{=};
#my $SUB_DELIMS = q{:} . q{/} . q{?} . q{#} . q{[} . q{]} . q{@};
#my $RESERVED_CHARACTERS = $GEN_DELIMS . $SUB_DELIMS;
#my $RFC_3986_RESERVED_CHARACTERS =~ s/(.{1})/\\$1/sg; # Escape every character.
sub contains_rfc_3986_res_chars {
my @params = validate_pos( @_, { type => SCALAR } );
## no critic (ValuesAndExpressions::RequireInterpolationOfMetachars)
my $rfc_3986_reserved_characters = q{\!\$\&\'\(\)\*\+\,\;\=\:\/\?\#\[\]\@};
## critic (ValuesAndExpressions::RequireInterpolationOfMetachars)
return ( $params[0] =~ m/[$rfc_3986_reserved_characters]{1,}/msx ) ? 1 : 0;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
IO::Iron::Common - Common routines for Client Libraries to Iron services IronCache, IronMQ and IronWorker.
=head1 VERSION
version 0.14
=for stopwords IronCache IronMQ IronWorker config json Mikko Koivunalho
=head1 REQUIREMENTS
=head1 FUNCTIONS
Internal functions for use in the Client objects.
=head2 IRON_CONFIG_KEYS
=head2 IRON_CLIENT_PARAMETERS
=head2 get_config
Get the config from file or from system environmental variables.
Follows the global configuration scheme as explained in http://dev.iron.io/mq/reference/configuration/.
The configuration is constructed as follows:
=over 8
=item 1. The global configuration file sets the defaults according to the file hierarchy. (F<.iron.json> in home folder)
=item 2. The global environment variables overwrite the global configuration file's values.
=item 3. The product-specific environment variables overwrite everything before them.
=item 4. The local configuration file overwrites everything before it according to the file hierarchy. (F<iron.json> in the same directory as the script being run)
=item 5. The configuration file specified when instantiating the client library overwrites everything before it according to the file hierarchy.
=item 6. The arguments passed when instantiating the client library overwrite everything before them.
=back
Return: ref to %config.
=head2 contains_rfc_3986_res_chars
Check that the string does not contain any RFC 3986 Reserved Characters:
!$&'()*+,;=:/?#[]@
Return True (1) if contains. Otherwise False (0).
=head1 AUTHOR
Mikko Koivunalho <mikko.koivunalho@iki.fi>
=head1 BUGS
Please report any bugs or feature requests to bug-io-iron@rt.cpan.org or through the web interface at:
http://rt.cpan.org/Public/Dist/Display.html?Name=IO-Iron
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2023 by Mikko Koivunalho.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut