package MooseX::Types::Structured;
-use MooseX::Types::Moose qw();
-use MooseX::Types -declare => [qw( Dict Tuple Optional )];
+use 5.008;
+use Moose;
+use Moose::Util::TypeConstraints;
+use MooseX::Meta::TypeConstraint::Structured;
+use MooseX::Types -declare => [qw(Dict Tuple)];
-our $VERSION = '0.01';
+our $VERSION = '0.04';
our $AUTHORITY = 'cpan:JJNAPIORK';
=head1 NAME
-MooseX::Types::Structured; Structured Type Constraints for Moose
+MooseX::Types::Structured - Structured Type Constraints for Moose
=head1 SYNOPSIS
-The following is example usage for this module
+The following is example usage for this module.
- package MyApp::Types
- TBD
+ package MyApp::MyClass;
+
+ use Moose;
+ use MooseX::Types::Moose qw(Str Int);
+ use MooseX::Types::Structured qw(Dict Tuple);
-=head1 DESCRIPTION
-
-What this application does, why I made it etc.
-
-=head1 TYPES
-
-This class defines the following types and subtypes.
-
-=cut
+ has name => (isa=>Dict[first_name=>Str, last_name=>Str]);
+Then you can instantiate this class with something like:
-=head1 SEE ALSO
-
-The following modules or resources may be of interest.
+ my $instance = MyApp::MyClass->new(
+ name=>{first_name=>'John', last_name=>'Napiorkowski'},
+ );
-L<Moose>, L<MooseX::TypeLibrary>, L<Moose::Meta::TypeConstraint>
+But all of these would cause an error:
-=head1 BUGS
-
-No known or reported bugs.
-
-=head1 AUTHOR
+ my $instance = MyApp::MyClass->new(name=>'John');
+ my $instance = MyApp::MyClass->new(name=>{first_name=>'John'});
+ my $instance = MyApp::MyClass->new(name=>{first_name=>'John', age=>39});
-John Napiorkowski, C<< <jjnapiork@cpan.org> >>
+Please see the test cases for more examples.
-=head1 COPYRIGHT & LICENSE
+=head1 DESCRIPTION
-This program is free software; you can redistribute it and/or modify
-it under the same terms as Perl itself.
+A structured type constraint is a standard container L</Moose> type constraint,
+such as an arrayref or hashref, which has been enhanced to allow you to
+explicitly name all the allow type constraints inside the structure. The
+generalized form is:
+
+ TypeConstraint[TypeParameters]
+
+Where TypeParameters is a list of type constraints.
+
+This type library enables structured type constraints. It is build on top of the
+L<MooseX::Types> library system, so you should review the documentation for that
+if you are not familiar with it.
+
+=head2 Comparing Parameterized types to Structured types
+
+Parameterized constraints are built into the core Moose types 'HashRef' and
+'ArrayRef'. Structured types have similar functionality, so their syntax is
+likewise similar. For example, you could define a parameterized constraint like:
+
+ subtype HashOfInts,
+ as Hashref[Int];
+
+which would constraint a value to something like [1,2,3,...] and so on. On the
+other hand, a structured type constraint explicitly names all it's allowed type
+parameter constraints. For the example:
+
+ subtype StringFollowedByInt,
+ as Tuple[Str,Int];
+
+would constrain it's value to something like ['hello', 111] but ['hello', 'world']
+would fail, as well as ['hello', 111, 'world']
+
+These structures can be as simple or elaborate as you wish. You can even
+combine various structured, parameterized and simple constraints all together:
+
+ subtype crazy,
+ as Tuple[
+ Int,
+ Dict[name=>Str, age=>Int],
+ ArrayRef[Int]
+ ];
+
+Which would match "[1, {name=>'John', age=>25},[10,11,12]]". Please notice how
+the type parameters can be visually arranged to your liking and to improve the
+clarity of your meaning. You don't need to run then altogether onto a single
+line.
+
+=head2 Alternatives
+
+You should exercise some care as to whether or not your complex structured
+constraints would be better off contained by a real object as in the following
+example:
+
+ package MyApp::MyStruct;
+ use Moose;
+
+ has $_ for qw(name age);
+
+ package MyApp::MyClass;
+ use Moose;
+
+ has person => (isa=>'MyApp::MyStruct');
+
+ my $instance = MyApp::MyClass->new(
+ person=>MyApp::MyStruct->new(name=>'John', age=>39),
+ );
+
+This method may take some additional time to setup but will give you more
+flexibility. However, structured constraints are highly compatible with this
+method, granting some interesting possibilities for coercion. Try:
+
+ subtype 'MyStruct',
+ as 'MyApp::MyStruct';
+
+ coerce 'MyStruct',
+ from (Dict[name=>Str, age=>Int]),
+ via { MyApp::MyStruct->new(%$_) },
+ from (Dict[last_name=>Str, first_name=>Str, dob=>DateTime]),
+ via {
+ my $name = $_->{first_name} .' '. $_->{last_name};
+ my $age = DateTime->now - $_->{dob};
+ MyApp::MyStruct->new( name=>$name, age=>$age->years );
+ };
+
+=head2 Subtyping a structured subtype
+
+You need to exercise some care when you try to subtype a structured type
+as in this example:
+
+ subtype Person,
+ as Dict[name=>Str, age=>iIt];
+
+ subtype FriendlyPerson,
+ as Person[name=>Str, age=>Int, totalFriends=>Int];
+
+This will actually work BUT you have to take care that the subtype has a
+structure that does not contradict the structure of it's parent. For now the
+above works, but I will clarify the syntax for this at a future point, so
+it's recommended to avoid (should not realy be needed so much anyway). For
+now this is supported in an EXPERIMENTAL way. Your thoughts, test cases and
+patches are welcomed for discussion.
+
+=head2 Coercions
+
+Coercions currently work for 'one level' deep. That is you can do:
+
+ subtype Person,
+ as Dict[name=>Str, age=>Int];
+
+ subtype Fullname,
+ as Dict[first=>Str, last=>Str];
+
+ coerce Person,
+ from BlessedPersonObject,
+ via { +{name=>$_->name, age=>$_->age} },
+ from ArrayRef,
+ via { +{name=>$_->[0], age=>$_->[1] },
+ from Dict[fullname=>Fullname, dob=>DateTime],
+ via {
+ my $age = $_->dob - DateTime->now;
+ +{
+ name=> $_->{fullname}->{first} .' '. $_->{fullname}->{last},
+ age=>$age->years
+ }
+ };
+
+And that should just work as expected. However, if there are any 'inner'
+coercions, such as a coercion on 'Fullname' or on 'DateTime', that coercion
+won't currently get activated.
+
+Please see the test '07-coerce.t' for a more detailed example.
+
+=head1 TYPE CONSTRAINTS
+
+This type library defines the following constraints.
+
+=head2 Tuple[@constraints]
+
+This defines an arrayref based constraint which allows you to validate a specific
+list of constraints. For example:
+
+ Tuple[Int,Str]; ## Validates [1,'hello']
+ Tuple[Str|Object, Int]; ##Validates ['hello', 1] or [$object, 2]
+
+=head2 Dict [%constraints]
+
+This defines a hashref based constraint which allowed you to validate a specific
+hashref. For example:
+
+ Dict[name=>Str, age=>Int]; ## Validates {name=>'John', age=>39}
+
+=head1 EXAMPLES
+
+Here are some additional example usage for structured types. All examples can
+be found also in the 't/examples.t' test. Your contributions are also welcomed.
+
+=head2 Normalize a HashRef
+
+You need a hashref to conform to a canonical structure but are required accept a
+bunch of different incoming structures. You can normalize using the Dict type
+constraint and coercions. This example also shows structured types mixed which
+other MooseX::Types libraries.
+
+ package Test::MooseX::Meta::TypeConstraint::Structured::Examples::Normalize;
+
+ use Moose;
+ use DateTime;
+
+ use MooseX::Types::Structured qw(Dict Tuple);
+ use MooseX::Types::DateTime qw(DateTime);
+ use MooseX::Types::Moose qw(Int Str Object);
+ use MooseX::Types -declare => [qw(Name Age Person)];
+
+ subtype Person,
+ as Dict[name=>Str, age=>Int];
+
+ coerce Person,
+ from Dict[first=>Str, last=>Str, years=>Int],
+ via { +{
+ name => "$_->{first} $_->{last}",
+ age=>$_->{years},
+ }},
+ from Dict[fullname=>Dict[last=>Str, first=>Str], dob=>DateTime],
+ via { +{
+ name => "$_->{fullname}{first} $_->{fullname}{last}",
+ age => ($_->{dob} - 'DateTime'->now)->years,
+ }};
+
+ has person => (is=>'rw', isa=>Person, coerce=>1);
=cut
-1;
-
-
-
-
-
-
-
-
-class_type "DateTime";
-class_type "DateTime::Duration";
-class_type "DateTime::TimeZone";
-class_type "DateTime::Locale::root" => { name => "DateTime::Locale" };
-
-subtype DateTime, as 'DateTime';
-subtype Duration, as 'DateTime::Duration';
-subtype TimeZone, as 'DateTime::TimeZone';
-subtype Locale, as 'DateTime::Locale';
-
-subtype( Now,
- as Str,
- where { $_ eq 'now' },
- Moose::Util::TypeConstraints::optimize_as {
- no warnings 'uninitialized';
- !ref($_[0]) and $_[0] eq 'now';
- },
+Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
+ MooseX::Meta::TypeConstraint::Structured->new(
+ name => "MooseX::Types::Structured::Tuple" ,
+ parent => find_type_constraint('ArrayRef'),
+ constraint_generator=> sub {
+ ## Get the constraints and values to check
+ my @type_constraints = @{shift @_};
+ my @values = @{shift @_};
+ ## Perform the checking
+ while(@type_constraints) {
+ my $type_constraint = shift @type_constraints;
+ if(@values) {
+ my $value = shift @values;
+ unless($type_constraint->check($value)) {
+ return;
+ }
+ } else {
+ return;
+ }
+ }
+ ## Make sure there are no leftovers.
+ if(@values) {
+ return;
+ } elsif(@type_constraints) {
+ return;
+ }else {
+ return 1;
+ }
+ }
+ )
);
-
-our %coercions = (
- DateTime => [
- from Num, via { 'DateTime'->from_epoch( epoch => $_ ) },
- from HashRef, via { 'DateTime'->new( %$_ ) },
- from Now, via { 'DateTime'->now },
- ],
- "DateTime::Duration" => [
- from Num, via { DateTime::Duration->new( seconds => $_ ) },
- from HashRef, via { DateTime::Duration->new( %$_ ) },
- ],
- "DateTime::TimeZone" => [
- from Str, via { DateTime::TimeZone->new( name => $_ ) },
- ],
- "DateTime::Locale" => [
- from Moose::Util::TypeConstraints::find_or_create_isa_type_constraint("Locale::Maketext"),
- via { DateTime::Locale->load($_->language_tag) },
- from Str, via { DateTime::Locale->load($_) },
- ],
+
+Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
+ MooseX::Meta::TypeConstraint::Structured->new(
+ name => "MooseX::Types::Structured::Dict",
+ parent => find_type_constraint('HashRef'),
+ constraint_generator=> sub {
+ ## Get the constraints and values to check
+ my %type_constraints = @{shift @_};
+ my %values = %{shift @_};
+ ## Perform the checking
+ while(%type_constraints) {
+ my($key, $type_constraint) = each %type_constraints;
+ delete $type_constraints{$key};
+ if(exists $values{$key}) {
+ my $value = $values{$key};
+ delete $values{$key};
+ unless($type_constraint->check($value)) {
+ return;
+ }
+ } else {
+ return;
+ }
+ }
+ ## Make sure there are no leftovers.
+ if(%values) {
+ return;
+ } elsif(%type_constraints) {
+ return;
+ }else {
+ return 1;
+ }
+ },
+ )
);
-for my $type ( "DateTime", DateTime ) {
- coerce $type => @{ $coercions{DateTime} };
-}
-
-for my $type ( "DateTime::Duration", Duration ) {
- coerce $type => @{ $coercions{"DateTime::Duration"} };
-}
-
-for my $type ( "DateTime::TimeZone", TimeZone ) {
- coerce $type => @{ $coercions{"DateTime::TimeZone"} };
-}
-
-for my $type ( "DateTime::Locale", Locale ) {
- coerce $type => @{ $coercions{"DateTime::Locale"} };
-}
-
-__PACKAGE__
-
-__END__
-
-=pod
-
-=head1 NAME
-
-MooseX::Types::DateTime - L<DateTime> related constraints and coercions for
-Moose
-
-=head1 SYNOPSIS
-
-Export Example:
-
- use MooseX::Types::DateTime qw(TimeZone);
-
- has time_zone => (
- isa => TimeZone,
- is => "rw",
- coerce => 1,
- );
-
- Class->new( time_zone => "Africa/Timbuktu" );
-
-Namespaced Example:
-
- use MooseX::Types::DateTime;
-
- has time_zone => (
- isa => 'DateTime::TimeZone',
- is => "rw",
- coerce => 1,
- );
-
- Class->new( time_zone => "Africa/Timbuktu" );
-
-=head1 DESCRIPTION
-
-This module packages several L<Moose::Util::TypeConstraints> with coercions,
-designed to work with the L<DateTime> suite of objects.
-
-=head1 CONSTRAINTS
-
-=over 4
-
-=item L<DateTime>
-
-A class type for L<DateTime>.
-
-=over 4
-
-=item from C<Num>
-
-Uses L<DateTime/from_epoch>. Floating values will be used for subsecond
-percision, see L<DateTime> for details.
-
-=item from C<HashRef>
-
-Calls L<DateTime/new> with the hash entries as arguments.
-
-=back
-
-=item L<Duration>
-
-A class type for L<DateTime::Duration>
-
-=over 4
-
-=item from C<Num>
-
-Uses L<DateTime::Duration/new> and passes the number as the C<seconds> argument.
-
-Note that due to leap seconds, DST changes etc this may not do what you expect.
-For instance passing in C<86400> is not always equivalent to one day, although
-there are that many seconds in a day. See L<DateTime/"How Date Math is Done">
-for more details.
-
-=item from C<HashRef>
-
-Calls L<DateTime::Duration/new> with the hash entries as arguments.
-
-=back
-
-=item L<DateTime::Locale>
-
-A class type for L<DateTime::Locale::root> with the name L<DateTime::Locale>.
-
-=over 4
-
-=item from C<Str>
-
-The string is treated as a language tag (e.g. C<en> or C<he_IL>) and given to
-L<DateTime::Locale/load>.
-
-=item from L<Locale::Maktext>
-
-The C<Locale::Maketext/language_tag> attribute will be used with L<DateTime::Locale/load>.
-
-=item L<DateTime::TimeZone>
-
-A class type for L<DateTime::TimeZone>.
-
-=over 4
-
-=item from C<Str>
-
-Treated as a time zone name or offset. See L<DateTime::TimeZone/USAGE> for more
-details on the allowed values.
-
-Delegates to L<DateTime::TimeZone/new> with the string as the C<name> argument.
-
-=back
-
=head1 SEE ALSO
-L<MooseX::Types::DateTimeX>
+The following modules or resources may be of interest.
-L<DateTime>, L<DateTimeX::Easy>
+L<Moose>, L<MooseX::TypeLibrary>, L<Moose::Meta::TypeConstraint>,
+L<MooseX::Meta::TypeConstraint::Structured>
-=head1 VERSION CONTROL
+=head1 TODO
-L<http://code2.0beta.co.uk/moose/svn/MooseX-Types-DateTime/trunk>. Ask on
-#moose for commit bits.
+Need to clarify deep coercions, need to clarify subtypes of subtypes.
=head1 AUTHOR
-Yuval Kogman E<lt>nothingmuch@woobling.orgE<gt>
-
-John Napiorkowski E<lt>jjn1056 at yahoo.comE<gt>
+John Napiorkowski, C<< <jjnapiork@cpan.org> >>
-=head1 COPYRIGHT
+=head1 COPYRIGHT & LICENSE
- Copyright (c) 2008 Yuval Kogman. All rights reserved
- This program is free software; you can redistribute
- it and/or modify it under the same terms as Perl itself.
+This program is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
=cut
+
+1;