| File: | blib/lib/App/FileSync/Args.pm |
| Coverage: | 100.0% |
| line | stmt | bran | cond | sub | time | code |
|---|---|---|---|---|---|---|
| 1 | package App::FileSync::Args; | |||||
| 2 | ||||||
| 3 | # One place that turns a routine's @_ into a validated hashref, so every public | |||||
| 4 | # entry point reads its arguments the same way and reports a bad one with a | |||||
| 5 | # catalogued message instead of the validator's own text. | |||||
| 6 | ||||||
| 7 | 324 324 324 | 744 255 4362 | use App::FileSync::Platform; | |||
| 8 | ||||||
| 9 | 324 324 324 | 535 246 2602 | use strict; | |||
| 10 | 324 324 324 | 444 293 5920 | use warnings; | |||
| 11 | 324 324 324 | 497 243 650 | use autodie qw(:all); | |||
| 12 | ||||||
| 13 | 324 324 324 | 689674 266 4563 | use Exporter qw(import); | |||
| 14 | 324 324 324 | 771 5249 6608 | use Params::Get qw(get_params); | |||
| 15 | 324 324 324 | 906 12672 5827 | use Params::Validate::Strict qw(validate_strict); | |||
| 16 | 324 324 324 | 749 12153 1040 | use Sub::Private { mode => 'enforce' }; | |||
| 17 | ||||||
| 18 | 324 324 324 | 33621 270 6512 | use App::FileSync::I18N qw(throw strip_location); | |||
| 19 | ||||||
| 20 | 324 324 324 | 547 225 676 | use version; our $VERSION = version->declare('0.001.1'); | |||
| 21 | ||||||
| 22 | our @EXPORT_OK = qw(named positional); | |||||
| 23 | ||||||
| 24 | # Errors should point at the caller of the routine whose arguments were bad. | |||||
| 25 | $Carp::Internal{ (__PACKAGE__) }++; | |||||
| 26 | ||||||
| 27 | =encoding utf8 | |||||
| 28 | ||||||
| 29 - 100 | =head1 NAME
App::FileSync::Args - validated arguments for App::FileSync's public routines
=head1 SYNOPSIS
use App::FileSync::Args qw(named positional);
sub repo_create {
my $self = shift;
my $args = named(\@_, {
name => { type => 'string', min => 1 },
trash_days => { type => 'integer', min => 0, optional => 1 },
}, 'repo_create');
...
}
sub load_repo {
my $args = positional(\@_, [qw(root repo)], {
root => { type => 'string', min => 1 },
repo => { type => 'string', min => 1 },
}, 'load_repo');
}
=head1 FUNCTIONS
=head2 named
Purpose: validate C<key =E<gt> value> arguments (or a single hashref).
Args: C<\@args>, C<\%schema> (L<Params::Validate::Strict> format), C<$routine>
(the name used in an error).
Returns: a hashref of validated (and coerced) arguments. An empty argument
list is an empty hash.
Side Effects: raises C<args.invalid> naming the routine and the problem.
Usage:
my $args = named(\@_, $SCHEMA, 'verify');
=head3 EXAMPLE
my $args = named([ deep => 1 ], { deep => { type => 'boolean', optional => 1 } }, 'verify');
=head3 API SPECIFICATION
=head4 INPUT
[
{ name => 'args', type => 'arrayref' },
{ name => 'schema', type => 'hashref' },
{ name => 'routine', type => 'string', min => 1 },
]
=head4 OUTPUT
{ type => 'hashref' }
=head3 MESSAGES
Key Meaning Resolution
args.invalid an argument is missing, unknown or invalid fix the call
=head3 FORMAL SPECIFICATION
named : seq VALUE à SCHEMA à NAME â (KEY ⸠VALUE)
pre pairs(args) satisfies schema
post result = coerce(pairs(args))
=cut | |||||
| 101 | ||||||
| 102 | sub named { | |||||
| 103 | 5780 | 9898 | my ($args, $schema, $routine) = @_; | |||
| 104 | ||||||
| 105 | 5780 5780 | 4317 11553 | my $input = @{$args} ? get_params(undef, $args) : {}; | |||
| 106 | 5780 | 73574 | return _validate($input, $schema, $routine); | |||
| 107 | } | |||||
| 108 | ||||||
| 109 - 154 | =head2 positional
Purpose: validate positional arguments by naming them.
Args: C<\@args>, C<\@names> (one per position), C<\%schema>, C<$routine>.
Returns: a hashref keyed by C<@names>; missing trailing arguments are
C<undef> and must be C<optional> in the schema.
Side Effects: raises C<args.invalid>.
Usage:
my $args = positional(\@_, [qw(root repo)], $SCHEMA, 'load_repo');
=head3 EXAMPLE
my $args = positional([ '/srv', 'docs' ], [qw(root repo)],
{ root => { type => 'string' }, repo => { type => 'string' } }, 'load_repo');
=head3 API SPECIFICATION
=head4 INPUT
[
{ name => 'args', type => 'arrayref' },
{ name => 'names', type => 'arrayref', min => 1 },
{ name => 'schema', type => 'hashref' },
{ name => 'routine', type => 'string', min => 1 },
]
=head4 OUTPUT
{ type => 'hashref' }
=head3 MESSAGES
Key Meaning Resolution
args.invalid an argument is missing or invalid fix the call
=head3 FORMAL SPECIFICATION
positional(a, n, s, r) = named(zip(n, a), s, r)
pre #a ⤠#n
=cut | |||||
| 155 | ||||||
| 156 | sub positional { | |||||
| 157 | 1007 | 1593 | my ($args, $names, $schema, $routine) = @_; | |||
| 158 | ||||||
| 159 | # Map by position directly: get_params treats a lone hashref argument as | |||||
| 160 | # named pairs, which is wrong for a positional routine that takes one. | |||||
| 161 | 1007 | 932 | my %input; | |||
| 162 | 1007 1007 1007 | 851 1477 1166 | @input{ @{$names} } = @{$args}; | |||
| 163 | 1007 1810 | 1568 2288 | delete @input{ grep { !defined $input{$_} } keys %input }; | |||
| 164 | ||||||
| 165 | 1007 | 2077 | return _validate(\%input, $schema, $routine); | |||
| 166 | } | |||||
| 167 | ||||||
| 168 | # _validate | |||||
| 169 | # Purpose: run Params::Validate::Strict and translate its failure. | |||||
| 170 | # Entry: $input is a hashref; $schema a Params::Validate::Strict schema. | |||||
| 171 | # Exit: returns the validated hashref, or raises args.invalid. | |||||
| 172 | # Side effects: none beyond the raise. | |||||
| 173 | sub _validate :Private { | |||||
| 174 | my ($input, $schema, $routine) = @_; | |||||
| 175 | ||||||
| 176 | my $valid = eval { validate_strict(schema => $schema, input => $input) }; | |||||
| 177 | unless ($valid) { | |||||
| 178 | # Drop the validator's own prefix and location; the routine name says | |||||
| 179 | # where, and the rest says what. | |||||
| 180 | my $reason = strip_location($@); | |||||
| 181 | $reason =~ s/\AParams::Validate::Strict line \d+: (?:validate_strict: )?//; | |||||
| 182 | $reason =~ s/ at \S+ line \d+\.?\z//; | |||||
| 183 | throw('args.invalid', { routine => $routine, reason => $reason }); | |||||
| 184 | } | |||||
| 185 | ||||||
| 186 | return $valid; | |||||
| 187 | 324 324 324 | 72386 286 4230 | } | |||
| 188 | ||||||
| 189 | 1; | |||||
| 190 | ||||||