NAME
Sah::Type - Standard types
VERSION
version 0.9.1
DESCRIPTION
This document specifies Sah standard types.
ROLE: BaseType
This is the base type role, all Sah types must implement this role.
Clauses
The list below is ordered by priority, from highest to lowest.
v => INT (default: 1)
Specify schema version. Should be 1
at the moment. See also: DefHash.
Priority: 0 (checked first before everything else).
default => ANY
Supply a default value.
Priority: 1 (very high). This is processed before all other clauses.
Example: Given schema [int => {req=>1}] an undef data is invalid, but given schema [int => {req=>1, default=>3}] an undef data is valid because it will be given default value first.
default_lang => LOCALE_CODE (defaut: en_US)
Set default language for this schema. Language-dependant attribute values (e.g. summary
, description
) will be assumed to be in the default language. See also: DefHash.
Priority: 2 (very high), after default
.
name => STR
A short (usually single-word, without any formatting) to name the schema, useful for identifying the schema when used as a type for human compiler. See also: DefHash.
To store translations, you can use the alt.lang.*
clause attributes.
Example:
[int => {
'name:alt.lang.en_US' => 'pos_int',
'name:alt.lang.id_ID' => 'bil_pos',
min=>0,
}]
Priority: 2 (very high), after default
.
See also: summary
, description
, tags
.
summary => STR
A one-line text (about 72 characters maximum, without any formatting) to describe the schema. This is useful, e.g. for manually describe a schema instead of using the human compiler. It can also be used in form field labels. See also: DefHash.
To store translations, you can use the alt.lang.*
clause attributes.
Example:
# definition for 'single_dice_throw' schema/type
[int => {
req => 1,
'summary:alt.lang.en_US' =>
'A number representing result of single dice throw (1-6)',
'summary:alt.lang.id_ID' =>
'Bilangan yang menyatakan hasil lempar sebuah dadu (1-6)',
between => [1, 6],
}]
Using the human compiler, the above schema will be output as the standard, more boring 'Integer, value between 1 and 6.'
Priority: 2 (very high), after default
.
See also: name
, description
, tags
.
description => STR
A longer text (a paragraph or more) to describe the schema, useful e.g. for help/usage text. Text should be in Markdown format. See also: DefHash.
To store translations, you can use the alt.lang.*
clause attributes.
Example:
[array => {
name => 'http_headers',
description => <<EOT,
HTTP headers should be specified as an array of 2-element arrays (pairs). Each
pair should contain header name in the first element (all lowercase, *-*
written as *_*) and header value in the second element.
Example:
: [[content_type => 'text/html'], [accept => 'text/html'], [accept => '*/*']]
EOT
req => 1,
of => 'http_header',
},
{
def => {
http_header => ['array*', len=>2],
},
}]
Priority: 2 (very high), after default
.
See also: name
, summary
, tags
.
tags => ARRAY OF STR
A list of tags, can be used to categorize schemas. See also: DefHash.
Priority: 2 (very high), after default
.
See also: name
, summary
, description
.
req => BOOL
If set to 1, require that data be defined. Otherwise, allow data to be undef (the default behaviour).
Priority: 3 (very high), executed after default
.
By default, undef will pass even elaborate schema, e.g. [int => {min=>0, max=>10, div_by=>3}] will still pass an undef. However, undef will not pass [int=>{req=>1}].
This behaviour is much like NULLs in SQL: we *can't* (in)validate something that is unknown/unset.
See also: forbidden
forbidden => BOOL
This is the opposite of req
, requiring that data be not defined (i.e. undef).
Priority: 3 (very high), executed after default
.
Given schema [int=>{forbidden=>1}], a non-undef value will fail. Another example: the schema [int=>{req=>1, forbidden=>1}] will always fail due to conflicting clauses.
See also: req
prefilters => [EXPR, ...]
Run expression(s), usually to preprocess data before further checking. Data is referred to in expression by variable $_
. Prefiltered value should persist until the end of all other clauses (until the end of clause set), after which the old value can be restored.
Priority: 10 (high). Run after default
and req
/forbidden
.
Specific attributes: perm
. If set to true, then prefiltered value will persist.
noop => ANY
Will do nothing. This clause is just a convenience if you want to do nothing (or perhaps just use the attributes of this clause to do things).
Priority: 50 (normal)
fail => BOOL
If set to 1, validation of this clause always fails. This is just a convenience to force failure.
Priority: 50 (normal)
cset => HASH
Evaluate a clause set.
Priority: 50 (normal)
if => [CLAUSE1=>VAL, CLAUSE2=>VAL]
If CLAUSE1 succeeds, then CLAUSE2 must also succeed. Otherwise, nothing is done. The second form (2-argument) operates on a clause set (hash) or clause sets (array of hashes).
Example:
# leap year
[int => {div_by=>4, if => [div_by => 100, div_by => 400]}]
The if
clause states that if input number is divisible by 100, it must also divisible by 400. Otherwise, the clause fails.
if_cset => [CSET1, CSET2]
If clause set CSET1 succeeds, then CSET2 must also succeed. Otherwise, nothing is done.
Examples:
[str => {min_len=>1, max_len=>10,
if_cset => [ {min_len=>4, max_len=>6}, {is_palindrome=>1} ]}]
The above says that if a string has length between 4 and 6 then it must be a palindrome. Otherwise it doesn't have to be one. But nevertheless, all input must be between 1 and 10 characters long.
[str => {if_cset => [ {'cset&' => [{match=>'a'}, {match=>'b'}]},
{'cset&' => [{match=>'c'}, {match=>'d'}]}, ]}]
The above says that if a string matches 'a' and 'b', it must also match 'c' and 'd'. As a side note, the above schema can also be written as:
[str => {if => [ 'match&'=>['a', 'b'], 'match&'=>['c', 'd'] ]}]
check => EXPR
Evaluate expression, which must evaluate to a true value for this clause to succeed.
Priority: 50 (normal)
postfilters => [EXPR, ...]
Run expression(s), usually to postprocess data. Data is referred to in expression by variable $_
. From here on, the data will be permanently set to the postfiltered value.
Priority: 90 (very low). Run after all other clauses.
ROLE: Comparable
This is the comparable type role. All types which have comparable values must implement this role. Most types implement this role, including str
, all number types, etc.
Clauses
in => [ANY, ...]
Require that the data be one of the specified choices.
See also: match (for type 'str'), has (for 'HasElems' types)
Examples:
[int => {in => [1, 2, 3, 4, 5, 6]}] # single dice throw value
[str => {'!in' => ['root', 'admin', 'administrator']}] # forbidden usernames
is => ANY
Require that the data is the same as VALUE. Will perform a numeric comparison for numeric types, or stringwise for string types, or deep comparison for deep structures.
Examples:
[int => {is => 3}]
[int => {'is&' => [1, 2, 3, 4, 5, 6]}] # effectively the same as 'in'
ROLE: HasElems
This is the role for types that have the notion of elements/length. It provides clauses like max_len
, len
, len_between
, all_elems
, elems
, etc. It is used by 'array', 'hash', and also 'str'.
Clauses
max_len => NUM
Requires that the data have at most NUM elements.
Example:
[str, {req=>1, max_len=>10}] # define a string with at most 10 characters
min_len => NUM
Requires that the data have at least NUM elements.
Example:
[array, {min_len=>1}] # define an array with at least one element
len_between => [NUM_MIN, NUM_MAX]
A convenience clause that combines min_len and max_len.
Example, the two schemas below are equivalent:
[str, {len_between=>[1, 10]}]
[str, {min_len=>1, max_len=>10}]
len => NUM
Requires that the data have exactly NUM elements.
has => ANY
Requires that the data contains the element.
Examples:
# requires that array has element x
[array => {has => 'x'}]
# requires that array has elements 'x', 'y', and 'z'
[array => {'has&' => ['x', 'y', 'z']}]
# requires that array does not have element 'x'
[array => {'!has' => 'x'}]
all_elems => SCHEMA
Requires that every element of the data validate to the specified schema.
Note: filters applied by SCHEMA to elements will be preserved.
Examples:
[array => {all_elems => 'int'}]
The above specifies an array of ints.
[hash => {all_elems => [str => { match => '^[A-Za-z0-9]+$' }]}]
The above specifies hash with alphanumeric-only values.
if_elem => [INDEX1 => SCHEMA1, INDEX2 => SCHEMA2]
State that if element with the index of INDEX1 passes SCHEMA1, then element with the index of INDEX2 must also passes SCHEMA2. Otherwise, nothing is done.
Examples:
[hash => {if_elem => [
password => 'str*', password_confirmation => 'str*',
]}]
The above says: key 'password_confirmation' is required if 'password' is set.
[hash => {'elem_deps&' => [
[ province => ['str*', {is => 'Outside US'}],
zipcode => [str => {forbidden=>1}] ],
[ province => ['str*', {not => 'Outside US'}],
zipcode => [str => {required=>1}] ]
]}]
The above says: if province is set to 'Outside US', then zipcode must not be specified. Otherwise if province is set to US states, zipcode is required.
if_elem_re => [REGEX1 => SCHEMA1, REGEX2 => SCHEMA2]
State that if all elements with index matching REGEX1 pass SCHEMA1, then all elements with index matching REGEX2 must also pass SCHEMA2. Otherwise, nothing is done.
Example:
[array => {'if_elem_re&' => [
[ '^0$', ['str*' => {is => 'int'}],
'[1-9]', ['hash*' => {keys_in => [qw/is min max/]}] ],
[ '^0$', ['str*' => {is => 'str'}],
'[1-9]', ['hash*' => {keys_in => [qw/is min max min_len max_len/]}] ],
[ '^0$', ['str*' => {is => 'bool'}],
'[1-9]', ['hash*' => {keys_in => [qw/is/]}] ],
]}]
The above says: if first element of array is int
, then the following elements must be hash with specified keys. A similar rule is there for first element being 'str' and 'bool'.
Example valid array:
['str', {min_len=>0, max_len=>1}, {is=>'a'}]
Example invalid array (key min_len
is not allowed):
['int', {min_len=>0, max_len=>1}, {is=>'a'}]
Note: You need to be careful with undef, because it matches all schema unless req=>1 (or the shortcut 'foo*') is specified.
ROLE: Sortable
This is the type role for sortable types. It provides clauses like min
, max
, and between
. It is used by many types, for example str
, all numeric types, etc.
Clauses
min => ANY
Require that the value is not less than some specified minimum (equivalent in intention to the Perl string ge
operator, or the numeric >=
operator).
Example:
[int => {min => 0}] # specify positive numbers
xmin => ANY
Require that the value is not less nor equal than some specified minimum (equivalent in intention to the Perl string gt
operator, or the numeric >
operator). The x
prefix is for "exclusive".
max => ANY
Require that the value is less or equal than some specified maximum (equivalent in intention to the Perl string le
operator, or the numeric <=
operator).
xmax => ANY
Require that the value is less than some specified maximum (equivalent in intention to the Perl string lt
operator, or the numeric <
operator). The x
prefix is for "exclusive".
between => [ANY_MIN, ANY_MAX]
A convenient clause to combine min
and max
.
Example, the following schemas are equivalent:
[float => {between => [0.0, 1.5]}]
[float => {min => 0.0, max => 1.5}]
xbetween => [ANY_MIN, ANY_MAX]
A convenient clause to combine xmin
and xmax
.
TYPE: buf
buf
stores binary data. Elements of buf data are bytes. It is derived from str
.
TYPE: num
num
stores numbers. This type assumes the Comparable and Sortable roles.
TYPE: float
int
stores real (floating-point) numbers. This type is derived from num
.
TYPE: int
int
stores integers. This type is derived from num
.
Clauses
mod => [INT1, INT2]
Require that (data mod INT1) equals INT2. For example, mod => [2, 1]
effectively specifies odd numbers.
div_by => INT
Require that data is divisible by a number. This is effectively just a shortcut for mod => [INT, 0]
.
Example: Given schema [int=>{div_by=>2}]
, undef, 0, 2, 4, and 6 are valid but 1, 3, 5 are not.
TYPE: str
str
stores strings (text). This type assumes the Comparable, Sortable, and HasElems roles (the elements are individual characters). Default encoding is utf8.
Clauses
match => REGEX|{COMPILER=>REGEX, ...}
Require that string match the specified regular expression.
Since regular expressions might not be 100% compatible from language to language, instead of avoiding the use of regex entirely, you can specify different regex for each target language, e.g.:
[str => {match => {
js => '...',
perl => '...',
python => '...',
}}]
To match against multiple regexes:
# string must match a, b, and c
[str => {"match&"=>['a', 'b', 'c']}]
# string must match either a or b or c
[str => {"match|"=>['a', 'b', 'c']}
# idem, shortcut form
[str => {"!match"=>'a'}]
# string must NOT match a nor b nor c (i.e. must match none of those)
[str => {"match.vals"=>[a, b, c], "match.max_ok"=>0}]
# string must at least not match a or b or c (i.e. if all match, schema fail;
# if at least one does not match, schema succeeds)
[str => {"match.vals"=>[a, b, c], "match.max_ok"=>2}]
is_re => BOOL
If value is true, require that the string be a valid regular expression string. If value is false, require that the string not be a valid regular expression string.
TYPE: bool
Boolean type. This type assumes the Comparable and Sortable roles.
TYPE: array
Array type. This type assumes the Comparable, Sortable, and HasElems roles (the elements are indexed by integers starting from 0).
Clauses
TBD
TYPE: hash
Hash (a.k.a. dictionary) type. This type assumes the Comparable, Sortable, and HasElems roles (the elements are indexed by strings).
Clauses
TBD
SEE ALSO
AUTHOR
Steven Haryanto <stevenharyanto@gmail.com>
COPYRIGHT AND LICENSE
This software is copyright (c) 2012 by Steven Haryanto.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.