SYNOPSIS
Non-OO interface:
use Data::Sah qw(
normalize_schema
gen_validator
);
# generate a validator for schema
my $v = gen_validator(["int*", min=>1, max=>10]);
# validate your data using the generated validator
say "valid" if $v->(5); # valid
say "valid" if $v->(11); # invalid
say "valid" if $v->(undef); # invalid
say "valid" if $v->("x"); # invalid
# generate validator which reports error message string, in Indonesian
my $v = gen_validator(["int*", min=>1, max=>10],
{return_type=>'str', lang=>'id_ID'});
say $v->(5); # ''
say $v->(12); # 'Data tidak boleh lebih besar dari 10'
# (in English: 'Data must not be larger than 10')
# normalize a schema
my $nschema = normalize_schema("int*"); # => ["int", {req=>1}, {}]
normalize_schema(["int*", min=>0]); # => ["int", {min=>0, req=>1}, {}]
OO interface (more advanced usage):
use Data::Sah;
my $sah = Data::Sah->new;
# get perl compiler
my $pl = $sah->get_compiler("perl");
# compile schema into Perl code
my $cd = $pl->compile(schema => ["int*", min=>0]);
say $cd->{result};
will print something like:
# req #0
(defined($data))
&&
# check type 'int'
(Scalar::Util::Numeric::isint($data))
&&
(# clause: min
($data >= 0))
To see the full validator code (with sub {} and all), you can do
something like:
% LOG=1 LOG_SAH_VALIDATOR_CODE=1 TRACE=1 perl -MLog::Any::App -MData::Sah=gen_validator -E'gen_validator(["int*", min=>0])'
which will print log message like:
normalized schema=['int',{min => 0,req => 1},{}]
validator code:
1|do {
2| require Scalar::Util::Numeric;
3| sub {
4| my ($data) = @_;
5| my $_sahv_res =
|
7| # req #0
8| (defined($data))
|
10| &&
|
12| # check type 'int'
13| (Scalar::Util::Numeric::isint($data))
|
15| &&
|
17| (# clause: min
18| ($data >= 0));
|
20| return($_sahv_res);
21| }}
STATUS
Some features are not implemented yet:
* def/subschema
* expression
* buf type
* date/datetime type
* obj: meths, attrs properties
* .prio, .err_msg, .ok_err_msg attributes
* .result_var attribute
* BaseType: if, prefilters, postfilters, check, prop, check_prop
clauses
* HasElems: each_elem, each_index, check_each_elem, check_each_index,
exists clauses
* HasElems: len, elems, indices properties
* hash: check_each_key, check_each_value, allowed_keys_re,
forbidden_keys_re clauses
* array: uniq clauses
* human compiler: markdown output
* markdown output
DESCRIPTION
This module, Data::Sah, implements compilers for producing Perl and
JavaScript validators, as well as translatable human description text
from Sah schemas. Compiler approach is used instead of interpreter for
faster speed.
The generated validator code can run without this module.
EXPORTS
None exported by default.
normalize_schema($schema) => ARRAY
Normalize $schema.
Can also be used as a method.
gen_validator($schema, \%opts) => CODE (or STR)
Generate validator code for $schema. Can also be used as a method.
Known options (unknown options will be passed to Perl schema compiler):
* accept_ref => BOOL (default: 0)
Normally the generated validator accepts data, as in:
$res = $vdr->($data);
$res = $vdr->(42);
If this option is set to true, validator accepts reference to data
instead, as in:
$res = $vdr->(\$data);
This allows $data to be modified by the validator (mainly, to set
default value specified in schema). For example:
my $data;
my $vdr = gen_validator([int => {min=>0, max=>10, default=>5}],
{accept_ref=>1});
my $res = $vdr->(\$data);
say $res; # => 1 (success)
say $data; # => 5
* source => BOOL (default: 0)
If set to 1, return source code string instead of compiled
subroutine. Usually only needed for debugging (but see also
$Log_Validator_Code and LOG_SAH_VALIDATOR_CODE if you want to log
validator source code).
ATTRIBUTES
compilers => HASH
A mapping of compiler name and compiler (Data::Sah::Compiler::*)
objects.
VARIABLES
$Log_Validator_Code (bool, default: 0)
ENVIRONMENT
LOG_SAH_VALIDATOR_CODE
METHODS
new() => OBJ
Create a new Data::Sah instance.
$sah->get_compiler($name) => OBJ
Get compiler object. Data::Sah::Compiler::$name will be loaded first
and instantiated if not already so. After that, the compiler object is
cached.
Example:
my $plc = $sah->get_compiler("perl"); # loads Data::Sah::Compiler::perl
$sah->normalize_schema($schema) => HASH
Normalize a schema, e.g. change int* into [int => {req=>1}], as well as
do some sanity checks on it. Returns the normalized schema if succeeds,
or dies on error.
Can also be used as a function.
Note: this functionality is implemented in Data::Sah::Normalize
(distributed separately in Data-Sah-Normalize). Use that module instead
if you just need normalizing schemas, to reduce dependencies.
$sah->normalize_clset($clset[, \%opts]) => HASH
Normalize a clause set, e.g. change {"!match"=>"abc"} into
{"match"=>"abc", "match.op"=>"not"}. Produce a shallow copy of the
input clause set hash.
Can also be used as a function.
$sah->normalize_var($var) => STR
Normalize a variable name in expression into its fully
qualified/absolute form.
Not yet implemented (pending specification).
For example:
[int => {min => 10, 'max=' => '2*$min'}]
$min in the above expression will be normalized as schema:clauses.min.
$sah->gen_validator($schema, \%opts) => CODE
Use the Perl compiler to generate validator code. Can also be used as a
function. See the documentation as a function for list of known
options.
MODULE ORGANIZATION
Data::Sah::Type::* roles specify Sah types, e.g. Data::Sah::Type::bool
specifies the bool type. It can also be used to name distributions that
introduce new types, e.g. Data-Sah-Type-complex which introduces
complex number type.
Data::Sah::FuncSet::* roles specify bundles of functions, e.g.
<Data::Sah::FuncSet::Core> specifies the core/standard functions.
Data::Sah::Compiler::$LANG:: namespace is for compilers. Each compiler
might further contain <::TH::*> and <::FSH::*> subnamespaces to
implement appropriate functionalities, e.g.
Data::Sah::Compiler::perl::TH::bool is the bool type handler for the
Perl compiler and Data::Sah::Compiler::perl::FSH::Core is the Core
funcset handler for Perl compiler.
Data::Sah::TypeX::$TYPENAME::$CLAUSENAME namespace can be used to name
distributions that extend an existing Sah type by introducing a new
clause for it. See Data::Sah::Manual::Extending for an example.
Data::Sah::Lang::$LANGCODE namespaces are for modules that contain
translations. They are further organized according to the organization
of other Data::Sah modules, e.g. Data::Sah::Lang::en_US::Type::int or
Data::Sah::Lang::en_US::TypeX::str::is_palindrome.
Sah::Schema:: namespace is reserved for modules that contain bundles of
schemas. For example, Sah::Schema::CPANMeta contains the schema to
validate CPAN META.yml. Sah::Schema::Int contains various schemas for
integers such as pos_int, int8, uint32. Sah::Schema::Sah contains the
schema for Sah schema itself.
FAQ
See also Sah::FAQ.
Relation to Data::Schema?
Data::Schema is the old incarnation of this module, deprecated since
2011.
There are enough incompatibilities between the two (some different
syntaxes, renamed clauses). Also, some terminology have been changed,
e.g. "attribute" become "clauses", "suffix" becomes "attributes". This
warrants a new name.
Compared to Data::Schema, Sah always compiles schemas and there is much
greater flexibility in code generation (can customize data term, code
can return boolean or error message string or detailed hash, can
generate code to validate multiple schemas, etc). There is no longer
hash form, schema is either a string or an array. Some clauses have
been renamed (mostly, commonly used clauses are abbreviated, Huffman
encoding thingy), some removed (usually because they are replaced by a
more general solution), and new ones have been added.
If you use Data::Schema, I recommend you migrate to Data::Sah as I will
not be developing Data::Schema anymore. Sorry, there's currently no
tool to convert your Data::Schema schemas to Sah, but it should be
relatively straightforward.
Comparison to {JSON::Schema, Data::Rx, Data::FormValidator, ...}?
See Sah::FAQ.
Why is it so slow?
You probably do not reuse the compiled schema, e.g. you continually
destroy and recreate Data::Sah object, or repeatedly recompile the same
schema. To gain the benefit of compilation, you need to keep the
compiled result and use the generated Perl code repeatedly.
Can I generate another schema dynamically from within the schema?
For example:
// if first element is an integer, require the array to contain only integers,
// otherwise require the array to contain only strings.
["array", {"min_len": 1, "of=": "[is_int($_[0]) ? 'int':'str']"}]
Currently no, Data::Sah does not support expression on clauses that
contain other schemas. In other words, dynamically generated schemas
are not supported. To support this, if the generated code needs to run
independent of Data::Sah, it needs to contain the compiler code itself
(or an interpreter) to compile or evaluate the generated schema.
However, an eval_schema() Sah function which uses Data::Sah can be
trivially declared and target the Perl compiler.
How to display the validator code being generated?
Use the source => 1 option in gen_validator().
If you use the OO interface, e.g.:
# generate perl code
my $cd = $plc->compile(schema=>..., ...);
then the generated code is in $cd->{result} and you can just print it.
If you generate validator using gen_validator(), you can set
environment LOG_SAH_VALIDATOR_CODE or package variable
$Log_Validator_Code to true and the generated code will be logged at
trace level using Log::Any. The log can be displayed using, e.g.,
Log::Any::App:
% LOG_SAH_VALIDATOR_CODE=1 TRACE=1 \
perl -MLog::Any::App -MData::Sah=gen_validator \
-e '$sub = gen_validator([int => min=>1, max=>10])'
Sample output:
normalized schema=['int',{max => 10,min => 1},{}]
schema already normalized, skipped normalization
validator code:
1|do {
2| require Scalar::Util::Numeric;
3| sub {
4| my ($data) = @_;
5| my $_sahv_res =
|
7| # skip if undef
8| (!defined($data) ? 1 :
|
10| (# check type 'int'
11| (Scalar::Util::Numeric::isint($data))
|
13| &&
|
15| (# clause: min
16| ($data >= 1))
|
18| &&
|
20| (# clause: max
21| ($data <= 10))));
|
23| return($_sahv_res);
24| }}
Lastly, you can also use validate-with-sah CLI utility from the
App::SahUtils distribution (use the --show-code option).
How to show the validation error message? The validator only returns
true/false!
Pass the return_type=>"str" to get an error message string on error, or
return_type=>"full" to get a hash of detailed error messages. Note also
that the error messages are translateable (e.g. use LANG or lang=>...
option. For example:
my $v = gen_validator([int => between => [1,10]], {return_type=>"str"});
say "$_: ", $v->($_) for 1, "x", 12;
will output:
1:
"x": Input is not of type integer
12: Must be between 1 and 10
What does the @... prefix that is sometimes shown on the error message
mean?
It shows the path to data item that fails the validation, e.g.:
my $v = gen_validator([array => of => [int=>min=>5], {return_type=>"str"});
say $v->([10, 5, "x"]);
prints:
@2: Input is not of type integer
which means that the third element (subscript 2) of the array fails the
validation. Another example:
my $v = gen_validator([array => of => [hash=>keys=>{a=>"int"}]]);
say $v->([{}, {a=>1.1}]);
prints:
@1/a: Input is not of type integer
How to show the process of validation by the compiled code?
If you are generating Perl code from schema, you can pass debug=>1
option so the code contains logging (Log::Any-based) and other
debugging information, which you can display. For example:
% TRACE=1 perl -MLog::Any::App -MData::Sah=gen_validator -E'
$v = gen_validator([array => of => [hash => {req_keys=>["a"]}]],
{return_type=>"str", debug=>1});
say "Validation result: ", $v->([{a=>1}, "x"]);'
will output:
...
[spath=[]]skip if undef ...
[spath=[]]check type 'array' ...
[spath=['of']]clause: {"of":["hash",{"req_keys":["a"]}]} ...
[spath=['of']]skip if undef ...
[spath=['of']]check type 'hash' ...
[spath=['of','req_keys']]clause: {"req_keys":["a"]} ...
[spath=['of']]skip if undef ...
[spath=['of']]check type 'hash' ...
Validation result: [spath=of]@1: Input is not of type hash
What else can I do with the compiled code?
Data::Sah offers some options in code generation. Beside compiling the
validator code into a subroutine, there are also some other options.
Examples:
* Dist::Zilla::Plugin::Rinci::Validate
This plugin inserts the generated code (without the sub { ... }
wrapper) to validate the content of %args right before # VALIDATE_ARG
or # VALIDATE_ARGS like below:
$SPEC{foo} = {
args => {
arg1 => { schema => ..., req=>1 },
arg2 => { schema => ... },
},
...
};
sub foo {
my %args = @_; # VALIDATE_ARGS
}
The schemas will be retrieved from the Rinci metadata ($SPEC{foo}
above). This means, subroutines in your built distribution will do
argument validation.
* Perinci::Sub::Wrapper
This module is part of the Perinci family. What the module does is
basically wrap your subroutine with a wrapper code that can include
validation code (among others). This is a convenient way to add
argument validation to an existing subroutine/code.
SEE ALSO
Other compiled validators
Other interpreted validators
Params::Validate is very fast, although minimal. Data::Rx, Kwalify,
Data::Verifier, Data::Validator, JSON::Schema, Validation::Class.
For Moo/Mouse/Moose stuffs: Moose type system,
MooseX::Params::Validate, Type::Tiny, among others.
Form-oriented: Data::FormValidator, FormValidator::Lite, among others.