package Sah::Type; # just to make PodWeaver happy

our $VERSION = '0.9.4'; # VERSION

1;
# ABSTRACT: Standard types


__END__
=pod

=head1 NAME

Sah::Type - Standard types

=head1 VERSION

version 0.9.4

=head1 DESCRIPTION

This document specifies Sah standard types.

=head1 TYPE: undef

This type does not have any clauses. The only value it knows is the undefined
value (like C<undef> in Perl, or C<null> in PHP).

=head1 ROLE: BaseType

This is the base type role, all Sah types (except C<undef>) must implement this
role.

=head2 Clauses

The list below is ordered by priority, from highest to lowest.

=head3 defhash_v : FLOAT

Priority: 0 (checked first before everything else).

Category: metadata.

From DefHash. Normally there is no need to set this.

=head3 v : FLOAT (default: 1)

Priority: 0 (checked first before everything else).

Category: metadata.

From DefHash. Specify Sah version. Should be C<1> at the moment.

=head3 schema_v : FLOAT (default: 1)

Priority: 0 (checked first before everything else).

Category: metadata.

Specify schema version. By default assumed to be 1 if not set.

=head3 base_v : FLOAT (default: 1)

Priority: 0 (checked first before everything else).

Category: metadata.

Specify base schema version. By default assumed to be 1 if not set. Using a base
schema with a different value will fail. Can be used to force child schemas to
update whenever we change our schema. For example:

 // schema: vocal
 ["str", {"in": ["a", "e", "i", "o", "u"]}]

 // schema: consonant, defined in terms of "vocal", by
 ["vocal", {"match": "\\A[a-z]\\z", "in.max_ok": 0}]

However, if C<vocal> changes its implementation or structure to:

 // the new vocal
 ["str", {"match": "\\A[aeiou]\\z"}]

then B<vocal> will break. To force B<consonant> to fail (so its author can
update it):

 // the new vocal
 ["str", {"schema_v": 2, "match": "\\A[aeiou]\\z"}]

Since C<vocal>'s C<schema_v> is now 2, it is not the same as 1 (which is implied
by C<consonant>, having the default value of C<base_v>). C<consonant>'s author
might then update its own implementation to match C<vocal>:

 // the adjusted consonant
 ["str", {"base_v":2, "cset":{"match":"\\A[a-z]\\z"}, "match.max_ok":0}]

Notice the matching of C<consonant>'s C<base_v> against C<vocal>'s C<schema_v>.
C<consonant> might also add its own C<< "schema_v":2 >> so other schemas
depending on it are forced to adjust, if needed.

=head3 default : ANY

Priority: 1 (very high). This is processed before all other clauses.

Category: default.

Supply a default value.

Example: Given schema C<< ["int", {"req": 1}] >> an undef data is invalid, but
given schema C<< ["int", {"req": 1, "default": 3}] >> an undef data is valid
because it will be given default value first.

=head3 default_lang : LOCALE_CODE (defaut: en_US)

Priority: 2 (very high), after C<default>.

Category: metadata.

From DefHash. Set default language for this schema. Language-dependant attribute
values (e.g. C<summary>, C<description>) will be assumed to be in the default
language.

=head3 name : STR

Priority: 2 (very high), after C<default>.

Category: metadata.

From DefHash. 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.

To store translations, you can use the C<alt.lang.*> clause attributes.

Example:

 ["int", {
     "name.alt.lang.en_US": "pos_int",
     "name.alt.lang.id_ID": "bil_pos",
     "min": 0
 }]

See also: C<summary>, C<description>, C<tags>.

=head3 summary : STR

Priority: 2 (very high), after C<default>.

Category: metadata.

From DefHash. 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.

To store translations, you can use the C<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]
 }]

Without the summary, using a compiler to human text the above schema might be
output as the standard, more boring "Integer, value between 1 and 6".

See also: C<name>, C<description>, C<tags>.

=head3 description : STR

Priority: 2 (very high), after C<default>.

Category: metadata.

From DefHash. A longer text (a paragraph or more) to describe the schema, useful
e.g. for help/usage text. Text should be in Markdown format.

To store translations, you can use the C<alt.lang.*> clause attributes.

Example (using Perl syntax because it supports heredoc):

 [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],
      },
 }]

See also: C<name>, C<summary>, C<tags>.

=head3 tags : ARRAY OF STR

Priority: 2 (very high), after C<default>.

Category: metadata.

From DefHash. A list of tags, can be used to categorize schemas.

See also: C<name>, C<summary>, C<description>.

=head3 req : BOOL

Priority: 3 (very high), executed after C<default>.

Category: constraint.

If set to 1, require that data be defined. Otherwise, allow data to be undef
(the default behaviour).

By default, undef will pass even elaborate schema, e.g. C<< ["int", {"min": 0,
"max": 10, "div_by": 3}] >> will still pass an undef. However, undef will not
pass C<< ["int": {"req": 1}] >>.

This behaviour is much like NULLs in SQL: we *can't* (in)validate something that
is unknown/unset.

See also: C<forbidden>

=head3 forbidden : BOOL

Priority: 3 (very high), executed after C<default>.

Category: constraint.

This is the opposite of C<req>, requiring that data be not defined (i.e. undef).

Given schema C<< ["int", {"forbidden": 1}] >>, a non-undef value will fail.
Another example: the schema C<< ["int", {"req": 1, "forbidden": 1}] >> will
always fail due to conflicting clauses.

See also: C<req>

=head3 prefilters : [EXPR, ...]

Priority: 10 (high). Run after C<default> and C<req>/C<forbidden>.

Category: filter.

Run expression(s), usually to preprocess data before further checking. Data is
referred to in expression by variable C<$_>. 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.

Specific attributes: C<perm>. If set to true, then prefiltered value will
persist.

=head3 ok : ANY -> true

Priority: 50 (normal)

Return value: true (always succeeds).

Category: constraint.

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). It is the default
in the C<else> section of the C<if_clause> clause.

To force failure, you can use C<< "!ok": 1 >>.

=head3 cset : HASH -> INT

Priority: 50 (normal)

Return value: (number of successful clauses + 1) on success, false on failure.

Category: constraint.

Evaluate a clause set. Note that return value adds 1 to the number of successful
clauses to avoid returning 0 (evaluates to false). And it will only be returned
if clause is successful. Otherwise false (0) will be returned.

=head3 check : EXPR -> ANY

Priority: 50 (normal)

Return value: result of evaluated expression

Category: constraint.

Evaluate expression, which must evaluate to a true value for this clause to
succeed. Example:

 // require that string is a palindrome, using a Sah function
 ["str", "check", "is_palindrome($_)"]

 // require that the *length of* string is a prime number
 ["str", "check", "is_prime(len($_))"]

 // same thing, using input.prop attribute
 ["str", "check.input.prop", "len", "check", "is_prime($_)"]

 // check that the email's Subject header is a palindrome
 ["email", "check.input.prop", ["headers", "subject"],
           "check", "is_palindrome($_)"]

 // check that

Evaluate expression against property. Property will be available in expression
as C<$_>. Example:

 // require length of string to be divisible by 3
 ["str", "check_prop": ["len", "$_ % 3 == 0"]

=head3 if : HASH -> ANY

Priority: 50 (normal)

Return value: if condition is true, then the C<then_*> result, otherwise the
C<else_*> result.

Category: constraint.

A generic condition clause.

To use this clause, first specify one of the condition keys in the argument:
C<expr> (evaluate expression, value should the expression), C<clause> (evaluate
data against clause, value should be an array C<< [CLAUSE_NAME, CLAUSE_ARG] >>),
C<schema> (validate data against schema, value should be the schema), C<prop>
(evaluate property against a schema, value is C<< [PROP, SCHEMA] >> where PROP
is the property name, or if property has arguments, C<< [PROP_NAME, PROP_ARGS]
>>).

Then specify one of the then keys and one of the else keys:
C<{then,else}{_prop,_expr,schema,clause}>. The default then key is C<< "then": 1
>>. The default else is C<< "else": 1 >>, unless C<< "then": some-true-value >>
is specified, in which case the default else is C<< "else": 0 >>. Examples:

 // forbid the string to be lowercase
 "if": {"clause": ["match", "^[a-z]$"], "then": 0}

 // if string is lowercase, it must be a palindrome
 "if": {"clause": ["match", "^[a-z]$"], "then_expr": "is_palindrome($_)"}

 // if string is lowercase, it must be a palindrome, otherwise it must be longer
 // than 3 characters.
 "if": {"clause": ["match", "^[a-z]$"],
        "then_expr": "is_palindrome($_)",
        "else_expr": "len($_) > 3"}

 // require the length of the string to be an even number
 "if": {"prop": ["len", ["int", "div_by", 2]], "then": 1}

 // if string is a palindrome, then require it to have length > 5
 "if": {"expr": "is_palindrome($_)", "then_prop": ["len", ["int", "xmin": 5]]}
 "if": {"expr": "is_palindrome($_)", "then_expr": "len($_) > 5"}

=head3 each : SCHEMA

Priority: 50 (normal)

Category: constraint, looping

Requires that every element of data validate to the specified schema. The first
element that fails the schema will terminate the loop.

Examples:

 ["array", {"each": "int"}]
 ["array", {"of": "int"}] // same thing, "of" is the same as "each"

The above specifies an array of integers.

 ["hash", {"each": ["str", {"match": "^[A-Za-z0-9]+$" }]}]

The above specifies hash with alphanumeric-only values.

Using the C<.input> attribute, you can change the input.

=head3 check_each : EXPR

Priority: 50 (normal)

Category: constraint, looping

Just like C<each> but instead of using schema, each element is tested using
expression.

Using the C<.input> attribute, you can change the input.

=head3 exists : SCHEMA

Priority: 50 (normal)

Category: constraint, looping

Test that there is at least one element of data that validates to the schema.
That element is returned. Be careful to not return element which has the value
which evaluates to false.

Using the C<.input> attribute, you can change the input.

=head3 check_exists : EXPR

Priority: 50 (normal)

Category: constraint, looping

Just like C<exists> but instead of using schema, each element is tested using
expression.

Using the C<.input> attribute, you can change the input.

=head3 postfilters : [EXPR, ...]

Priority: 90 (very low). Run after all other clauses.

Category: filter.

Run expression(s), usually to postprocess data. Data is referred to in
expression by variable C<$_>. From here on, the data will be permanently set to
the postfiltered value.

=head1 ROLE: Comparable

This is the comparable type role. All types which have comparable values must
implement this role. Most types implement this role, including C<str>, all
number types, etc.

=head2 Clauses

=head3 in : [ANY, ...]

Priority: 50 (normal)

Category: constraint

Require that the data be one of the specified choices.

See also: C<match> (for type 'str'), C<has> (for 'HasElems' types)

Examples:

 ["int", {"in": [1, 2, 3, 4, 5, 6]}] // single dice throw value
 ["str", {"!in": ["root", "admin", "administrator"]}] // forbidden usernames

=head3 is : ANY

Priority: 50 (normal)

Category: constraint

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'

=head1 ROLE: HasElems

This is the role for types that have the notion of elements/length. It provides
clauses like C<max_len>, C<len>, C<len_between>, C<each>, etc. It is used by
C<array>, C<hash>, and also C<str>.

=head2 Properties

=head3 len -> STR

=head2 Clauses

=head3 max_len : NUM

Priority: 50 (normal)

Category: constraint

Requires that the data have at most NUM elements.

Example:

 ["str", {"req": 1, "max_len": 10}] // string with at most 10 characters

=head3 min_len : NUM

Priority: 50 (normal)

Category: constraint

Requires that the data have at least NUM elements.

Example:

 ["array", {"min_len": 1}] // define an array with at least one element

=head3 len_between : [NUM_MIN, NUM_MAX]

Priority: 50 (normal)

Category: constraint

A convenience clause that combines C<min_len> and C<max_len>.

Example, the two schemas below are equivalent:

 ["str", {"len_between": [1, 10]}]
 ["str", {"min_len": 1, "max_len": 10}]

=head3 len : NUM

Priority: 50 (normal)

Category: constraint

Requires that the data have exactly NUM elements.

=head3 has : ANY

Priority: 50 (normal)

Category: constraint

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"}]

=head3 uniq => BOOL

If set to 1, require that the array values be unique (like in a set). If set to
0, require that there are duplicates in the array.

=head3 each_index : SCHEMA

Priority: 50 (normal)

Category: constraint, looping

Like C<each> but iterate over the indices. For type like C<array>, this is 0, 1,
... N. For C<hash>, this is the keys of hash.

Using the C<.input> attribute, you can change the input.

=head3 check_each_index : EXPR

Priority: 50 (normal)

Category: constraint, looping

Like C<each_index> but instead of using schema, each index is tested using
expression.

Using the C<.input> attribute, you can change the input.

=head1 ROLE: Sortable

This is the type role for sortable types. It provides clauses like C<min>,
C<max>, and C<between>. It is used by many types, for example C<str>, all
numeric types, etc.

=head2 Clauses

=head3 min => ANY

Require that the value is not less than some specified minimum (equivalent in
intention to the Perl string C<ge> operator, or the numeric C<< >= >> operator).

Example:

 [int => {min => 0}] // specify positive numbers

=head3 xmin => ANY

Require that the value is not less nor equal than some specified minimum
(equivalent in intention to the Perl string C<gt> operator, or the numeric C<< >
>> operator). The C<x> prefix is for "exclusive".

=head3 max => ANY

Require that the value is less or equal than some specified maximum (equivalent
in intention to the Perl string C<le> operator, or the numeric C<< <= >>
operator).

=head3 xmax => ANY

Require that the value is less than some specified maximum (equivalent in
intention to the Perl string C<lt> operator, or the numeric C<< < >> operator).
The C<x> prefix is for "exclusive".

=head3 between => [ANY_MIN, ANY_MAX]

A convenient clause to combine C<min> and C<max>.

Example, the following schemas are equivalent:

 [float => {between => [0.0, 1.5]}]
 [float => {min => 0.0, max => 1.5}]

=head3 xbetween => [ANY_MIN, ANY_MAX]

A convenient clause to combine C<xmin> and C<xmax>.

=head1 TYPE: buf

C<buf> stores binary data. Elements of buf data are bytes. It is derived from
C<str>.

=head1 TYPE: num

C<num> stores numbers. This type assumes the Comparable and Sortable roles.

=head1 TYPE: float

C<int> stores real (floating-point) numbers. This type is derived from C<num>.

=head2 Clauses

=head3 is_nan => BOOL

Require that number is a "NaN" (or "-NaN").

=head2 is_inf => BOOL

Require that number is an "Inf" or "Infinity" (or "-Inf" or "-Infinity").

=head1 TYPE: int

C<int> stores integers. This type is derived from C<num>.

=head2 Clauses

=head3 mod => [INT1, INT2]

Require that (data mod INT1) equals INT2. For example, C<< mod => [2, 1] >>
effectively specifies odd numbers.

=head3 div_by => INT

Require that data is divisible by a number. This is effectively just a shortcut
for C<< mod => [INT, 0] >>.

Example: Given schema C<< [int=>{div_by=>2}] >>, undef, 0, 2, 4, and 6 are valid
but 1, 3, 5 are not.

=head1 TYPE: str

C<str> stores strings (text). This type assumes the Comparable, Sortable, and
HasElems roles (the elements are individual characters). Default encoding is
utf8.

=head2 Clauses

=head3 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"=>[a, b, c], "match.is_multi"=>1, "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"=>[a, b, c], "match.max_ok"=>2}]

=head3 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.

=head1 TYPE: cistr

=head1 TYPE: bool

Boolean type. This type assumes the Comparable and Sortable roles.

=head1 TYPE: array

Array type. This type assumes the Comparable, Sortable, and HasElems roles (the
elements are indexed by integers starting from 0).

=head2 Clauses

=head3 elems => ARRAY_OF_SCHEMA

Specify that corresponding array element must validate to the schema. Example:

 // Rinci function result envelope
 [array => {
     elems => [
         [int => {between => [100, 999]}],
         'str',
         'any',
         'defhash',
     ]
 }]

The above schema validates result envelope, which is an array (see
L<Rinci::function>). The first element (the status) must be a 3-digit integer.
The second element (the error message) is a string. The third element (the
actual result) can be anything. The fourth element (result metadata) is a hash
containing extra data. Examples of valid data including:

 [404, "Not found"]
 [200, "OK", [1, 2, 3, 4], {'cmdline.pager'=>'less'}]

=head3 of => SCHEMA

This is just an alias to C<each>.

=head1 TYPE: hash

Hash (a.k.a. dictionary) type. This type assumes the Comparable, Sortable, and
HasElems roles (the elements are indexed by strings).

=head2 Clauses

=head3 keys => HASH

Specify schema for specific pair value. Also, restrict keys of hash to the list
specified in this clause, except if C<allow_extra_keys> clause is set to true.
Example:

 ['hash*' => {
     keys => {
         name => 'str',
         address => [any => {of=>['str', [array => {of=>'str'}]]}],
         email => 'email_address',
     },
 }]

The above schema requires data to be a hash with keys C<name>, C<address>,
C<email>. None of the keys are required to be present (use C<req_keys> for
that), but other keys are not allowed.

=head3 allow_extra_keys => BOOL (default: 0)

If set to true, allow hash to have keys other than specified in the C<keys>
clause. See also: C<keys>.

=head3 each_key => SCHEMA

Alias to C<each_index>.

=head3 each_value => SCHEMA

Alias to C<each>.

=head1 TYPE: any

A type to specify alternate schemas.

=head2 Clauses

=head3 of => [SCHEMA, ...]

Specify the schema(s) where the value will need to be valid to at least one of
them.

=head1 TYPE: all

A type to specify co-schemas (all schemas that must be validated to value).

=head2 Clauses

=head3 of => [SCHEMA, ...]

Specify the schema(s) where the value will need to be valid to all of them.

=head1 TYPE: obj

Object.

=head2 Properties

=head3 methods

=head3 attributes

=head2 Clauses

=head3 can

=head3 isa

=head3

=head1 TYPE: date

=head1 SEE ALSO

L<Sah>

=head1 AUTHOR

Steven Haryanto <stevenharyanto@gmail.com>

=head1 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.

=cut

