GSP
Quick Navigator

Search Site

Unix VPS
A - Starter
B - Basic
C - Preferred
D - Commercial
MPS - Dedicated
Previous VPSs
* Sign Up! *

Support
Contact Us
Online Help
Handbooks
Domain Status
Man Pages

FAQ
Virtual Servers
Pricing
Billing
Technical

Network
Facilities
Connectivity
Topology Map

Miscellaneous
Server Agreement
Year 2038
Credits
 

USA Flag

 

 

Man Pages
Data::Rx::Manual::CustomTypes(3) User Contributed Perl Documentation Data::Rx::Manual::CustomTypes(3)

Data::Rx::Manual::CustomTypes - overview of making new checkers

version 0.200007

The 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 );

Data::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.

These 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.

We 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' );

You'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".

Some 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:

  • There isn't yet a convenient way to specify a schema, so we'll just accept "//any" for now. As we will then pass this result to "make_schema" shortly, we will get a further validation of that in any case! (But see <http://rx.codesimply.com/moretypes.html> for the full definition of a schema, if you prefer!)
  • Rx's type "//bool" is deliberately targeted at JSON like boolean objects, so we'll also accept undef and 1 as "truthy" values.

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!

Hakim Cassimally <osfameron@cpan.org>

Ricardo SIGNES <rjbs@cpan.org>

This 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.

2015-04-10 perl v5.32.1

Search for    or go to Top of page |  Section 3 |  Main Index

Powered by GSP Visit the GSP FreeBSD Man Page Interface.
Output converted with ManDoc.