#!/usr/bin/perl package Class::Workflow::Transition::Validate::Simple; use Moose::Role; with qw/ Class::Workflow::Transition Class::Workflow::Transition::Validate /; has error_state => ( does => "Class::Workflow::State", is => "rw", ); has no_die => ( isa => "Bool", is => "rw", default => 0, ); has validators => ( isa => "ArrayRef", is => "rw", auto_deref => 1, default => sub { [] }, ); has ignore_validator_rv => ( isa => "Bool", is => "rw", default => 0, ); sub add_validators { my ( $self, @validators ) = @_; push @{ $self->validators }, @validators; } sub clear_validators { my $self = shift; $self->validators([]); } sub validate { my ( $self, $instance, @args ) = @_; my $ignore_rv = $self->ignore_validator_rv; my @errors; foreach my $validator ( $self->validators ) { local $@; my $ok = eval { $self->$validator( $instance, @args ) }; if ( $@ ) { push @errors, $@; } elsif ( !$ignore_rv and !$ok ) { push @errors, "general error"; } } s/ at .* line \d+\.\n//s for @errors; die join(", ", @errors) . "\n" if @errors; # FIXME add @errors to an exception object that stringifies return 1; } sub validation_error { my ( $self, $error, $instance, @args ) = @_; chomp $error unless ref $error; if ( my $state = $self->error_state ) { return $self->derive_and_accept_instance( $instance => { state => $state, error => $error }, @args ); } else { if ( $self->no_die ) { return $self->derive_and_accept_instance( $instance => { state => $instance->state, error => $error, }, @args ); } else { die $error; } } } __PACKAGE__; __END__ =pod =head1 NAME Class::Workflow::Transition::Validate::Simple - Easier validation code. =head1 SYNOPSIS package MyTransition; use Moose; with qw/Class::Workflow::Transition::Validate::Simple/; # ... $t->clear_validators; $t->add_validators( sub { ... } ); =head1 DESCRIPTION =head1 FIELDS =over 4 =item ignore_validator_rv This is useful if your validators only throw exceptions. Defaults to false =item error_state This contains a state that will be moved to if a validation error occurs. In conjunction with L this can work like a catch block. The instance will have the C attribute set. =item no_die If this is set, C will not die even if C is not set. The instance will have the C attribute set, and will be reaccepted into it's current state, with the error. =back =head1 METHODS =over 4 =item validators This is an optional list of sub refs which will be called to validate input before applying C. They should raise an exception or return a false value if the input is bad. They may put validation result information inside the L or equivalent, if one is used. A more comprehensive solution is to override the C method yourself and provide rich exception objects with validation error descriptors inside them. The validators are invoked as methods on the transition. IF C is true then only exceptions are considered input validations. =item add_validators @code_refs =item clear_validators Modify the list of validators. =back =head1 ROLES This role consumes the following roles: =over 4 =item * L =back =cut