lib/Params/Get.pm

Structural Coverage (Approximate)

TER1 (Statement): 100.00%
TER2 (Branch): 100.00%
TER3 (LCSAJ): 100.0% (7/7)
Approximate LCSAJ segments: 45

LCSAJ Legend

Covered — this LCSAJ path was executed during testing.

Not covered — this LCSAJ path was never executed. These are the paths to focus on.

Multiple dots on a line indicate that multiple control-flow paths begin at that line. Hovering over any dot shows:

        start → end → jump
        

Uncovered paths show [NOT COVERED] in the tooltip.

Mutant Testing Legend

Survived (tests missed this) Killed (tests detected this) No mutation
    1: package Params::Get;
    2: 
    3: # Normalises the many calling conventions Perl callers use when passing
    4: # arguments -- positional scalar, named pairs, hashref, arrayref -- into a
    5: # single hashref so the receiving sub need not care which style was used.
    6: 
    7: # TODO: Investigate Params::Smart
    8: 
    9: use strict;
   10: use warnings;
   11: use autodie qw(:all);
   12: 
   13: use parent 'Exporter';
   14: 
   15: use Carp ();
   16: use Scalar::Util ();
   17: 
   18: use Readonly;
   19: 
   20: our @EXPORT_OK = qw(get_params);
   21: 
   22: =head1 NAME
   23: 
   24: Params::Get - Normalise subroutine arguments regardless of calling convention
   25: 
   26: =head1 VERSION
   27: 
   28: Version 0.17
   29: 
   30: =cut
   31: 
   32: our $VERSION = '0.17';
   33: 
   34: # Reference-type sentinels.  Collected here so a typo is a compile-time
   35: # error via Readonly and grep/ack finds every usage in one search.
   36: Readonly::Scalar my $T_HASH   => 'HASH';
   37: Readonly::Scalar my $T_ARRAY  => 'ARRAY';
   38: Readonly::Scalar my $T_SCALAR => 'SCALAR';
   39: Readonly::Scalar my $T_CODE   => 'CODE';
   40: Readonly::Scalar my $T_REF    => 'REF';
   41: 
   42: =head1 DESCRIPTION
   43: 
   44: C<Params::Get> exports a single function, C<get_params>, which accepts a
   45: caller's argument list (or a reference to it) in any of the common Perl
   46: calling conventions and returns a unified hash-ref.  Library authors can
   47: write one normalisation call at the top of every public method rather than
   48: hand-rolling the same conditional chains in each one.
   49: 
   50: When combined with L<Params::Validate::Strict> and L<Return::Set> you can
   51: formally specify and enforce the input and output contracts of every method.
   52: 
   53: =head1 SYNOPSIS
   54: 
   55:     use Params::Get qw(get_params);
   56:     use Params::Validate::Strict;
   57: 
   58:     sub where_am_i {
   59:         my $params = Params::Validate::Strict::validate_strict({
   60:             args   => get_params(undef, \@_),
   61:             schema => {
   62:                 latitude  => { type => 'number', min => -90,  max =>  90 },
   63:                 longitude => { type => 'number', min => -180, max => 180 },
   64:             },
   65:         });
   66:         printf "You are at %s, %s\n",
   67:             $params->{latitude}, $params->{longitude};
   68:     }
   69: 
   70:     where_am_i(latitude => 0.3, longitude => 124);
   71:     where_am_i({ latitude => 3.14, longitude => -155 });
   72: 
   73: =head1 METHODS
   74: 
   75: =head2 get_params
   76: 
   77: Parse the argument list passed to a subroutine and return a unified hash-ref
   78: regardless of the calling convention used.  Supported conventions:
   79: 
   80: =over 4
   81: 
   82: =item * Single hash-ref: C<foo({ a =E<gt> 1 })>
   83: 
   84: =item * Named key/value pairs: C<foo(a =E<gt> 1, b =E<gt> 2)>
   85: 
   86: =item * Single scalar with a default key: C<foo('US')> given C<get_params('country', @_)>
   87: 
   88: =item * Array-ref shorthand: C<foo(\@_)> inside the callee
   89: 
   90: =item * Mandatory positional argument plus an options hash-ref:
   91: C<Obj-E<gt>new($val, { opt =E<gt> 1 })>
   92: 
   93: =item * Scalar-ref: C<foo(\'text')> -- dereferenced automatically
   94: 
   95: =item * Blessed object or CODE ref: mapped under C<$default>
   96: 
   97: =item * Array-ref of positional key names as C<$default>:
   98: C<get_params([qw(name age)], @_)>
   99: 
  100: =back
  101: 
  102: =head3 ARGUMENTS
  103: 
  104: =over 4
  105: 
  106: =item C<$default> (scalar string, arrayref of strings, or C<undef>)
  107: 
  108: Controls how a single non-hash argument is interpreted:
  109: 
  110: =over 8
  111: 
  112: =item * B<string> -- used as the key name when a lone scalar, ref, or object
  113: is received.
  114: 
  115: =item * B<arrayref of strings> -- positional key names; the I<n>th argument
  116: is mapped to the I<n>th name.  Extra arguments are silently discarded;
  117: missing arguments produce C<undef> values.
  118: 
  119: =item * B<undef> -- no default key; the caller must pass named pairs or a
  120: single hash-ref.  An empty argument list returns C<undef>.
  121: 
  122: =back
  123: 
  124: =item C<@args>
  125: 
  126: The caller's argument list, passed either as a flat list (C<@_>) or as a
  127: reference to the array (C<\@_>).  Both forms are accepted transparently.
  128: 
  129: =back
  130: 
  131: =head3 RETURNS
  132: 
  133: A hash-ref on success, or C<undef> when C<$default> is C<undef> and no
  134: arguments are provided.
  135: 
  136: =head3 SIDE EFFECTS
  137: 
  138: Croaks from the caller's frame on programming errors (wrong calling
  139: convention, non-ARRAY ref passed as C<$default>).  Confesses with a full
  140: stack trace when C<$default> is defined but zero arguments are received,
  141: because that almost always indicates a programming error.
  142: 
  143: =head3 API SPECIFICATION
  144: 
  145: =head4 Input
  146: 
  147: 	{
  148: 		default => {
  149: 			type => [ 'string', 'stringref' ],
  150: 			optional => 1,
  151: 			position => 0,
  152: 		}, args => {
  153: 			type => [ 'array', 'arrayref' ],
  154: 			optional => 1,
  155: 			position => 1,
  156: 		}
  157: 	}
  158: 
  159: =head4 Domain Partitions and Boundary Values
  160: 
  161: B<C<$default> -- position 0>
  162: 
  163:     Valid partitions:
  164:       EP1  undef           No default key; caller must pass named pairs or a hashref.
  165:       EP2  non-empty str   Used as the hash key for a single positional argument.
  166:                            Truthy: the two-element \@_ shorthand is active.
  167:       EP3  ""              Valid but FALSY; the shorthand guard ($default && ...)
  168:                            is suppressed.  Named-pairs path is used instead.
  169:       EP4  "0"             Valid but FALSY; identical behaviour to EP3.
  170:       EP5  ARRAY ref       Positional-names mode: nth arg maps to nth key name.
  171:       EP6  []              Valid; 0 named slots -- all positional args discarded.
  172: 
  173:     Invalid partitions (croak at guard, before @args is inspected):
  174:       EP7  CODE ref
  175:       EP8  HASH ref
  176:       EP9  SCALAR ref
  177:       EP10 REF (ref-of-ref)
  178:       EP11 Blessed non-ARRAY object  (ref() returns class name, not expected type)
  179:       EP12 Blessed ARRAY object      (ref() returns class name, not "ARRAY")
  180: 
  181:     BVA edges for string $default:
  182:       length=0  ("")  Valid, falsy, opaque key.
  183:       length=1        Minimum truthy string; shorthand guard active.
  184:       length=65536    Maximum tested; accepted without error.
  185: 
  186:     BVA edges for ARRAY ref $default key count:
  187:       0 keys          All positional args discarded; {} returned.
  188:       1 key           Only first arg mapped.
  189:       keys > @args    Missing args produce undef-valued slots (no warning).
  190:       keys < @args    Extra args silently discarded.
  191: 
  192: B<C<@args> -- position 1..N>
  193: 
  194:     Valid partitions:
  195:       CN0a  0 args, EP1 ($default undef)   Returns undef.
  196:       CN1   1 HASH ref, no $default        Fast path; hashref returned by identity.
  197:       CN2   1 non-HASH, defined $default   Arg wrapped under $default key.
  198:       CN3   2 args, 2nd HASH, defined $d   OO constructor path; see below.
  199:       CN4   even N >= 2, no CN3 match      Flat key/value pairs.
  200: 
  201:     Invalid partitions:
  202:       CN0b  0 args, defined $default       Carp::confess with full stack trace.
  203:       CN5   odd N >= 3                     Carp::croak with usage message.
  204: 
  205:     BVA edges for @args count:
  206:       0     Minimum; routes to CN0a or CN0b.
  207:       1     Single-arg dispatch; many sub-partitions by arg type (see below).
  208:       2     Even minimum; triggers CN3 when 2nd arg is a HASH ref.
  209:       3     Minimum odd; triggers CN5 (croak).
  210:       4     Minimum even for CN4 (plain pairs).
  211:       1000  Maximum tested; accepted in O(n).
  212: 
  213: B<OO constructor options hash -- key-count BVA>
  214: 
  215: When C<$num_args == 2> and C<ref($args-E<gt>[1]) eq 'HASH'>:
  216: 
  217:     keys=0  Empty hashref stored as value: { $default => {} }.
  218:     keys=1  Non-empty; if first arg IS $default key name: hashref wrapped.
  219:             Otherwise: first arg is mandatory value; options merged in.
  220:     keys=N  Same as keys=1 non-match case; all options merged.
  221: 
  222: B<Single-arg type sub-partitions (with defined C<$default>)>
  223: 
  224:     undef          Wrapped: { $default => undef }
  225:     ""             Wrapped: { $default => "" }
  226:     "0"            Wrapped: { $default => "0" }
  227:     string         Wrapped as-is.
  228:     SCALAR ref     Dereferenced then wrapped: { $default => ${$arg} }.
  229:     ARRAY ref      Wrapped as-is (not dereferenced).
  230:     CODE ref       Wrapped as-is.
  231:     blessed object Wrapped as-is.
  232:     HASH ref       LIMITATION: bypasses $default; returned by identity.
  233: 
  234: =head4 output
  235: 
  236:     {
  237: 	type => 'hashref',
  238: 	optional => 1,
  239:     }
  240: 
  241: =head3 MESSAGES
  242: 
  243:     Message                                             Meaning                                Resolution
  244:     --------------------------------------------------  -------------------------------------  ----------------------------------------------
  245:     ::get_params: $default must be a scalar or          A non-ARRAY ref was passed as          Pass a plain string, arrayref of strings,
  246:       arrayref                                          $default                               or undef
  247:     Usage: Pkg->method($key => $val)  [stack trace]    $default is defined but no args given  Ensure the caller always passes a value
  248:     Usage: Pkg->method()                               Odd-length or unrecognisable arg list  Correct the calling convention in the caller
  249: 
  250: =head3 PSEUDOCODE
  251: 
  252:     1.  Fast-path: if the sole argument is a plain HASH ref, return it
  253:         immediately (fires before $default is inspected -- see LIMITATIONS).
  254: 
  255:     2.  Shift $default.  Validate: must be undef, a plain scalar, or an
  256:         ARRAY ref.  Any other ref type croaks immediately.
  257: 
  258:     3.  If $default_ref eq "ARRAY", map remaining @_ positionally to the key
  259:         names and return.  (Premise: an ARRAY ref is always truthy, so no
  260:         separate truthiness pre-check is needed -- $default_ref eq "ARRAY"
  261:         is sufficient.)
  262: 
  263:     4.  Detect the \@_ calling convention: if exactly one ARRAY ref argument
  264:         remains, check the two-element (key => scalar-val) shorthand and
  265:         return immediately when it matches.  The shorthand guard uses
  266:         truthiness (not definedness) so that falsy $default strings ("0", "")
  267:         suppress it -- this is a documented invariant.  Otherwise unwrap and
  268:         use the array contents as the effective @args.
  269: 
  270:     5.  Dispatch on argument count:
  271:         0 -- confess (with stack trace) if $default is defined;
  272:              return undef otherwise.
  273:         1 -- if $default is defined, two arms cover all wrappable types:
  274:                SCALAR ref  -> deref then wrap (pulled left as fast guard).
  275:                plain scalar | ARRAY | CODE | blessed -> wrap as-is.
  276:              Unblessed HASH and exotic refs fall through to the no-default
  277:              path (see LIMITATIONS).
  278:              Without $default: unwrap REF-of-REF, pass HASH ref through,
  279:              return empty ARRAY ref as-is.  Anything else: croak.
  280:         2 with HASH ref as arg[1]
  281:           -- Mandatory-positional + options-hashref pattern.
  282:         even N -- treat as flat key/value pairs.
  283:         odd N  -- croak.
  284: 
  285: =cut
  286: 
  287: sub get_params
  288: {
  289: 	# Fast path: sole argument is already a plain hashref.  Returning it
  290: 	# directly avoids the overhead of shifting and inspecting $default.
  291: 	# Consequence: a single hashref always bypasses default key naming --
  292: 	# documented in LIMITATIONS.
293 → 298 → 306  293: 	return $_[0] if (@_ == 1) && (ref($_[0]) eq $T_HASH);

Mutants (Total: 3, Killed: 3, Survived: 0)

294: 295: my $default = shift; 296: my $default_ref = ref($default); 297: 298: if ($default_ref && ($default_ref ne $T_ARRAY)) {

Mutants (Total: 1, Killed: 1, Survived: 0)

299: Carp::croak(__PACKAGE__, '::get_params: $default must be a scalar or arrayref'); 300: } 301: 302: # Positional-names feature: $default is an arrayref of key names and the 303: # remaining @_ are values to map to those keys in order. 304: # Premise: ref(X) eq "ARRAY" implies X is a reference, which is always truthy. 305: # Conclusion: the "$default &&" pre-check would never flip this branch; omitted. 306 → 306 → 315 306: if($default_ref eq $T_ARRAY) {

Mutants (Total: 1, Killed: 1, Survived: 0)

307: # Honour the single-hashref passthrough for consistency with scalar $default. 308: return $_[0] if (@_ == 1) && (ref($_[0]) eq $T_HASH);

Mutants (Total: 3, Killed: 3, Survived: 0)

309: my %rc; 310: { no warnings 'uninitialized'; @rc{@{$default}} = @_[0 .. $#{$default}] } 311: return \%rc;

Mutants (Total: 2, Killed: 2, Survived: 0)

312: } 313: 314: # Detect \@_ usage: caller passed a reference to its own @_. 315 → 316 → 329 315: my ($args, $from_arrayref); 316: if ((@_ == 1) && (ref($_[0]) eq $T_ARRAY)) {

Mutants (Total: 2, Killed: 2, Survived: 0)

317: # Two-element shorthand: caller did routine('key' => 'scalar') and the 318: # callee received \@_. Only fires when the value is a plain scalar to 319: # avoid ambiguity with an arrayref value. 320: if($default && (@{$_[0]} == 2) && ($_[0]->[0] eq $default) && !ref($_[0]->[1])) {

Mutants (Total: 2, Killed: 2, Survived: 0)

321: return { $default => $_[0]->[1] }; 322: } 323: $args = $_[0]; 324: $from_arrayref = 1; 325: } else { 326: $args = \@_; 327: } 328: 329 → 332 → 342 329: my $num_args = scalar @{$args}; 330: 331: # --- Zero arguments --- 332: if ($num_args == 0) {

Mutants (Total: 2, Killed: 2, Survived: 0)

333: if (defined $default) {

Mutants (Total: 1, Killed: 1, Survived: 0)

334: # Full stack trace via Devel::Confess because receiving zero args 335: # when a default is defined is virtually always a programming error. 336: Carp::confess('Usage: ', __PACKAGE__, '->', (caller(1))[3], "($default => \$val)"); 337: } 338: return; 339: } 340: 341: # --- One argument --- 342 → 342 → 381 342: if ($num_args == 1) {

Mutants (Total: 2, Killed: 2, Survived: 0)

343: if (defined $default) {

Mutants (Total: 1, Killed: 1, Survived: 0)

344: my $arg = $args->[0]; 345: my $kind = ref($arg); 346: 347: # SCALAR ref is the only arm with a distinct action (deref before wrap); 348: # pull it left as a fast guard (Modus Ponens / fail-fast). 349: return { $default => ${$arg} } if $kind eq $T_SCALAR; 350: 351: # De Morgan reduction: four arms with identical action collapse to one 352: # disjunction. Premise: !$kind (plain scalar), ARRAY, CODE, and blessed 353: # objects all return the arg as-is under $default. 354: # Conclusion: unblessed HASH / REF / exotic refs fall through to the 355: # no-default path below (see LIMITATIONS). 356: return { $default => $arg } 357: if !$kind || $kind eq $T_ARRAY || $kind eq $T_CODE 358: || Scalar::Util::blessed($arg); 359: } 360: 361: return unless defined $args->[0]; 362: 363: # Copy before type checks: $args->[0] is an alias to the caller's 364: # variable via @_ -- assigning through it would silently mutate the 365: # caller's data. Work on a named copy instead. 366: my $val = $args->[0]; 367: $val = ${$val} if ref($val) eq $T_REF; 368: 369: return $val if ref($val) eq $T_HASH;

Mutants (Total: 2, Killed: 2, Survived: 0)

370: 371: # Empty arrayref with no default: return the ref itself. 372: if ((ref($val) eq $T_ARRAY) && (@{$val} == 0)) {

Mutants (Total: 2, Killed: 2, Survived: 0)

373: return $val;

Mutants (Total: 2, Killed: 2, Survived: 0)

374: } 375: 376: Carp::croak('Usage: ', __PACKAGE__, '->', (caller(1))[3], '()'); 377: } 378: 379: # --- Two arguments where the second is a hash ref --- 380: # Handles the Obj->new($mandatory, \%options) convention. 381 → 381 → 395 381: if (($num_args == 2) && (ref($args->[1]) eq $T_HASH)) {

Mutants (Total: 2, Killed: 2, Survived: 0)

382: if (defined $default) {

Mutants (Total: 1, Killed: 1, Survived: 0)

383: if (scalar keys %{$args->[1]}) {

Mutants (Total: 1, Killed: 1, Survived: 0)

384: # When first arg is the default key name itself, second arg is its value. 385: return { $default => $args->[1] } if $args->[0] eq $default; 386: # Otherwise: first arg is the mandatory value; options are merged. 387: return { $default => $args->[0], %{$args->[1]} }; 388: } 389: # Empty options hashref: store the ref as the value. 390: return { $default => $args->[1] }; 391: } 392: } 393: 394: # --- \@_ with multiple values under a scalar default --- 395 → 398 → 402 395: return { $default => $args } if $from_arrayref && defined $default; 396: 397: # --- Even-length list: flat key/value pairs --- 398: if (($num_args % 2) == 0) {

Mutants (Total: 2, Killed: 2, Survived: 0)

399: return { @{$args} }; 400: } 401: 402: Carp::croak('Usage: ', __PACKAGE__, '->', (caller(1))[3], '()'); 403: } 404: 405: =head1 LIMITATIONS 406: 407: =over 4 408: 409: =item B<Single empty arrayref cannot be distinguished from C<\@_> of an empty list> 410: 411: When the caller does C<foo([])> and the callee uses C<get_params('key', @_)>, 412: the C<@_> list is C<([])> -- one element, an arrayref. The function 413: interprets the lone arrayref as a C<\@_> passthrough, unwraps it to an empty 414: list, and then croaks because C<$default> is defined but there are zero 415: arguments. Workaround: pass the value as a named pair 416: (C<key =E<gt> []>) or ensure the callee always uses C<\@_>. 417: 418: =item B<Single hash ref always bypasses C<$default> key naming> 419: 420: C<get_params('config', { a =E<gt> 1 })> returns C<{ a =E<gt> 1 }>, not 421: C<{ config =E<gt> { a =E<gt> 1 } }>. The fast path fires before C<$default> 422: is inspected. To store a hash ref under a default key, pass it as a named 423: pair: C<get_params('config', config =E<gt> { a =E<gt> 1 })>. 424: 425: =item B<No mechanism to mark a C<$default> argument as optional> 426: 427: When C<$default> is a string and zero arguments are received, the function 428: always confesses. There is no way to express I<"accept zero args and return 429: undef gracefully">. 430: 431: =item B<Duplicate keys in a flat list silently overwrite; last value wins> 432: 433: C<get_params(undef, foo =E<gt> 1, foo =E<gt> 2)> returns C<{ foo =E<gt> 2 }> 434: with no warning. If an attacker controls part of the argument list, a 435: later duplicate key can silently override an earlier sanitised value. 436: Detect and reject duplicate keys in the validation layer (e.g. 437: L<Params::Validate::Strict>) rather than relying on C<get_params> to catch 438: them. 439: 440: =item B<Positional-names C<$default> silently discards extra arguments> 441: 442: C<get_params([qw(a b)], 1, 2, 3)> returns C<{ a =E<gt> 1, b =E<gt> 2 }> 443: and ignores C<3>. If strict arity is required, validate the returned hash 444: with L<Params::Validate::Strict>. 445: 446: =back 447: 448: =head1 AUTHOR 449: 450: Nigel Horne, C<< <njh at nigelhorne.com> >> 451: 452: =head1 BUGS 453: 454: Please report bugs or feature requests to C<bug-params-get at rt.cpan.org> 455: or through L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Params-Get>. 456: 457: =head1 SEE ALSO 458: 459: =over 4 460: 461: =item * L<Params::Smart> 462: 463: =item * L<Params::Validate::Strict> 464: 465: =item * L<Return::Set> 466: 467: =item * L<Test Dashboard|https://nigelhorne.github.io/Params-Get/coverage/> 468: 469: =back 470: 471: =head1 SUPPORT 472: 473: This module is provided as-is without any warranty. 474: 475: =over 4 476: 477: =item * MetaCPAN: L<https://metacpan.org/dist/Params-Get> 478: 479: =item * RT: L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=Params-Get> 480: 481: =item * CPAN Testers: L<http://matrix.cpantesters.org/?dist=Params-Get> 482: 483: =item * CPAN Testers Dependencies: L<http://deps.cpantesters.org/?module=Params::Get> 484: 485: =back 486: 487: =head2 FORMAL SPECIFICATION 488: 489: =head3 get_params 490: 491: Let D = default key (Str | [Str*] | undef), A = argument tuple. 492: 493: get_params : D x A* -> HashRef | Undef 494: 495: -- Fast path (fires before D is inspected -- see LIMITATIONS) 496: get_params(D, h) == h when |A|=1, h:HashRef 497: 498: -- Positional-names default 499: get_params([n1..nk], v*) == {ni -> vi} i in 1..k, vi = undef when missing 500: 501: -- Scalar default, single arg 502: get_params(d, s) == {d -> s} d:Str, s:Scalar 503: get_params(d, a) == {d -> a} d:Str, a:ArrayRef 504: get_params(d, \s) == {d -> s} d:Str (scalarref dereferenced) 505: get_params(d, c) == {d -> c} d:Str, c:CodeRef 506: get_params(d, o) == {d -> o} d:Str, o:BlessedObject 507: 508: -- Mandatory-positional + options-hashref 509: get_params(d, v, {k->w..}) == {d->v, k->w..} non-empty opts 510: get_params(d, d, {k->w..}) == {d -> {k->w..}} first arg IS the key name 511: 512: -- Named pairs 513: get_params(undef, k1,v1..) == {ki -> vi} when |A| is even 514: 515: -- Empty / error 516: get_params(undef) == undef 517: get_params(d) => confess d:Str (missing required arg) 518: get_params(D, odd-list) => croak 519: 520: =head1 LICENCE AND COPYRIGHT 521: 522: Copyright 2025-2026 Nigel Horne. 523: 524: Usage is subject to the GPL2 licence terms. If you use this module, 525: please let me know. 526: 527: =cut 528: 529: 1;