Reputation: 1769
Stupid perl question. Say I'm preparing some API calls, and I want to declare the needed variables in a hash for neatness. How do I refer back to the same hash I'm declaring as I'm declaring it?
my %api_hash = (
'api_endpoint1' => {
'username' => 'foo',
'password' => 'bar',
'api_url' => 'https://192.168.1.10:9200',
'headers' => {Accept => 'application/json', Authorization => 'Basic ' . encode_base64($username . ':' . $password)}
},
'api_endpoint2' => {
'username' => 'bah',
'password' => 'wizz',
'api_url' => 'https://192.168.1.20:9182',
'headers' => {Accept => 'application/json', Authorization => 'Basic ' . encode_base64($username . ':' . $password)}
}
);
Instead of $username
and $password
in $api_hash{<endpoint name>}{'headers'}
, how would I set those to $api_hash{<endpoint name>}{'username'}
and $api_hash{<endpoint name>}{'password'}
in the same declaration statement?
Upvotes: 3
Views: 81
Reputation: 1832
For one-off applications, what you've done is just fine. If you're going to go through the trouble of making some code reusable, it'd better take you less time than writing out the extra "fluffy" code. If you're manually writing the username and password in the hash, just write it manually in the 2nd location as well.
It's hard to say what the Right Way[TM] is to do what you want without more information about what else you're doing. You can use the generator that mob showed. The "most right" way is probably to make it an object and make the headers
key a method call.
{ package MyEndpoint;
sub new {
my $class = shift;
my $self = { @_ };
return bless $self, $class;
}
sub get_headers {
my self = shift;
my %headers = %{$self->{-headers}};
if ($self->{-auth} eq "basic") {
$headers{Authorization} = sprintf "Basic %s",
encode_base64(join ":", @{$self}{qw/-username -password/});
}
return %headers;
}
my %api_hash = (
api_endpoint1 => MyEndpoint->new(
-username => 'foo',
-password => 'bar',
-api_url => 'https://192.168.1.10:9200',
-auth => 'basic',
-headers => {Accept => 'application/json'},
},
...
);
## Later on when using the endpoint
foreach my $endpoint (values %api_hash) {
my @headers = $endpoint->get_headers;
...
}
But to directly answer your question, to use values from a data structure while you're defining it, you have to abuse the parser.
my %api_hash;
for (1, 2) {
%api_hash = (
'api_endpoint1' => {
'username' => 'foo',
'password' => 'bar',
'api_url' => 'https://192.168.1.10:9200',
'headers' => {Accept => 'application/json',
Authorization => 'Basic ' . encode_base64(
$api_hash{api_endpoint1}{username} . ':' . $api_hash{api_endpoint1}{password}
)}
},
'api_endpoint2' => {
'username' => 'bah',
'password' => 'wizz',
'api_url' => 'https://192.168.1.20:9182',
'headers' => {Accept => 'application/json',
Authorization => 'Basic ' . encode_base64(
$api_hash{api_endpoint2}{username} . ':' . $api_hash{api_endpoint2}{password}
)}
}
);
}
That's right, you execute the assignment twice with a loop. The first pass sets all the plain values; at this point all the self references are undef
. On the second pass all the plain keys and values now exist so that the self references will resolve to the real values. You see why it's parser abuse. You're defining and overwriting the entire thing twice to get at those self references. For something small and quick and dirty that is just fine. This is Perl. However it's also wasteful and lame for Real Code[TM].
It's better to build it up from discreet variables or make it an object. This is one of the main reasons objects are awesome. You can shove all the boilerplate into a module and generate things only when you actually need them.
HTH
Upvotes: 1
Reputation: 118695
You can use a function to build the four key-value pairs of from your three inputs.
sub api_endpoint {
my ($user,$pw,$url) = @_;
return { username => $user, password => $pw, api_url => $url,
headers => { Accept => 'application/json',
Authorization => 'Basic ' . encode_base64($user . ':' . $pw)} }
}
my %api_hash = (
api_endpoint1 => api_endpoint('foo','bar','https://192.168.1.10:9200'),
api_endpoint2 => api_endpoint('bah','wizz','https://192.168.1.20:9182'),
...
);
Or to retain more of the look and feel of a hash declaration, have the function accept as much of the hashref as it can and return the hashref with the fourth argument
sub api_endpoint {
my ($ep) = @_;
$ep->{headers} = {
Accept => 'application/json',
Authorization => 'Basic '
. encode_base64($ep->{username} . ':' . $ep->{password}
};
return $ep;
}
my %api_hash = (
'api_endpoint1' => api_endpoint({
'username' => 'foo',
'password' => 'bar',
'api_url' => 'https://192.168.1.10:9200',
}),
'api_endpoint2' => api_endpoint({
'username' => 'bah',
'password' => 'wizz',
'api_url' => 'https://192.168.1.20:9182',
})
);
Upvotes: 3
Reputation: 62236
You can declare everything in the hash except the headers, then add them with a for loop after the declaration:
use warnings;
use strict;
my %api_hash = (
'api_endpoint1' => {
'username' => 'foo',
'password' => 'bar',
'api_url' => 'https://192.168.1.10:9200',
},
'api_endpoint2' => {
'username' => 'bah',
'password' => 'wizz',
'api_url' => 'https://192.168.1.20:9182',
}
);
for my $ep (keys %api_hash) {
my $auth = 'Basic ' . encode_base64($api_hash{$ep}{username} . ':' . $api_hash{$ep}{password});
$api_hash{$ep}{headers} = {Accept => 'application/json', Authorization => $auth}
}
Upvotes: 2