package Type::Tiny::ConstrainedObject; use 5.008001; use strict; use warnings; BEGIN { $Type::Tiny::ConstrainedObject::AUTHORITY = 'cpan:TOBYINK'; $Type::Tiny::ConstrainedObject::VERSION = '2.004000'; } $Type::Tiny::ConstrainedObject::VERSION =~ tr/_//d; sub _croak ($;@) { require Error::TypeTiny; goto \&Error::TypeTiny::croak } use Type::Tiny (); our @ISA = 'Type::Tiny'; my %errlabel = ( parent => 'a parent', constraint => 'a constraint coderef', inlined => 'an inlining coderef', ); sub new { my $proto = shift; my %opts = ( @_ == 1 ) ? %{ $_[0] } : @_; for my $key ( qw/ parent constraint inlined / ) { next unless exists $opts{$key}; _croak( '%s type constraints cannot have %s passed to the constructor', $proto->_short_name, $errlabel{$key}, ); } $proto->SUPER::new( %opts ); } #/ sub new sub has_parent { !!1; } sub parent { require Types::Standard; Types::Standard::Object(); } sub _short_name { die "subclasses must implement this"; # uncoverable statement } my $i = 0; my $_where_expressions = sub { my $self = shift; my $name = shift; $name ||= "where expression check"; my ( %env, @codes ); while ( @_ ) { my $expr = shift; my $constraint = shift; if ( !ref $constraint ) { push @codes, sprintf( 'do { local $_ = %s; %s }', $expr, $constraint ); } else { require Types::Standard; my $type = Types::Standard::is_RegexpRef( $constraint ) ? Types::Standard::StrMatch()->of( $constraint ) : Types::TypeTiny::to_TypeTiny( $constraint ); if ( $type->can_be_inlined ) { push @codes, sprintf( 'do { my $tmp = %s; %s }', $expr, $type->inline_check( '$tmp' ) ); } else { ++$i; $env{ '$chk' . $i } = do { my $chk = $type->compiled_check; \$chk }; push @codes, sprintf( '$chk%d->(%s)', $i, $expr ); } } #/ else [ if ( !ref $constraint )] } #/ while ( @_ ) if ( keys %env ) { # cannot inline my $sub = Eval::TypeTiny::eval_closure( source => sprintf( 'sub ($) { local $_ = shift; %s }', join( q( and ), @codes ) ), description => sprintf( '%s for %s', $name, $self->name ), environment => \%env, ); return $self->where( $sub ); } #/ if ( keys %env ) else { return $self->where( join( q( and ), @codes ) ); } }; sub stringifies_to { my $self = shift; my ( $constraint ) = @_; $self->$_where_expressions( "stringification check", q{"$_"}, $constraint ); } sub numifies_to { my $self = shift; my ( $constraint ) = @_; $self->$_where_expressions( "numification check", q{0+$_}, $constraint ); } sub with_attribute_values { my $self = shift; my %constraint = @_; $self->$_where_expressions( "attributes check", map { my $attr = $_; qq{\$_->$attr} => $constraint{$attr} } sort keys %constraint, ); } 1; __END__ =pod =encoding utf-8 =head1 NAME Type::Tiny::ConstrainedObject - shared behavour for Type::Tiny::Class, etc =head1 STATUS This module is considered experiemental. =head1 DESCRIPTION =head2 Methods The following methods exist for L, L, L, and any type constraints that inherit from C or C in L. These methods will also work for L if at least one of the types in the intersection provides these methods. These methods will also work for L if all of the types in the union provide these methods. =over =item C<< stringifies_to($constraint) >> Generates a new child type constraint which checks the object's stringification against a constraint. For example: my $type = Type::Tiny::Class->new(class => 'URI'); my $child = $type->stringifies_to( StrMatch[qr/^http:/] ); $child->assert_valid( URI->new("http://example.com/") ); In the above example, C<< $child >> is a type constraint that checks objects are blessed into (or inherit from) the URI class, and when stringified (e.g. though overloading) the result matches the regular expression C<< qr/^http:/ >>. C<< $constraint >> may be a type constraint, something that can be coerced to a type constraint (such as a coderef returning a boolean), a string of Perl code operating on C<< $_ >>, or a reference to a regular expression. So the following would work: my $child = $type->stringifies_to( sub { qr/^http:/ } ); my $child = $type->stringifies_to( qr/^http:/ ); my $child = $type->stringifies_to( 'm/^http:/' ); my $child = $type->where('"$_" =~ /^http:/'); =item C<< numifies_to($constraint) >> The same as C but checks numification. The following might be useful: use Types::Standard qw(Int Overload); my $IntLike = Int | Overload->numifies_to(Int) =item C<< with_attribute_values($attr1 => $constraint1, ...) >> This is best explained with an example: use Types::Common qw( InstanceOf StrMatch IntRange ); my $person = InstanceOf['Local::Human']; my $woman = $person->with_attribute_values( gender => StrMatch[ qr/^F/i ], age => IntRange[ 18 => () ], ); $woman->assert_valid($alice); This assertion will firstly check that C<< $alice >> is a Local::Human, then check that C<< $alice->gender >> starts with an "F", and lastly check that C<< $alice->age >> is an integer at least 18. Again, constraints can be type constraints, coderefs, strings of Perl code, or regular expressions. Technically the "attributes" don't need to be Moo/Moose/Mouse attributes, but any methods which can be called with no parameters and return a scalar. =back =head1 BUGS Please report any bugs to L. =head1 SEE ALSO L. L. =head1 AUTHOR Toby Inkster Etobyink@cpan.orgE. =head1 COPYRIGHT AND LICENCE This software is copyright (c) 2019-2023 by Toby Inkster. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =head1 DISCLAIMER OF WARRANTIES THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.