diff --git a/cpanfile b/cpanfile index fc40a23..ddd1556 100644 --- a/cpanfile +++ b/cpanfile @@ -26,5 +26,6 @@ requires 'URI::Split'; on 'test' => sub { requires 'Test::More', '0.98'; requires 'Test::Requires'; + requires 'Test::Output'; }; diff --git a/lib/JSV.pm b/lib/JSV.pm index 1ff89de..4a25310 100644 --- a/lib/JSV.pm +++ b/lib/JSV.pm @@ -19,7 +19,7 @@ JSV - A perl implementation of JSON Schema (draft-04) validator =head1 DESCRIPTION -See e.g. L +See L (module) and L (command line script). =head1 LICENSE diff --git a/script/jsv b/script/jsv new file mode 100755 index 0000000..bd4d32c --- /dev/null +++ b/script/jsv @@ -0,0 +1,119 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use Getopt::Long; +use Pod::Usage; +use JSV::Validator; +use JSON; + +my %opt; +GetOptions( + \%opt, + 'color|c!', + 'help|h|?', + 'schema|s=s', + 'quiet|q!', +) or pod2usage( -exitval => 1, indent => 2 ); +pod2usage( -exitval => 0, indent => 2 ) if $opt{help}; + +$opt{color} = -t STDOUT unless defined $opt{color}; ## no critic + +sub read_json { + my $file = shift; + my $fh; + if ($file eq '-') { + $fh = *STDIN; + } elsif( !open($fh, "<", $file) ) { + print STDERR "failed to open $file\n"; + exit 3; + } + local $/; + my $json = eval { JSON->new->decode(<$fh>) } or do { + print STDERR "failed to parse $file\n"; + exit 2; + }; + $json; +} + +JSV::Validator->load_environments("draft4"); +my $jsv = JSV::Validator->new( environment => "draft4" ); + +my $schema = $opt{schema} ? read_json($opt{schema}) : { }; + +my $error=0; +foreach my $file ( @ARGV ? @ARGV : '-' ) { + my $instance = read_json($file); + my $result = $jsv->validate($schema, $instance); + unless ($opt{quiet}) { + my $valid = $result ? 'valid ' : 'invalid'; + if ($opt{color}) { + $valid = ($result ? "\e[0;32m" : "\e[1;31m") . $valid . "\e[0m"; + } + print "$valid $file\n"; + } + $error = 1 unless $result; +} + +exit $error; + +=head1 NAME + +jsv - validate JSON files against JSON Schema + +=head1 SYNOPSIS + +jsv -s schema.json [ file1.json file2.json ... | < file.json ] + +=head1 USAGE + +This script validates JSON against a JSON Schema. + +=head2 Exit codes + +=over + +=item 0 + +JSON is valid + +=item 1 + +JSON is invalid + +=item 2 + +JSON could not be parsed + +=item 3 + +input file not found or not readable + +=back + +=head1 OPTIONS + +=over + +=item --schema|-s FILE + +JSON Schema file to validate with. Use C<-> for STDIN + +=item --color|-c + +Show validation result in color (default). Disable with C<--no-color> + +=item --quiet + +Don't output validation result + +=item --help|-h|-? + +show this help + +=back + +=head1 SEE ALSO + +L + +=cut diff --git a/t/05_script.t b/t/05_script.t new file mode 100644 index 0000000..54c178f --- /dev/null +++ b/t/05_script.t @@ -0,0 +1,34 @@ +use strict; +use warnings; + +use Test::More; +use Test::Output; + +my $exit; +sub jsv { + system($^X, 'script/jsv', @_); $exit = $? >> 8 +} + +output_like { jsv '-h' } qr/^Usage:\n jsv /m, qr/^$/, 'help'; +is $exit, 0, 'no error'; + +output_is { jsv 't/script/file6.json' } + "", "failed to open t/script/file6.json\n"; +is $exit, 3, 'missing file'; + +output_is { jsv 't/script/file5.json' } + "", "failed to parse t/script/file5.json\n"; +is $exit, 2, 'JSON broken'; + +output_is { jsv '-s', 't/script/schema.json', map { "t/script/file$_.json" } 1..4 } + "invalid t/script/file1.json\n". + "valid t/script/file2.json\n". + "valid t/script/file3.json\n". + "invalid t/script/file4.json\n", + ""; +is $exit, 1, 'JSON invalid'; + +output_is { jsv '-s', 't/script/schema.json', 't/script/file2.json', '-q' } "", ""; +is $exit, 0, 'JSON valid'; + +done_testing; diff --git a/t/script/file1.json b/t/script/file1.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/t/script/file1.json @@ -0,0 +1 @@ +{} diff --git a/t/script/file2.json b/t/script/file2.json new file mode 100644 index 0000000..c21e1f3 --- /dev/null +++ b/t/script/file2.json @@ -0,0 +1 @@ +{ "föö": 1 } diff --git a/t/script/file3.json b/t/script/file3.json new file mode 100644 index 0000000..a1294d3 --- /dev/null +++ b/t/script/file3.json @@ -0,0 +1 @@ +{ "föö": 10, "bar": "xyz" } diff --git a/t/script/file4.json b/t/script/file4.json new file mode 100644 index 0000000..e791691 --- /dev/null +++ b/t/script/file4.json @@ -0,0 +1 @@ +{ "föö": 1.2, "bar": "xyz" } diff --git a/t/script/file5.json b/t/script/file5.json new file mode 100644 index 0000000..98232c6 --- /dev/null +++ b/t/script/file5.json @@ -0,0 +1 @@ +{ diff --git a/t/script/schema.json b/t/script/schema.json new file mode 100644 index 0000000..49ae1e4 --- /dev/null +++ b/t/script/schema.json @@ -0,0 +1,8 @@ +{ + "type": "object", + "properties": { + "föö": { "type": "integer" }, + "bar": { "type": "string" } + }, + "required": [ "föö" ] +}