NAME
PAGI::StructuredParameters - Whitelist and structure incoming request parameters
SYNOPSIS
use PAGI::StructuredParameters;
my $params = PAGI::StructuredParameters->new(
src => 'body',
src_data => {
username => 'jnap',
'name.first' => 'John',
'name.last' => 'Napiorkowski',
'email[0]' => 'jnap@example.com',
},
);
my $clean = $params->permitted(
'username',
name => ['first', 'last'],
+{ email => [] },
);
# $clean = {
# username => 'jnap',
# name => { first => 'John', last => 'Napiorkowski' },
# email => ['jnap@example.com'],
# }
DESCRIPTION
A no-dependency port of the core of Catalyst::Utils::StructuredParameters, decoupled from Catalyst. Its job is to whitelist and structure incoming parameters before they reach a model: a prior layer to validation, not validation itself. Validation of values is out of scope (use a downstream validator such as Valiant).
Form and query parameters arrive as a flat hash whose keys encode nesting (name.first, email[0], children[0].age). The rule grammar reconstructs the named, present keys into the described nested shape and drops everything else. Already-nested body data (such as decoded JSON) is simply whitelisted.
CONSTRUCTOR
new
my $params = PAGI::StructuredParameters->new(
src => 'body', # 'body' | 'query' | 'data'
src_data => \%params, # the source hash to filter
namespace => \@fields, # optional starting namespace
flatten_array_value => $bool, # default: 1 for body/query, 0 for data
max_array_depth => 1000, # cap on array reconstruction
context => $obj, # optional; passed to required's callback
);
src_data is the materialized source hash. For request-bound, asynchronous sources (a POST body), use the adapters in PAGI::StructuredParameters::Request instead.
REQUEST ADAPTERS
These class methods bind the engine to a live PAGI::Request, returning a PAGI::StructuredParameters::Request whose permitted/required are asynchronous (the request body is read with await).
from_body
my $params = PAGI::StructuredParameters->from_body($req, $context);
Source is the request's form/body parameters (array values flattened by default).
from_query
my $params = PAGI::StructuredParameters->from_query($req, $context);
Source is the URI query string (array values flattened by default).
from_data
my $params = PAGI::StructuredParameters->from_data($req, $context);
Source is the decoded request body data such as JSON (arrays kept as-is).
The optional $context is passed as the first argument to required's on-missing callback.
RULE SPECIFICATIONS
'username', 'password'— scalar keys.name => ['first', 'last']— a nested hash, reconstructed fromname.first/name.last.+{ email => [] }— an array, reconstructed fromemail[0],email[1], ...+{ children => [['name','age']] }— an array of hashes, reconstructed fromchildren[0].name,children[1].age, ...
A leading arrayref is treated as a starting namespace affix, e.g. $params->permitted(['person'], 'name', 'age').
METHODS
namespace
$params->namespace(['person']);
Scope all subsequent rules under the given key path. Returns the object for chaining.
flatten_array_value
$params->flatten_array_value(0);
Toggle array-value flattening ("pick last"), a common workaround for HTML form controls that submit repeated values. On by default for body/query, off for data. Returns the object for chaining.
max_array_depth
$params->max_array_depth(50);
Cap the number of items reconstructed for any single array, guarding against hostile input. Throws PAGI::StructuredParameters::Exception::InvalidArrayLength when exceeded. Returns the object for chaining.
permitted
my $clean = $params->permitted(@rules);
Lenient whitelist. Returns a clean hashref containing only the permitted, present keys, reconstructed into the shape the rules describe. Absent keys are simply omitted.
required
my $clean = $params->required(@rules, $on_missing);
Strict whitelist. The trailing argument must be a coderef (otherwise a loud required needs an on-missing callback error is raised at the call site).
On success, returns the clean hashref, exactly as "permitted" would. If any required key is absent, $on_missing is invoked as $on_missing->($context, \@missing) — where $context is the object's context attribute (in PAGI::Nano this is the request context $c) and \@missing is an arrayref of every missing dotted key path — and its return value is thrown. PAGI::Nano's dispatch catches that thrown, respond-able value and sends it, making the failure response explicit and chosen at the call site.
Passing the context as an argument (rather than closing over it) lets the callback be a shared, named sub reused across routes.
SEE ALSO
PAGI::StructuredParameters::Request, PAGI::Nano, Catalyst::TraitFor::Request::StructuredParameters.
AUTHOR
John Napiorkowski <jjnapiork@cpan.org>
COPYRIGHT & LICENSE
Copyright 2026, John Napiorkowski.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.