Skip site navigation (1)Skip section navigation (2)

FreeBSD Manual Pages

  
 
  

home | help
Data::Rx::Manual::CustUserpContributed Perl DoData::Rx::Manual::CustomTypes(3)

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

VERSION
       version 0.200007

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

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

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

   W3C DateTime	- using	Perl and CPAN in checks
       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'	);

   Enum	- delegate to another schema
       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".

   CSV - delegation, checking input
       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:

       o   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!)

       o   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!

POD AUTHOR
       Hakim Cassimally	<osfameron@cpan.org>

AUTHOR
       Ricardo SIGNES <rjbs@cpan.org>

COPYRIGHT AND LICENSE
       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.

perl v5.32.1			  2015-04-10  Data::Rx::Manual::CustomTypes(3)

NAME | VERSION | SYNOPSIS | OVERVIEW | EXAMPLES | POD AUTHOR | AUTHOR | COPYRIGHT AND LICENSE

Want to link to this manual page? Use this URL:
<https://www.freebsd.org/cgi/man.cgi?query=Data::Rx::Manual::CustomTypes&sektion=3&manpath=FreeBSD+13.0-RELEASE+and+Ports>

home | help