Validate-CodiceFiscale

 view release on metacpan or  search on metacpan

lib/Validate/CodiceFiscale.pm  view on Meta::CPAN

package Validate::CodiceFiscale;
use v5.24;
use Carp;
use experimental qw< signatures >;
{ our $VERSION = '0.004' }

use List::Util 'sum';
use Time::Local 'timegm';
use JSON::PP 'decode_json';
use Exporter 'import';

our @EXPORT_OK = qw< assert_valid_cf decode_cf is_valid_cf validate_cf r >;

# PUBLIC interface

sub assert_valid_cf ($cf, %options) {
   my $errors = validate_cf($cf, all_errors => 0, %options) or return;

   defined(my $ecb = $options{on_error})
     or croak join ', ', $errors->@*;

   my $exception = $ecb->($errors->@*);
   die $exception;    # just as a fallback, $ecb might throw by itself
} ## end sub assert_valid_cf

sub decode_cf ($cf, %options) {
   return _decode_and_validate($cf, %options, all_errors => 1);
}

sub is_valid_cf ($cf, %options) {
   my $error = 0;
   _validate_cf($cf, $options{data}, sub { $error = 1; return 0 });
   return !$error;
}

sub validate_cf ($cf, %options) {
   my $r = _decode_and_validate($cf, %options);
   my $errors = $r->{errors} // [];
   return scalar($errors->@*) ? $errors : undef;
} ## end sub validate_cf

# The following is useful for one-lines:
#
#     $ perl -MValidate::CodiceFiscale=r -er bcadfe88a48h501p
#
sub r (@args) {
   @args = @ARGV unless @args;
   my $i = 0;
   my $n = 0;
   for my $cf (@ARGV) {
      if (my $errors = validate_cf($cf)) {
         say "$i not ok - " . join(', ', $errors->@*);
         ++$n;
      }
      else {
         say "$i ok - $cf";
      }
      ++$i;
   } ## end for my $cf (@ARGV)
   return $n ? 1 : 0;
} ## end sub r

exit r(@ARGV) unless caller();    # modulino

# PRIVATE interface

sub _decode_and_validate ($cf, %options) {
   my $data = $options{data} // undef;

   my $collect_all_errors = $options{all_errors} // 1;
   my @errors;
   my $callback = sub ($msg) {
      push @errors, $msg;
      return $collect_all_errors;
   };

   my $r = _validate_cf($cf, $data, $callback);
   $r->{errors} = \@errors;
   return $r;
}

sub _validate_cf ($cf, $data, $cb) {
   state $consonant = qr{(?imxs:[BCDFGHJKLMNPQRSTVWXYZ])};
   state $vowel     = qr{(?imxs:[AEIOU])};
   state $namish    = qr{(?imxs:
         $consonant  $consonant  $consonant  # includes CCX, CXX, XXX
      |  $consonant  $consonant  $vowel
      |  $consonant  $vowel      $vowel
      |  $consonant  $vowel      X
      |  $vowel      $vowel      $vowel
      |  $vowel      $vowel      X
      |  $vowel      X           X
   )};
   state $digitish = qr{(?imxs:[0-9LMNPQRSTUV])};

   if (length($cf) != 16) {
      $cb->('invalid length');
      return {};
   }

   $cf = uc($cf);
   my %portions = (
      surname  => substr($cf, 0,  3),
      name     => substr($cf, 3,  3),
      date     => substr($cf, 6,  5),
      place    => substr($cf, 11, 4),



( run in 1.087 second using v1.01-cache-2.11-cpan-524268b4103 )