xref: /openbsd-src/gnu/usr.bin/perl/cpan/CPAN-Meta/lib/CPAN/Meta/Validator.pm (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1use 5.006;
2use strict;
3use warnings;
4package CPAN::Meta::Validator;
5our $VERSION = '2.140640'; # VERSION
6
7# =head1 SYNOPSIS
8#
9#   my $struct = decode_json_file('META.json');
10#
11#   my $cmv = CPAN::Meta::Validator->new( $struct );
12#
13#   unless ( $cmv->is_valid ) {
14#     my $msg = "Invalid META structure.  Errors found:\n";
15#     $msg .= join( "\n", $cmv->errors );
16#     die $msg;
17#   }
18#
19# =head1 DESCRIPTION
20#
21# This module validates a CPAN Meta structure against the version of the
22# the specification claimed in the C<meta-spec> field of the structure.
23#
24# =cut
25
26#--------------------------------------------------------------------------#
27# This code copied and adapted from Test::CPAN::Meta
28# by Barbie, <barbie@cpan.org> for Miss Barbell Productions,
29# L<http://www.missbarbell.co.uk>
30#--------------------------------------------------------------------------#
31
32#--------------------------------------------------------------------------#
33# Specification Definitions
34#--------------------------------------------------------------------------#
35
36my %known_specs = (
37    '1.4' => 'http://module-build.sourceforge.net/META-spec-v1.4.html',
38    '1.3' => 'http://module-build.sourceforge.net/META-spec-v1.3.html',
39    '1.2' => 'http://module-build.sourceforge.net/META-spec-v1.2.html',
40    '1.1' => 'http://module-build.sourceforge.net/META-spec-v1.1.html',
41    '1.0' => 'http://module-build.sourceforge.net/META-spec-v1.0.html'
42);
43my %known_urls = map {$known_specs{$_} => $_} keys %known_specs;
44
45my $module_map1 = { 'map' => { ':key' => { name => \&module, value => \&exversion } } };
46
47my $module_map2 = { 'map' => { ':key' => { name => \&module, value => \&version   } } };
48
49my $no_index_2 = {
50    'map'       => { file       => { list => { value => \&string } },
51                     directory  => { list => { value => \&string } },
52                     'package'  => { list => { value => \&string } },
53                     namespace  => { list => { value => \&string } },
54                    ':key'      => { name => \&custom_2, value => \&anything },
55    }
56};
57
58my $no_index_1_3 = {
59    'map'       => { file       => { list => { value => \&string } },
60                     directory  => { list => { value => \&string } },
61                     'package'  => { list => { value => \&string } },
62                     namespace  => { list => { value => \&string } },
63                     ':key'     => { name => \&string, value => \&anything },
64    }
65};
66
67my $no_index_1_2 = {
68    'map'       => { file       => { list => { value => \&string } },
69                     dir        => { list => { value => \&string } },
70                     'package'  => { list => { value => \&string } },
71                     namespace  => { list => { value => \&string } },
72                     ':key'     => { name => \&string, value => \&anything },
73    }
74};
75
76my $no_index_1_1 = {
77    'map'       => { ':key'     => { name => \&string, list => { value => \&string } },
78    }
79};
80
81my $prereq_map = {
82  map => {
83    ':key' => {
84      name => \&phase,
85      'map' => {
86        ':key'  => {
87          name => \&relation,
88          %$module_map1,
89        },
90      },
91    }
92  },
93};
94
95my %definitions = (
96  '2' => {
97    # REQUIRED
98    'abstract'            => { mandatory => 1, value => \&string  },
99    'author'              => { mandatory => 1, list => { value => \&string } },
100    'dynamic_config'      => { mandatory => 1, value => \&boolean },
101    'generated_by'        => { mandatory => 1, value => \&string  },
102    'license'             => { mandatory => 1, list => { value => \&license } },
103    'meta-spec' => {
104      mandatory => 1,
105      'map' => {
106        version => { mandatory => 1, value => \&version},
107        url     => { value => \&url },
108        ':key' => { name => \&custom_2, value => \&anything },
109      }
110    },
111    'name'                => { mandatory => 1, value => \&string  },
112    'release_status'      => { mandatory => 1, value => \&release_status },
113    'version'             => { mandatory => 1, value => \&version },
114
115    # OPTIONAL
116    'description' => { value => \&string },
117    'keywords'    => { list => { value => \&string } },
118    'no_index'    => $no_index_2,
119    'optional_features'   => {
120      'map'       => {
121        ':key'  => {
122          name => \&string,
123          'map'   => {
124            description        => { value => \&string },
125            prereqs => $prereq_map,
126            ':key' => { name => \&custom_2, value => \&anything },
127          }
128        }
129      }
130    },
131    'prereqs' => $prereq_map,
132    'provides'    => {
133      'map'       => {
134        ':key' => {
135          name  => \&module,
136          'map' => {
137            file    => { mandatory => 1, value => \&file },
138            version => { value => \&version },
139            ':key' => { name => \&custom_2, value => \&anything },
140          }
141        }
142      }
143    },
144    'resources'   => {
145      'map'       => {
146        license    => { list => { value => \&url } },
147        homepage   => { value => \&url },
148        bugtracker => {
149          'map' => {
150            web => { value => \&url },
151            mailto => { value => \&string},
152            ':key' => { name => \&custom_2, value => \&anything },
153          }
154        },
155        repository => {
156          'map' => {
157            web => { value => \&url },
158            url => { value => \&url },
159            type => { value => \&string },
160            ':key' => { name => \&custom_2, value => \&anything },
161          }
162        },
163        ':key'     => { value => \&string, name => \&custom_2 },
164      }
165    },
166
167    # CUSTOM -- additional user defined key/value pairs
168    # note we can only validate the key name, as the structure is user defined
169    ':key'        => { name => \&custom_2, value => \&anything },
170  },
171
172'1.4' => {
173  'meta-spec'           => {
174    mandatory => 1,
175    'map' => {
176      version => { mandatory => 1, value => \&version},
177      url     => { mandatory => 1, value => \&urlspec },
178      ':key'  => { name => \&string, value => \&anything },
179    },
180  },
181
182  'name'                => { mandatory => 1, value => \&string  },
183  'version'             => { mandatory => 1, value => \&version },
184  'abstract'            => { mandatory => 1, value => \&string  },
185  'author'              => { mandatory => 1, list  => { value => \&string } },
186  'license'             => { mandatory => 1, value => \&license },
187  'generated_by'        => { mandatory => 1, value => \&string  },
188
189  'distribution_type'   => { value => \&string  },
190  'dynamic_config'      => { value => \&boolean },
191
192  'requires'            => $module_map1,
193  'recommends'          => $module_map1,
194  'build_requires'      => $module_map1,
195  'configure_requires'  => $module_map1,
196  'conflicts'           => $module_map2,
197
198  'optional_features'   => {
199    'map'       => {
200        ':key'  => { name => \&string,
201            'map'   => { description        => { value => \&string },
202                         requires           => $module_map1,
203                         recommends         => $module_map1,
204                         build_requires     => $module_map1,
205                         conflicts          => $module_map2,
206                         ':key'  => { name => \&string, value => \&anything },
207            }
208        }
209     }
210  },
211
212  'provides'    => {
213    'map'       => {
214      ':key' => { name  => \&module,
215        'map' => {
216          file    => { mandatory => 1, value => \&file },
217          version => { value => \&version },
218          ':key'  => { name => \&string, value => \&anything },
219        }
220      }
221    }
222  },
223
224  'no_index'    => $no_index_1_3,
225  'private'     => $no_index_1_3,
226
227  'keywords'    => { list => { value => \&string } },
228
229  'resources'   => {
230    'map'       => { license    => { value => \&url },
231                     homepage   => { value => \&url },
232                     bugtracker => { value => \&url },
233                     repository => { value => \&url },
234                     ':key'     => { value => \&string, name => \&custom_1 },
235    }
236  },
237
238  # additional user defined key/value pairs
239  # note we can only validate the key name, as the structure is user defined
240  ':key'        => { name => \&string, value => \&anything },
241},
242
243'1.3' => {
244  'meta-spec'           => {
245    mandatory => 1,
246    'map' => {
247      version => { mandatory => 1, value => \&version},
248      url     => { mandatory => 1, value => \&urlspec },
249      ':key'  => { name => \&string, value => \&anything },
250    },
251  },
252
253  'name'                => { mandatory => 1, value => \&string  },
254  'version'             => { mandatory => 1, value => \&version },
255  'abstract'            => { mandatory => 1, value => \&string  },
256  'author'              => { mandatory => 1, list  => { value => \&string } },
257  'license'             => { mandatory => 1, value => \&license },
258  'generated_by'        => { mandatory => 1, value => \&string  },
259
260  'distribution_type'   => { value => \&string  },
261  'dynamic_config'      => { value => \&boolean },
262
263  'requires'            => $module_map1,
264  'recommends'          => $module_map1,
265  'build_requires'      => $module_map1,
266  'conflicts'           => $module_map2,
267
268  'optional_features'   => {
269    'map'       => {
270        ':key'  => { name => \&string,
271            'map'   => { description        => { value => \&string },
272                         requires           => $module_map1,
273                         recommends         => $module_map1,
274                         build_requires     => $module_map1,
275                         conflicts          => $module_map2,
276                         ':key'  => { name => \&string, value => \&anything },
277            }
278        }
279     }
280  },
281
282  'provides'    => {
283    'map'       => {
284      ':key' => { name  => \&module,
285        'map' => {
286          file    => { mandatory => 1, value => \&file },
287          version => { value => \&version },
288          ':key'  => { name => \&string, value => \&anything },
289        }
290      }
291    }
292  },
293
294
295  'no_index'    => $no_index_1_3,
296  'private'     => $no_index_1_3,
297
298  'keywords'    => { list => { value => \&string } },
299
300  'resources'   => {
301    'map'       => { license    => { value => \&url },
302                     homepage   => { value => \&url },
303                     bugtracker => { value => \&url },
304                     repository => { value => \&url },
305                     ':key'     => { value => \&string, name => \&custom_1 },
306    }
307  },
308
309  # additional user defined key/value pairs
310  # note we can only validate the key name, as the structure is user defined
311  ':key'        => { name => \&string, value => \&anything },
312},
313
314# v1.2 is misleading, it seems to assume that a number of fields where created
315# within v1.1, when they were created within v1.2. This may have been an
316# original mistake, and that a v1.1 was retro fitted into the timeline, when
317# v1.2 was originally slated as v1.1. But I could be wrong ;)
318'1.2' => {
319  'meta-spec'           => {
320    mandatory => 1,
321    'map' => {
322      version => { mandatory => 1, value => \&version},
323      url     => { mandatory => 1, value => \&urlspec },
324      ':key'  => { name => \&string, value => \&anything },
325    },
326  },
327
328
329  'name'                => { mandatory => 1, value => \&string  },
330  'version'             => { mandatory => 1, value => \&version },
331  'license'             => { mandatory => 1, value => \&license },
332  'generated_by'        => { mandatory => 1, value => \&string  },
333  'author'              => { mandatory => 1, list => { value => \&string } },
334  'abstract'            => { mandatory => 1, value => \&string  },
335
336  'distribution_type'   => { value => \&string  },
337  'dynamic_config'      => { value => \&boolean },
338
339  'keywords'            => { list => { value => \&string } },
340
341  'private'             => $no_index_1_2,
342  '$no_index'           => $no_index_1_2,
343
344  'requires'            => $module_map1,
345  'recommends'          => $module_map1,
346  'build_requires'      => $module_map1,
347  'conflicts'           => $module_map2,
348
349  'optional_features'   => {
350    'map'       => {
351        ':key'  => { name => \&string,
352            'map'   => { description        => { value => \&string },
353                         requires           => $module_map1,
354                         recommends         => $module_map1,
355                         build_requires     => $module_map1,
356                         conflicts          => $module_map2,
357                         ':key'  => { name => \&string, value => \&anything },
358            }
359        }
360     }
361  },
362
363  'provides'    => {
364    'map'       => {
365      ':key' => { name  => \&module,
366        'map' => {
367          file    => { mandatory => 1, value => \&file },
368          version => { value => \&version },
369          ':key'  => { name => \&string, value => \&anything },
370        }
371      }
372    }
373  },
374
375  'resources'   => {
376    'map'       => { license    => { value => \&url },
377                     homepage   => { value => \&url },
378                     bugtracker => { value => \&url },
379                     repository => { value => \&url },
380                     ':key'     => { value => \&string, name => \&custom_1 },
381    }
382  },
383
384  # additional user defined key/value pairs
385  # note we can only validate the key name, as the structure is user defined
386  ':key'        => { name => \&string, value => \&anything },
387},
388
389# note that the 1.1 spec only specifies 'version' as mandatory
390'1.1' => {
391  'name'                => { value => \&string  },
392  'version'             => { mandatory => 1, value => \&version },
393  'license'             => { value => \&license },
394  'generated_by'        => { value => \&string  },
395
396  'license_uri'         => { value => \&url },
397  'distribution_type'   => { value => \&string  },
398  'dynamic_config'      => { value => \&boolean },
399
400  'private'             => $no_index_1_1,
401
402  'requires'            => $module_map1,
403  'recommends'          => $module_map1,
404  'build_requires'      => $module_map1,
405  'conflicts'           => $module_map2,
406
407  # additional user defined key/value pairs
408  # note we can only validate the key name, as the structure is user defined
409  ':key'        => { name => \&string, value => \&anything },
410},
411
412# note that the 1.0 spec doesn't specify optional or mandatory fields
413# but we will treat version as mandatory since otherwise META 1.0 is
414# completely arbitrary and pointless
415'1.0' => {
416  'name'                => { value => \&string  },
417  'version'             => { mandatory => 1, value => \&version },
418  'license'             => { value => \&license },
419  'generated_by'        => { value => \&string  },
420
421  'license_uri'         => { value => \&url },
422  'distribution_type'   => { value => \&string  },
423  'dynamic_config'      => { value => \&boolean },
424
425  'requires'            => $module_map1,
426  'recommends'          => $module_map1,
427  'build_requires'      => $module_map1,
428  'conflicts'           => $module_map2,
429
430  # additional user defined key/value pairs
431  # note we can only validate the key name, as the structure is user defined
432  ':key'        => { name => \&string, value => \&anything },
433},
434);
435
436#--------------------------------------------------------------------------#
437# Code
438#--------------------------------------------------------------------------#
439
440# =method new
441#
442#   my $cmv = CPAN::Meta::Validator->new( $struct )
443#
444# The constructor must be passed a metadata structure.
445#
446# =cut
447
448sub new {
449  my ($class,$data) = @_;
450
451  # create an attributes hash
452  my $self = {
453    'data'    => $data,
454    'spec'    => eval { $data->{'meta-spec'}{'version'} } || "1.0",
455    'errors'  => undef,
456  };
457
458  # create the object
459  return bless $self, $class;
460}
461
462# =method is_valid
463#
464#   if ( $cmv->is_valid ) {
465#     ...
466#   }
467#
468# Returns a boolean value indicating whether the metadata provided
469# is valid.
470#
471# =cut
472
473sub is_valid {
474    my $self = shift;
475    my $data = $self->{data};
476    my $spec_version = $self->{spec};
477    $self->check_map($definitions{$spec_version},$data);
478    return ! $self->errors;
479}
480
481# =method errors
482#
483#   warn( join "\n", $cmv->errors );
484#
485# Returns a list of errors seen during validation.
486#
487# =cut
488
489sub errors {
490    my $self = shift;
491    return ()   unless(defined $self->{errors});
492    return @{$self->{errors}};
493}
494
495# =begin :internals
496#
497# =head2 Check Methods
498#
499# =over
500#
501# =item *
502#
503# check_map($spec,$data)
504#
505# Checks whether a map (or hash) part of the data structure conforms to the
506# appropriate specification definition.
507#
508# =item *
509#
510# check_list($spec,$data)
511#
512# Checks whether a list (or array) part of the data structure conforms to
513# the appropriate specification definition.
514#
515# =item *
516#
517# =back
518#
519# =cut
520
521my $spec_error = "Missing validation action in specification. "
522  . "Must be one of 'map', 'list', or 'value'";
523
524sub check_map {
525    my ($self,$spec,$data) = @_;
526
527    if(ref($spec) ne 'HASH') {
528        $self->_error( "Unknown META specification, cannot validate." );
529        return;
530    }
531
532    if(ref($data) ne 'HASH') {
533        $self->_error( "Expected a map structure from string or file." );
534        return;
535    }
536
537    for my $key (keys %$spec) {
538        next    unless($spec->{$key}->{mandatory});
539        next    if(defined $data->{$key});
540        push @{$self->{stack}}, $key;
541        $self->_error( "Missing mandatory field, '$key'" );
542        pop @{$self->{stack}};
543    }
544
545    for my $key (keys %$data) {
546        push @{$self->{stack}}, $key;
547        if($spec->{$key}) {
548            if($spec->{$key}{value}) {
549                $spec->{$key}{value}->($self,$key,$data->{$key});
550            } elsif($spec->{$key}{'map'}) {
551                $self->check_map($spec->{$key}{'map'},$data->{$key});
552            } elsif($spec->{$key}{'list'}) {
553                $self->check_list($spec->{$key}{'list'},$data->{$key});
554            } else {
555                $self->_error( "$spec_error for '$key'" );
556            }
557
558        } elsif ($spec->{':key'}) {
559            $spec->{':key'}{name}->($self,$key,$key);
560            if($spec->{':key'}{value}) {
561                $spec->{':key'}{value}->($self,$key,$data->{$key});
562            } elsif($spec->{':key'}{'map'}) {
563                $self->check_map($spec->{':key'}{'map'},$data->{$key});
564            } elsif($spec->{':key'}{'list'}) {
565                $self->check_list($spec->{':key'}{'list'},$data->{$key});
566            } else {
567                $self->_error( "$spec_error for ':key'" );
568            }
569
570
571        } else {
572            $self->_error( "Unknown key, '$key', found in map structure" );
573        }
574        pop @{$self->{stack}};
575    }
576}
577
578sub check_list {
579    my ($self,$spec,$data) = @_;
580
581    if(ref($data) ne 'ARRAY') {
582        $self->_error( "Expected a list structure" );
583        return;
584    }
585
586    if(defined $spec->{mandatory}) {
587        if(!defined $data->[0]) {
588            $self->_error( "Missing entries from mandatory list" );
589        }
590    }
591
592    for my $value (@$data) {
593        push @{$self->{stack}}, $value || "<undef>";
594        if(defined $spec->{value}) {
595            $spec->{value}->($self,'list',$value);
596        } elsif(defined $spec->{'map'}) {
597            $self->check_map($spec->{'map'},$value);
598        } elsif(defined $spec->{'list'}) {
599            $self->check_list($spec->{'list'},$value);
600        } elsif ($spec->{':key'}) {
601            $self->check_map($spec,$value);
602        } else {
603          $self->_error( "$spec_error associated with '$self->{stack}[-2]'" );
604        }
605        pop @{$self->{stack}};
606    }
607}
608
609# =head2 Validator Methods
610#
611# =over
612#
613# =item *
614#
615# header($self,$key,$value)
616#
617# Validates that the header is valid.
618#
619# Note: No longer used as we now read the data structure, not the file.
620#
621# =item *
622#
623# url($self,$key,$value)
624#
625# Validates that a given value is in an acceptable URL format
626#
627# =item *
628#
629# urlspec($self,$key,$value)
630#
631# Validates that the URL to a META specification is a known one.
632#
633# =item *
634#
635# string_or_undef($self,$key,$value)
636#
637# Validates that the value is either a string or an undef value. Bit of a
638# catchall function for parts of the data structure that are completely user
639# defined.
640#
641# =item *
642#
643# string($self,$key,$value)
644#
645# Validates that a string exists for the given key.
646#
647# =item *
648#
649# file($self,$key,$value)
650#
651# Validate that a file is passed for the given key. This may be made more
652# thorough in the future. For now it acts like \&string.
653#
654# =item *
655#
656# exversion($self,$key,$value)
657#
658# Validates a list of versions, e.g. '<= 5, >=2, ==3, !=4, >1, <6, 0'.
659#
660# =item *
661#
662# version($self,$key,$value)
663#
664# Validates a single version string. Versions of the type '5.8.8' and '0.00_00'
665# are both valid. A leading 'v' like 'v1.2.3' is also valid.
666#
667# =item *
668#
669# boolean($self,$key,$value)
670#
671# Validates for a boolean value. Currently these values are '1', '0', 'true',
672# 'false', however the latter 2 may be removed.
673#
674# =item *
675#
676# license($self,$key,$value)
677#
678# Validates that a value is given for the license. Returns 1 if an known license
679# type, or 2 if a value is given but the license type is not a recommended one.
680#
681# =item *
682#
683# custom_1($self,$key,$value)
684#
685# Validates that the given key is in CamelCase, to indicate a user defined
686# keyword and only has characters in the class [-_a-zA-Z].  In version 1.X
687# of the spec, this was only explicitly stated for 'resources'.
688#
689# =item *
690#
691# custom_2($self,$key,$value)
692#
693# Validates that the given key begins with 'x_' or 'X_', to indicate a user
694# defined keyword and only has characters in the class [-_a-zA-Z]
695#
696# =item *
697#
698# identifier($self,$key,$value)
699#
700# Validates that key is in an acceptable format for the META specification,
701# for an identifier, i.e. any that matches the regular expression
702# qr/[a-z][a-z_]/i.
703#
704# =item *
705#
706# module($self,$key,$value)
707#
708# Validates that a given key is in an acceptable module name format, e.g.
709# 'Test::CPAN::Meta::Version'.
710#
711# =back
712#
713# =end :internals
714#
715# =cut
716
717sub header {
718    my ($self,$key,$value) = @_;
719    if(defined $value) {
720        return 1    if($value && $value =~ /^--- #YAML:1.0/);
721    }
722    $self->_error( "file does not have a valid YAML header." );
723    return 0;
724}
725
726sub release_status {
727  my ($self,$key,$value) = @_;
728  if(defined $value) {
729    my $version = $self->{data}{version} || '';
730    if ( $version =~ /_/ ) {
731      return 1 if ( $value =~ /\A(?:testing|unstable)\z/ );
732      $self->_error( "'$value' for '$key' is invalid for version '$version'" );
733    }
734    else {
735      return 1 if ( $value =~ /\A(?:stable|testing|unstable)\z/ );
736      $self->_error( "'$value' for '$key' is invalid" );
737    }
738  }
739  else {
740    $self->_error( "'$key' is not defined" );
741  }
742  return 0;
743}
744
745# _uri_split taken from URI::Split by Gisle Aas, Copyright 2003
746sub _uri_split {
747     return $_[0] =~ m,(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?,;
748}
749
750sub url {
751    my ($self,$key,$value) = @_;
752    if(defined $value) {
753      my ($scheme, $auth, $path, $query, $frag) = _uri_split($value);
754      unless ( defined $scheme && length $scheme ) {
755        $self->_error( "'$value' for '$key' does not have a URL scheme" );
756        return 0;
757      }
758      unless ( defined $auth && length $auth ) {
759        $self->_error( "'$value' for '$key' does not have a URL authority" );
760        return 0;
761      }
762      return 1;
763    }
764    $value ||= '';
765    $self->_error( "'$value' for '$key' is not a valid URL." );
766    return 0;
767}
768
769sub urlspec {
770    my ($self,$key,$value) = @_;
771    if(defined $value) {
772        return 1    if($value && $known_specs{$self->{spec}} eq $value);
773        if($value && $known_urls{$value}) {
774            $self->_error( 'META specification URL does not match version' );
775            return 0;
776        }
777    }
778    $self->_error( 'Unknown META specification' );
779    return 0;
780}
781
782sub anything { return 1 }
783
784sub string {
785    my ($self,$key,$value) = @_;
786    if(defined $value) {
787        return 1    if($value || $value =~ /^0$/);
788    }
789    $self->_error( "value is an undefined string" );
790    return 0;
791}
792
793sub string_or_undef {
794    my ($self,$key,$value) = @_;
795    return 1    unless(defined $value);
796    return 1    if($value || $value =~ /^0$/);
797    $self->_error( "No string defined for '$key'" );
798    return 0;
799}
800
801sub file {
802    my ($self,$key,$value) = @_;
803    return 1    if(defined $value);
804    $self->_error( "No file defined for '$key'" );
805    return 0;
806}
807
808sub exversion {
809    my ($self,$key,$value) = @_;
810    if(defined $value && ($value || $value =~ /0/)) {
811        my $pass = 1;
812        for(split(",",$value)) { $self->version($key,$_) or ($pass = 0); }
813        return $pass;
814    }
815    $value = '<undef>'  unless(defined $value);
816    $self->_error( "'$value' for '$key' is not a valid version." );
817    return 0;
818}
819
820sub version {
821    my ($self,$key,$value) = @_;
822    if(defined $value) {
823        return 0    unless($value || $value =~ /0/);
824        return 1    if($value =~ /^\s*((<|<=|>=|>|!=|==)\s*)?v?\d+((\.\d+((_|\.)\d+)?)?)/);
825    } else {
826        $value = '<undef>';
827    }
828    $self->_error( "'$value' for '$key' is not a valid version." );
829    return 0;
830}
831
832sub boolean {
833    my ($self,$key,$value) = @_;
834    if(defined $value) {
835        return 1    if($value =~ /^(0|1|true|false)$/);
836    } else {
837        $value = '<undef>';
838    }
839    $self->_error( "'$value' for '$key' is not a boolean value." );
840    return 0;
841}
842
843my %v1_licenses = (
844    'perl'         => 'http://dev.perl.org/licenses/',
845    'gpl'          => 'http://www.opensource.org/licenses/gpl-license.php',
846    'apache'       => 'http://apache.org/licenses/LICENSE-2.0',
847    'artistic'     => 'http://opensource.org/licenses/artistic-license.php',
848    'artistic_2'   => 'http://opensource.org/licenses/artistic-license-2.0.php',
849    'lgpl'         => 'http://www.opensource.org/licenses/lgpl-license.php',
850    'bsd'          => 'http://www.opensource.org/licenses/bsd-license.php',
851    'gpl'          => 'http://www.opensource.org/licenses/gpl-license.php',
852    'mit'          => 'http://opensource.org/licenses/mit-license.php',
853    'mozilla'      => 'http://opensource.org/licenses/mozilla1.1.php',
854    'open_source'  => undef,
855    'unrestricted' => undef,
856    'restrictive'  => undef,
857    'unknown'      => undef,
858);
859
860my %v2_licenses = map { $_ => 1 } qw(
861  agpl_3
862  apache_1_1
863  apache_2_0
864  artistic_1
865  artistic_2
866  bsd
867  freebsd
868  gfdl_1_2
869  gfdl_1_3
870  gpl_1
871  gpl_2
872  gpl_3
873  lgpl_2_1
874  lgpl_3_0
875  mit
876  mozilla_1_0
877  mozilla_1_1
878  openssl
879  perl_5
880  qpl_1_0
881  ssleay
882  sun
883  zlib
884  open_source
885  restricted
886  unrestricted
887  unknown
888);
889
890sub license {
891    my ($self,$key,$value) = @_;
892    my $licenses = $self->{spec} < 2 ? \%v1_licenses : \%v2_licenses;
893    if(defined $value) {
894        return 1    if($value && exists $licenses->{$value});
895    } else {
896        $value = '<undef>';
897    }
898    $self->_error( "License '$value' is invalid" );
899    return 0;
900}
901
902sub custom_1 {
903    my ($self,$key) = @_;
904    if(defined $key) {
905        # a valid user defined key should be alphabetic
906        # and contain at least one capital case letter.
907        return 1    if($key && $key =~ /^[_a-z]+$/i && $key =~ /[A-Z]/);
908    } else {
909        $key = '<undef>';
910    }
911    $self->_error( "Custom resource '$key' must be in CamelCase." );
912    return 0;
913}
914
915sub custom_2 {
916    my ($self,$key) = @_;
917    if(defined $key) {
918        return 1    if($key && $key =~ /^x_/i);  # user defined
919    } else {
920        $key = '<undef>';
921    }
922    $self->_error( "Custom key '$key' must begin with 'x_' or 'X_'." );
923    return 0;
924}
925
926sub identifier {
927    my ($self,$key) = @_;
928    if(defined $key) {
929        return 1    if($key && $key =~ /^([a-z][_a-z]+)$/i);    # spec 2.0 defined
930    } else {
931        $key = '<undef>';
932    }
933    $self->_error( "Key '$key' is not a legal identifier." );
934    return 0;
935}
936
937sub module {
938    my ($self,$key) = @_;
939    if(defined $key) {
940        return 1    if($key && $key =~ /^[A-Za-z0-9_]+(::[A-Za-z0-9_]+)*$/);
941    } else {
942        $key = '<undef>';
943    }
944    $self->_error( "Key '$key' is not a legal module name." );
945    return 0;
946}
947
948my @valid_phases = qw/ configure build test runtime develop /;
949sub phase {
950    my ($self,$key) = @_;
951    if(defined $key) {
952        return 1 if( length $key && grep { $key eq $_ } @valid_phases );
953        return 1 if $key =~ /x_/i;
954    } else {
955        $key = '<undef>';
956    }
957    $self->_error( "Key '$key' is not a legal phase." );
958    return 0;
959}
960
961my @valid_relations = qw/ requires recommends suggests conflicts /;
962sub relation {
963    my ($self,$key) = @_;
964    if(defined $key) {
965        return 1 if( length $key && grep { $key eq $_ } @valid_relations );
966        return 1 if $key =~ /x_/i;
967    } else {
968        $key = '<undef>';
969    }
970    $self->_error( "Key '$key' is not a legal prereq relationship." );
971    return 0;
972}
973
974sub _error {
975    my $self = shift;
976    my $mess = shift;
977
978    $mess .= ' ('.join(' -> ',@{$self->{stack}}).')'  if($self->{stack});
979    $mess .= " [Validation: $self->{spec}]";
980
981    push @{$self->{errors}}, $mess;
982}
983
9841;
985
986# ABSTRACT: validate CPAN distribution metadata structures
987
988__END__
989
990=pod
991
992=encoding UTF-8
993
994=head1 NAME
995
996CPAN::Meta::Validator - validate CPAN distribution metadata structures
997
998=head1 VERSION
999
1000version 2.140640
1001
1002=head1 SYNOPSIS
1003
1004  my $struct = decode_json_file('META.json');
1005
1006  my $cmv = CPAN::Meta::Validator->new( $struct );
1007
1008  unless ( $cmv->is_valid ) {
1009    my $msg = "Invalid META structure.  Errors found:\n";
1010    $msg .= join( "\n", $cmv->errors );
1011    die $msg;
1012  }
1013
1014=head1 DESCRIPTION
1015
1016This module validates a CPAN Meta structure against the version of the
1017the specification claimed in the C<meta-spec> field of the structure.
1018
1019=head1 METHODS
1020
1021=head2 new
1022
1023  my $cmv = CPAN::Meta::Validator->new( $struct )
1024
1025The constructor must be passed a metadata structure.
1026
1027=head2 is_valid
1028
1029  if ( $cmv->is_valid ) {
1030    ...
1031  }
1032
1033Returns a boolean value indicating whether the metadata provided
1034is valid.
1035
1036=head2 errors
1037
1038  warn( join "\n", $cmv->errors );
1039
1040Returns a list of errors seen during validation.
1041
1042=begin :internals
1043
1044=head2 Check Methods
1045
1046=over
1047
1048=item *
1049
1050check_map($spec,$data)
1051
1052Checks whether a map (or hash) part of the data structure conforms to the
1053appropriate specification definition.
1054
1055=item *
1056
1057check_list($spec,$data)
1058
1059Checks whether a list (or array) part of the data structure conforms to
1060the appropriate specification definition.
1061
1062=item *
1063
1064=back
1065
1066=head2 Validator Methods
1067
1068=over
1069
1070=item *
1071
1072header($self,$key,$value)
1073
1074Validates that the header is valid.
1075
1076Note: No longer used as we now read the data structure, not the file.
1077
1078=item *
1079
1080url($self,$key,$value)
1081
1082Validates that a given value is in an acceptable URL format
1083
1084=item *
1085
1086urlspec($self,$key,$value)
1087
1088Validates that the URL to a META specification is a known one.
1089
1090=item *
1091
1092string_or_undef($self,$key,$value)
1093
1094Validates that the value is either a string or an undef value. Bit of a
1095catchall function for parts of the data structure that are completely user
1096defined.
1097
1098=item *
1099
1100string($self,$key,$value)
1101
1102Validates that a string exists for the given key.
1103
1104=item *
1105
1106file($self,$key,$value)
1107
1108Validate that a file is passed for the given key. This may be made more
1109thorough in the future. For now it acts like \&string.
1110
1111=item *
1112
1113exversion($self,$key,$value)
1114
1115Validates a list of versions, e.g. '<= 5, >=2, ==3, !=4, >1, <6, 0'.
1116
1117=item *
1118
1119version($self,$key,$value)
1120
1121Validates a single version string. Versions of the type '5.8.8' and '0.00_00'
1122are both valid. A leading 'v' like 'v1.2.3' is also valid.
1123
1124=item *
1125
1126boolean($self,$key,$value)
1127
1128Validates for a boolean value. Currently these values are '1', '0', 'true',
1129'false', however the latter 2 may be removed.
1130
1131=item *
1132
1133license($self,$key,$value)
1134
1135Validates that a value is given for the license. Returns 1 if an known license
1136type, or 2 if a value is given but the license type is not a recommended one.
1137
1138=item *
1139
1140custom_1($self,$key,$value)
1141
1142Validates that the given key is in CamelCase, to indicate a user defined
1143keyword and only has characters in the class [-_a-zA-Z].  In version 1.X
1144of the spec, this was only explicitly stated for 'resources'.
1145
1146=item *
1147
1148custom_2($self,$key,$value)
1149
1150Validates that the given key begins with 'x_' or 'X_', to indicate a user
1151defined keyword and only has characters in the class [-_a-zA-Z]
1152
1153=item *
1154
1155identifier($self,$key,$value)
1156
1157Validates that key is in an acceptable format for the META specification,
1158for an identifier, i.e. any that matches the regular expression
1159qr/[a-z][a-z_]/i.
1160
1161=item *
1162
1163module($self,$key,$value)
1164
1165Validates that a given key is in an acceptable module name format, e.g.
1166'Test::CPAN::Meta::Version'.
1167
1168=back
1169
1170=end :internals
1171
1172=for Pod::Coverage anything boolean check_list custom_1 custom_2 exversion file
1173identifier license module phase relation release_status string string_or_undef
1174url urlspec version header check_map
1175
1176=head1 BUGS
1177
1178Please report any bugs or feature using the CPAN Request Tracker.
1179Bugs can be submitted through the web interface at
1180L<http://rt.cpan.org/Dist/Display.html?Queue=CPAN-Meta>
1181
1182When submitting a bug or request, please include a test-file or a patch to an
1183existing test-file that illustrates the bug or desired feature.
1184
1185=head1 AUTHORS
1186
1187=over 4
1188
1189=item *
1190
1191David Golden <dagolden@cpan.org>
1192
1193=item *
1194
1195Ricardo Signes <rjbs@cpan.org>
1196
1197=back
1198
1199=head1 COPYRIGHT AND LICENSE
1200
1201This software is copyright (c) 2010 by David Golden and Ricardo Signes.
1202
1203This is free software; you can redistribute it and/or modify it under
1204the same terms as the Perl 5 programming language system itself.
1205
1206=cut
1207