/extlib/Params/ValidatePP.pm

http://github.com/openmelody/melody · Perl · 714 lines · 522 code · 141 blank · 51 comment · 76 complexity · 6390bd63fab276864b63032680effee2 MD5 · raw file

  1. package Params::Validate;
  2. use strict;
  3. use warnings;
  4. use Scalar::Util ();
  5. # suppress subroutine redefined warnings if we tried to load the XS
  6. # version and failed.
  7. no warnings 'redefine';
  8. BEGIN {
  9. sub SCALAR () {1}
  10. sub ARRAYREF () {2}
  11. sub HASHREF () {4}
  12. sub CODEREF () {8}
  13. sub GLOB () {16}
  14. sub GLOBREF () {32}
  15. sub SCALARREF () {64}
  16. sub UNKNOWN () {128}
  17. sub UNDEF () {256}
  18. sub OBJECT () {512}
  19. sub HANDLE () { 16 | 32 }
  20. sub BOOLEAN () { 1 | 256 }
  21. }
  22. # Various internals notes (for me and any future readers of this
  23. # monstrosity):
  24. #
  25. # - A lot of the weirdness is _intentional_, because it optimizes for
  26. # the _success_ case. It does not really matter how slow the code is
  27. # after it enters a path that leads to reporting failure. But the
  28. # "success" path should be as fast as possible.
  29. #
  30. # -- We only calculate $called as needed for this reason, even though it
  31. # means copying code all over.
  32. #
  33. # - All the validation routines need to be careful never to alter the
  34. # references that are passed.
  35. #
  36. # -- The code assumes that _most_ callers will not be using the
  37. # skip_leading or ignore_case features. In order to not alter the
  38. # references passed in, we copy them wholesale when normalizing them
  39. # to make these features work. This is slower but lets us be faster
  40. # when not using them.
  41. # Matt Sergeant came up with this prototype, which slickly takes the
  42. # first array (which should be the caller's @_), and makes it a
  43. # reference. Everything after is the parameters for validation.
  44. sub validate_pos (\@@) {
  45. return if $NO_VALIDATION && !defined wantarray;
  46. my $p = shift;
  47. my @specs = @_;
  48. my @p = @$p;
  49. if ($NO_VALIDATION) {
  50. # if the spec is bigger that's where we can start adding
  51. # defaults
  52. for ( my $x = $#p + 1; $x <= $#specs; $x++ ) {
  53. $p[$x] = $specs[$x]->{default}
  54. if ref $specs[$x] && exists $specs[$x]->{default};
  55. }
  56. return wantarray ? @p : \@p;
  57. }
  58. # I'm too lazy to pass these around all over the place.
  59. local $options ||= _get_options( ( caller(0) )[0] )
  60. unless defined $options;
  61. my $min = 0;
  62. while (1) {
  63. last
  64. unless (
  65. ref $specs[$min]
  66. ? !( exists $specs[$min]->{default} || $specs[$min]->{optional} )
  67. : $specs[$min]
  68. );
  69. $min++;
  70. }
  71. my $max = scalar @specs;
  72. my $actual = scalar @p;
  73. unless ( $actual >= $min
  74. && ( $options->{allow_extra} || $actual <= $max ) ) {
  75. my $minmax = (
  76. $options->{allow_extra}
  77. ? "at least $min"
  78. : ( $min != $max ? "$min - $max" : $max )
  79. );
  80. my $val = $options->{allow_extra} ? $min : $max;
  81. $minmax .= $val != 1 ? ' were' : ' was';
  82. my $called = _get_called();
  83. $options->{on_fail}->( "$actual parameter"
  84. . ( $actual != 1 ? 's' : '' ) . " "
  85. . ( $actual != 1 ? 'were' : 'was' )
  86. . " passed to $called but $minmax expected\n" );
  87. }
  88. my $bigger = $#p > $#specs ? $#p : $#specs;
  89. foreach ( 0 .. $bigger ) {
  90. my $spec = $specs[$_];
  91. next unless ref $spec;
  92. if ( $_ <= $#p ) {
  93. my $value = defined $p[$_] ? qq|"$p[$_]"| : 'undef';
  94. _validate_one_param( $p[$_], \@p, $spec,
  95. "Parameter #" . ( $_ + 1 ) . " ($value)" );
  96. }
  97. $p[$_] = $spec->{default} if $_ > $#p && exists $spec->{default};
  98. }
  99. _validate_pos_depends( \@p, \@specs );
  100. foreach (
  101. grep {
  102. defined $p[$_]
  103. && !ref $p[$_]
  104. && ref $specs[$_]
  105. && $specs[$_]{untaint}
  106. } 0 .. $bigger
  107. ) {
  108. ( $p[$_] ) = $p[$_] =~ /(.+)/;
  109. }
  110. return wantarray ? @p : \@p;
  111. }
  112. sub _validate_pos_depends {
  113. my ( $p, $specs ) = @_;
  114. for my $p_idx ( 0 .. $#$p ) {
  115. my $spec = $specs->[$p_idx];
  116. next
  117. unless $spec
  118. && UNIVERSAL::isa( $spec, 'HASH' )
  119. && exists $spec->{depends};
  120. my $depends = $spec->{depends};
  121. if ( ref $depends ) {
  122. require Carp;
  123. local $Carp::CarpLevel = 2;
  124. Carp::croak(
  125. "Arguments to 'depends' for validate_pos() must be a scalar");
  126. }
  127. my $p_size = scalar @$p;
  128. if ( $p_size < $depends - 1 ) {
  129. my $error
  130. = ( "Parameter #"
  131. . ( $p_idx + 1 )
  132. . " depends on parameter #"
  133. . $depends
  134. . ", which was not given" );
  135. $options->{on_fail}->($error);
  136. }
  137. }
  138. return 1;
  139. }
  140. sub _validate_named_depends {
  141. my ( $p, $specs ) = @_;
  142. foreach my $pname ( keys %$p ) {
  143. my $spec = $specs->{$pname};
  144. next
  145. unless $spec
  146. && UNIVERSAL::isa( $spec, 'HASH' )
  147. && $spec->{depends};
  148. unless ( UNIVERSAL::isa( $spec->{depends}, 'ARRAY' )
  149. || !ref $spec->{depends} ) {
  150. require Carp;
  151. local $Carp::CarpLevel = 2;
  152. Carp::croak(
  153. "Arguments to 'depends' must be a scalar or arrayref");
  154. }
  155. foreach my $depends_name (
  156. ref $spec->{depends}
  157. ? @{ $spec->{depends} }
  158. : $spec->{depends}
  159. ) {
  160. unless ( exists $p->{$depends_name} ) {
  161. my $error
  162. = ( "Parameter '$pname' depends on parameter '"
  163. . $depends_name
  164. . "', which was not given" );
  165. $options->{on_fail}->($error);
  166. }
  167. }
  168. }
  169. }
  170. sub validate (\@$) {
  171. return if $NO_VALIDATION && !defined wantarray;
  172. my $p = $_[0];
  173. my $specs = $_[1];
  174. local $options = _get_options( ( caller(0) )[0] ) unless defined $options;
  175. if ( ref $p eq 'ARRAY' ) {
  176. # we were called as validate( @_, ... ) where @_ has a
  177. # single element, a hash reference
  178. if ( ref $p->[0] ) {
  179. $p = { %{ $p->[0] } };
  180. }
  181. elsif ( @$p % 2 ) {
  182. my $called = _get_called();
  183. $options->{on_fail}
  184. ->( "Odd number of parameters in call to $called "
  185. . "when named parameters were expected\n" );
  186. }
  187. else {
  188. $p = {@$p};
  189. }
  190. }
  191. if ( $options->{normalize_keys} ) {
  192. $specs = _normalize_callback( $specs, $options->{normalize_keys} );
  193. $p = _normalize_callback( $p, $options->{normalize_keys} );
  194. }
  195. elsif ( $options->{ignore_case} || $options->{strip_leading} ) {
  196. $specs = _normalize_named($specs);
  197. $p = _normalize_named($p);
  198. }
  199. if ($NO_VALIDATION) {
  200. return (
  201. wantarray
  202. ? (
  203. # this is a hash containing just the defaults
  204. (
  205. map { $_ => $specs->{$_}->{default} }
  206. grep {
  207. ref $specs->{$_} && exists $specs->{$_}->{default}
  208. }
  209. keys %$specs
  210. ),
  211. (
  212. ref $p eq 'ARRAY'
  213. ? (
  214. ref $p->[0]
  215. ? %{ $p->[0] }
  216. : @$p
  217. )
  218. : %$p
  219. )
  220. )
  221. : do {
  222. my $ref = (
  223. ref $p eq 'ARRAY'
  224. ? (
  225. ref $p->[0]
  226. ? $p->[0]
  227. : {@$p}
  228. )
  229. : $p
  230. );
  231. foreach (
  232. grep {
  233. ref $specs->{$_}
  234. && exists $specs->{$_}->{default}
  235. }
  236. keys %$specs
  237. ) {
  238. $ref->{$_} = $specs->{$_}->{default}
  239. unless exists $ref->{$_};
  240. }
  241. return $ref;
  242. }
  243. );
  244. }
  245. _validate_named_depends( $p, $specs );
  246. unless ( $options->{allow_extra} ) {
  247. if ( my @unmentioned = grep { !exists $specs->{$_} } keys %$p ) {
  248. my $called = _get_called();
  249. $options->{on_fail}->( "The following parameter"
  250. . ( @unmentioned > 1 ? 's were' : ' was' )
  251. . " passed in the call to $called but "
  252. . ( @unmentioned > 1 ? 'were' : 'was' )
  253. . " not listed in the validation options: @unmentioned\n"
  254. );
  255. }
  256. }
  257. my @missing;
  258. # the iterator needs to be reset in case the same hashref is being
  259. # passed to validate() on successive calls, because we may not go
  260. # through all the hash's elements
  261. keys %$specs;
  262. OUTER:
  263. while ( my ( $key, $spec ) = each %$specs ) {
  264. if (
  265. !exists $p->{$key}
  266. && (
  267. ref $spec
  268. ? !(
  269. do {
  270. # we want to short circuit the loop here if we
  271. # can assign a default, because there's no need
  272. # check anything else at all.
  273. if ( exists $spec->{default} ) {
  274. $p->{$key} = $spec->{default};
  275. next OUTER;
  276. }
  277. }
  278. || do {
  279. # Similarly, an optional parameter that is
  280. # missing needs no additional processing.
  281. next OUTER if $spec->{optional};
  282. }
  283. )
  284. : $spec
  285. )
  286. ) {
  287. push @missing, $key;
  288. }
  289. # Can't validate a non hashref spec beyond the presence or
  290. # absence of the parameter.
  291. elsif ( ref $spec ) {
  292. my $value = defined $p->{$key} ? qq|"$p->{$key}"| : 'undef';
  293. _validate_one_param( $p->{$key}, $p, $spec,
  294. "The '$key' parameter ($value)" );
  295. }
  296. }
  297. if (@missing) {
  298. my $called = _get_called();
  299. my $missing = join ', ', map {"'$_'"} @missing;
  300. $options->{on_fail}->( "Mandatory parameter"
  301. . ( @missing > 1 ? 's' : '' )
  302. . " $missing missing in call to $called\n" );
  303. }
  304. # do untainting after we know everything passed
  305. foreach my $key (
  306. grep {
  307. defined $p->{$_}
  308. && !ref $p->{$_}
  309. && ref $specs->{$_}
  310. && $specs->{$_}{untaint}
  311. }
  312. keys %$p
  313. ) {
  314. ( $p->{$key} ) = $p->{$key} =~ /(.+)/;
  315. }
  316. return wantarray ? %$p : $p;
  317. }
  318. sub validate_with {
  319. return if $NO_VALIDATION && !defined wantarray;
  320. my %p = @_;
  321. local $options = _get_options( ( caller(0) )[0], %p );
  322. unless ($NO_VALIDATION) {
  323. unless ( exists $options->{called} ) {
  324. $options->{called} = ( caller( $options->{stack_skip} ) )[3];
  325. }
  326. }
  327. if ( UNIVERSAL::isa( $p{spec}, 'ARRAY' ) ) {
  328. return validate_pos( @{ $p{params} }, @{ $p{spec} } );
  329. }
  330. else {
  331. # intentionally ignore the prototype because this contains
  332. # either an array or hash reference, and validate() will
  333. # handle either one properly
  334. return &validate( $p{params}, $p{spec} );
  335. }
  336. }
  337. sub _normalize_callback {
  338. my ( $p, $func ) = @_;
  339. my %new;
  340. foreach my $key ( keys %$p ) {
  341. my $new_key = $func->($key);
  342. unless ( defined $new_key ) {
  343. die
  344. "The normalize_keys callback did not return a defined value when normalizing the key '$key'";
  345. }
  346. if ( exists $new{$new_key} ) {
  347. die
  348. "The normalize_keys callback returned a key that already exists, '$new_key', when normalizing the key '$key'";
  349. }
  350. $new{$new_key} = $p->{$key};
  351. }
  352. return \%new;
  353. }
  354. sub _normalize_named {
  355. # intentional copy so we don't destroy original
  356. my %h = ( ref $_[0] ) =~ /ARRAY/ ? @{ $_[0] } : %{ $_[0] };
  357. if ( $options->{ignore_case} ) {
  358. $h{ lc $_ } = delete $h{$_} for keys %h;
  359. }
  360. if ( $options->{strip_leading} ) {
  361. foreach my $key ( keys %h ) {
  362. my $new;
  363. ( $new = $key ) =~ s/^\Q$options->{strip_leading}\E//;
  364. $h{$new} = delete $h{$key};
  365. }
  366. }
  367. return \%h;
  368. }
  369. sub _validate_one_param {
  370. my ( $value, $params, $spec, $id ) = @_;
  371. if ( exists $spec->{type} ) {
  372. unless ( defined $spec->{type}
  373. && Scalar::Util::looks_like_number( $spec->{type} )
  374. && $spec->{type} > 0 ) {
  375. my $msg
  376. = "$id has a type specification which is not a number. It is ";
  377. if ( defined $spec->{type} ) {
  378. $msg .= "a string - $spec->{type}";
  379. }
  380. else {
  381. $msg .= "undef";
  382. }
  383. $msg
  384. .= ".\n Use the constants exported by Params::Validate to declare types.";
  385. $options->{on_fail}->($msg);
  386. }
  387. unless ( _get_type($value) & $spec->{type} ) {
  388. my $type = _get_type($value);
  389. my @is = _typemask_to_strings($type);
  390. my @allowed = _typemask_to_strings( $spec->{type} );
  391. my $article = $is[0] =~ /^[aeiou]/i ? 'an' : 'a';
  392. my $called = _get_called(1);
  393. $options->{on_fail}->( "$id to $called was $article '@is', which "
  394. . "is not one of the allowed types: @allowed\n" );
  395. }
  396. }
  397. # short-circuit for common case
  398. return
  399. unless ( $spec->{isa}
  400. || $spec->{can}
  401. || $spec->{callbacks}
  402. || $spec->{regex} );
  403. if ( exists $spec->{isa} ) {
  404. foreach ( ref $spec->{isa} ? @{ $spec->{isa} } : $spec->{isa} ) {
  405. unless ( eval { $value->isa($_) } ) {
  406. my $is = ref $value ? ref $value : 'plain scalar';
  407. my $article1 = $_ =~ /^[aeiou]/i ? 'an' : 'a';
  408. my $article2 = $is =~ /^[aeiou]/i ? 'an' : 'a';
  409. my $called = _get_called(1);
  410. $options->{on_fail}
  411. ->( "$id to $called was not $article1 '$_' "
  412. . "(it is $article2 $is)\n" );
  413. }
  414. }
  415. }
  416. if ( exists $spec->{can} ) {
  417. foreach ( ref $spec->{can} ? @{ $spec->{can} } : $spec->{can} ) {
  418. unless ( eval { $value->can($_) } ) {
  419. my $called = _get_called(1);
  420. $options->{on_fail}
  421. ->("$id to $called does not have the method: '$_'\n");
  422. }
  423. }
  424. }
  425. if ( $spec->{callbacks} ) {
  426. unless ( UNIVERSAL::isa( $spec->{callbacks}, 'HASH' ) ) {
  427. my $called = _get_called(1);
  428. $options->{on_fail}->(
  429. "'callbacks' validation parameter for $called must be a hash reference\n"
  430. );
  431. }
  432. foreach ( keys %{ $spec->{callbacks} } ) {
  433. unless ( UNIVERSAL::isa( $spec->{callbacks}{$_}, 'CODE' ) ) {
  434. my $called = _get_called(1);
  435. $options->{on_fail}->(
  436. "callback '$_' for $called is not a subroutine reference\n"
  437. );
  438. }
  439. unless ( $spec->{callbacks}{$_}->( $value, $params ) ) {
  440. my $called = _get_called(1);
  441. $options->{on_fail}
  442. ->("$id to $called did not pass the '$_' callback\n");
  443. }
  444. }
  445. }
  446. if ( exists $spec->{regex} ) {
  447. unless ( ( defined $value ? $value : '' ) =~ /$spec->{regex}/ ) {
  448. my $called = _get_called(1);
  449. $options->{on_fail}
  450. ->("$id to $called did not pass regex check\n");
  451. }
  452. }
  453. }
  454. {
  455. # if it UNIVERSAL::isa the string on the left then its the type on
  456. # the right
  457. my %isas = (
  458. 'ARRAY' => ARRAYREF,
  459. 'HASH' => HASHREF,
  460. 'CODE' => CODEREF,
  461. 'GLOB' => GLOBREF,
  462. 'SCALAR' => SCALARREF,
  463. );
  464. my %simple_refs = map { $_ => 1 } keys %isas;
  465. sub _get_type {
  466. return UNDEF unless defined $_[0];
  467. my $ref = ref $_[0];
  468. unless ($ref) {
  469. # catches things like: my $fh = do { local *FH; };
  470. return GLOB if UNIVERSAL::isa( \$_[0], 'GLOB' );
  471. return SCALAR;
  472. }
  473. return $isas{$ref} if $simple_refs{$ref};
  474. foreach ( keys %isas ) {
  475. return $isas{$_} | OBJECT if UNIVERSAL::isa( $_[0], $_ );
  476. }
  477. # I really hope this never happens.
  478. return UNKNOWN;
  479. }
  480. }
  481. {
  482. my %type_to_string = (
  483. SCALAR() => 'scalar',
  484. ARRAYREF() => 'arrayref',
  485. HASHREF() => 'hashref',
  486. CODEREF() => 'coderef',
  487. GLOB() => 'glob',
  488. GLOBREF() => 'globref',
  489. SCALARREF() => 'scalarref',
  490. UNDEF() => 'undef',
  491. OBJECT() => 'object',
  492. UNKNOWN() => 'unknown',
  493. );
  494. sub _typemask_to_strings {
  495. my $mask = shift;
  496. my @types;
  497. foreach (
  498. SCALAR, ARRAYREF, HASHREF, CODEREF, GLOB, GLOBREF,
  499. SCALARREF, UNDEF, OBJECT, UNKNOWN
  500. ) {
  501. push @types, $type_to_string{$_} if $mask & $_;
  502. }
  503. return @types ? @types : ('unknown');
  504. }
  505. }
  506. {
  507. my %defaults = (
  508. ignore_case => 0,
  509. strip_leading => 0,
  510. allow_extra => 0,
  511. on_fail => sub {
  512. require Carp;
  513. Carp::confess( $_[0] );
  514. },
  515. stack_skip => 1,
  516. normalize_keys => undef,
  517. );
  518. *set_options = \&validation_options;
  519. sub validation_options {
  520. my %opts = @_;
  521. my $caller = caller;
  522. foreach ( keys %defaults ) {
  523. $opts{$_} = $defaults{$_} unless exists $opts{$_};
  524. }
  525. $OPTIONS{$caller} = \%opts;
  526. }
  527. sub _get_options {
  528. my $caller = shift;
  529. if (@_) {
  530. return (
  531. $OPTIONS{$caller}
  532. ? {
  533. %{ $OPTIONS{$caller} },
  534. @_
  535. }
  536. : { %defaults, @_ }
  537. );
  538. }
  539. else {
  540. return (
  541. exists $OPTIONS{$caller}
  542. ? $OPTIONS{$caller}
  543. : \%defaults
  544. );
  545. }
  546. }
  547. }
  548. sub _get_called {
  549. my $extra_skip = $_[0] || 0;
  550. # always add one more for this sub
  551. $extra_skip++;
  552. my $called = (
  553. exists $options->{called}
  554. ? $options->{called}
  555. : ( caller( $options->{stack_skip} + $extra_skip ) )[3]
  556. );
  557. $called = 'N/A' unless defined $called;
  558. return $called;
  559. }
  560. 1;
  561. __END__
  562. =head1 NAME
  563. Params::ValidatePP - pure Perl implementation of Params::Validate
  564. =head1 SYNOPSIS
  565. See Params::Validate
  566. =head1 DESCRIPTION
  567. This is a pure Perl implementation of Params::Validate. See the
  568. Params::Validate documentation for details.
  569. =head1 COPYRIGHT
  570. Copyright (c) 2004-2007 David Rolsky. All rights reserved. This
  571. program is free software; you can redistribute it and/or modify it
  572. under the same terms as Perl itself.
  573. =cut