![]() |
![]()
| ![]() |
![]()
NAMEData::Rx::Manual::CustomTypes - overview of making new checkersVERSIONversion 0.200007SYNOPSISThe easiest way to create a custom type plugin is to subclass Data::Rx::CommonType::EasyNew.package My::Type::Foo; use parent 'Data::Rx::CommonType::EasyNew'; sub type_uri { 'tag:example.com,EXAMPLE:rx/foo', } sub guts_from_arg { my ($class, $arg, $rx) = @_; # get and validate arguments from $arg return { # the "guts" for this object # these might be validator objects using CPAN modules # or using $rx->make_schema() etc. }, } sub assert_valid { my ($self, $value) = @_; # check the value, and either return 1 for success # or die on failure } 1; and later... use Data::Rx; use My::Type::Foo; my $rx = Data::Rx->new({ sort_keys => 1, prefix => { example => 'tag:example.com,EXAMPLE:rx/', }, type_plugins => [qw( My::Type::Foo )], }); my $schema = $rx->make_schema('/example/foo'); $schema->assert_valid( $some_value ); OVERVIEWData::Rx ships with a variety of core validators -- single <http://rx.codesimply.com/coretypes.html#single>, collection <http://rx.codesimply.com/coretypes.html#collect>, and combination <http://rx.codesimply.com/coretypes.html#combo> types, which can be combined in surprisingly powerful ways. However the core language is deliberately limited to known cross-platform features, and there are things that you simply cannot represent with it. However, you can create custom type plugins in any implementation, including Data::Rx in Perl.EXAMPLESThese examples are worked fully in the "examples/" directory. In this man page, we will just look at interesting features of each type plugin, for clarity.W3C DateTime - using Perl and CPAN in checksWe might want to validate dates in the W3CDTF format, which look like "2003-02-15T13:50:05-05:00". We could of course write this with a regular expression, but let's take an even better approach and dash to the CPAN, where we find an existing module, DateTime::Format::W3CDTF.Our parser, then, will instantiate one of these objects, and return it with "guts_from_arg" to be stashed away. use DateTime::Format::W3CDTF; sub guts_from_arg { my ($class, $arg, $rx) = @_; return { dt => DateTime::Format::W3CDTF->new, }; } We can then test this in the "assert_valid" routine by returning true if the date format matches: sub assert_valid { my ($self, $value) = @_; return 1 if $value && eval { $self->{dt}->parse_datetime( $value ); }; If it doesn't, then we should return an error, and to make sure that we act like a good citizen in the Rx ecosystem, let's use "Data::Rx::CommonType::EasyNew"'s provided method "fail": $self->fail({ error => [ qw(type) ], message => "found value is not a w3 datetime", value => $value, }) } Now we can use this checker like so: $rx->make_schema('/example/datetime/w3') ->assert_valid( '2003-02-15T13:50:05-05:00' ); Enum - delegate to another schemaYou'll often want to create data-types that match a set of values like ("open", "closed") or (0, 15, 30, 40). Data::Rx doesn't have an Enum type, but it does have "//any":{ type => '//any', of => [ { type => '//str', value => 'open' }, { type => '//str', value => 'closed' }, ] } This is a bit clumsy though, with the repetition of the type "//str". Instead we would like an Enum type which might be declared like: { type => '/example/enum', contents => { type => '//str', values => [ qw/ open closed /], }, } Ignoring input checking (for this example), we can get this information from the $arg parameter: sub guts_from_arg { my ($class, $arg, $rx) = @_; my $type = $arg->{contents}{type}; my @values = @{ $arg->{contents}{values} }; We already saw how we would write the enum as an "//any" schema. And in fact the easiest way to implement this type plugin is to do exactly that! Let's create a schema which is equivalent, and return it, to be stashed in the object: my $schema = $rx->make_schema({ type => '//any', of => [ map {; { type => $type, value => $_ } } @values, ], }); return { schema => $schema }; } Now, checking the enum is as simple as delegating to this schema: sub assert_valid { my ($self, $value) = @_; $self->{schema}->assert_valid( $value ); } As we are delegating to another schema's "assert_valid" we know that any exceptions will be in the correct format. However, the error will be the one that "//any" provides: Failed //any: matched none of the available alternatives This is probably clear enough for an enum. But we could improve this message by calling "check" instead of "assert_valid" and raising our own, nicely formatted, exception using "fail". CSV - delegation, checking inputSome APIs like to specify a list of IDs or statuses not as an array (which of course Rx handles with "//arr" but as a comma separated list. Curses!We would like to write a type plugin that's defined something like: { type => '/example/csv', contents => '/example/status', } Of course now that we are getting data as strings, we also have to worry about spaces: e.g. in '123, 456', is the second ID ' 456' or just '456'? So let's also accept an optional 3rd parameter "trim". Now that we're asking for a more complex input data structure, let's validate it using Rx itself! sub guts_from_arg { my ($class, $arg, $rx) = @_; my $meta = $rx->make_schema({ type => '//rec', required => { # contents => '/.meta/schema', # not yet implemented contents => '//any', }, optional => { trim => { # we don't just accept //bool as this only includes 'boolean' objects, # let's also allow undef/0/1, as this is more Perlish! type => '//any', of => [ '//nil', '//bool', '//int' ] }, }, }); $meta->assert_valid( $arg ); The "contents" argument is required, and should be a valid schema. We've had to make a few trade-offs:
As we are expecting a comma separated string, the first check we'll want to make is that the object we receive is in fact a string. So the guts we'll return are: return { trim => $arg->{trim}, str_schema => $rx->make_schema('//str'), item_schema => $rx->make_schema( $arg->{contents} ), }; Now our "assert_valid" routine will use all of these pieces: use String::Trim; sub assert_valid { my ($self, $value) = @_; First we check that we got a string: $self->{str_schema}->assert_valid( $value ); This means we can safely split the result: my @values = split ',' => $value; my $item_schema = $self->{item_schema}; my $trim = $self->{trim}; For each result we trim (if requested) and use the supplied checker on each element. for my $subvalue (@values) { trim($subvalue) if $trim; $item_schema->assert_valid( $subvalue ); } return 1; } Putting together all the pieces, we can call this like so: my $csv = $rx->make_schema({ type => '/example/csv', contents => { type => '/example/enum', contents => { type => '//str', values => [qw/ open closed /], } }, trim => 1, }); $csv->assert_valid( 'open, closed' ); # OK! POD AUTHORHakim Cassimally <osfameron@cpan.org>AUTHORRicardo SIGNES <rjbs@cpan.org>COPYRIGHT AND LICENSEThis software is copyright (c) 2015 by Ricardo SIGNES.This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.
|