App-jsonvalidate
view release on metacpan or search on metacpan
scripts/jsonvalidate view on Meta::CPAN
{
_message( 5, "No <green>schema_base</> value provided, deriving it from <green>$root_schema_path</>" );
# Set 'schema_base' to the parent directory of the first schema file found.
$opts->{schema_base} = $root_schema_path->parent;
_message( 5, "<green>schema_base</> is now set to <green>$opts->{schema_base}</>" );
}
if( $opts->{extensions} )
{
$opts->{unique_keys} = $opts->{extensions};
}
# Build the validator from the FIRST schema (root)
my %ctor =
(
compile => $opts->{compile} ? 1 : 0,
content_assert => $opts->{content_checks} ? 1 : 0,
extensions => $opts->{extensions} ? 1 : 0,
ignore_unknown_required_vocab => $opts->{ignore_vocab} ? 1 : 0,
max_errors => $opts->{max_errors},
normalize_instance => $opts->{normalize} ? 1 : 0,
trace => $opts->{trace} ? 1 : 0,
trace_limit => $opts->{trace_limit},
trace_sample => $opts->{trace_sample},
unique_keys => $opts->{unique_keys} ? 1 : 0,
);
local $JSON::Schema::Validate::DEBUG = $opts->{debug};
my $js = JSON::Schema::Validate->new( $schemas[0], %ctor );
# Register built-in formats if desired
if( $opts->{register_formats} )
{
_message( 5, "Registering builtin formats." );
$js->register_builtin_formats;
}
# Content checks on?
if( $opts->{content_checks} )
{
_message( 5, "Asserting content." );
$js->content_checks(1);
}
# Simple resolver:
# - file:/⦠or relative paths from --schema-base
# - http(s):// if LWP::UserAgent is available (optional)
# - otherwise: if the requested absolute matches any secondary schema $id, return it
my %by_id;
for my $s ( @schemas )
{
if( ref( $s ) eq 'HASH' && defined( $s->{'$id'} ) )
{
$by_id{ $s->{'$id'} } = $s;
}
}
$js->set_resolver(sub
{
my( $abs_uri ) = @_;
my $orig_uri = $abs_uri;
# Strip fragment for all decisions below
my( $doc_uri, $fragment ) = ( $abs_uri =~ /^([^#]+)(#.*)?$/ );
$doc_uri //= $abs_uri;
my $has_fragment = defined( $fragment ) && length( $fragment );
# First: exact $id match from any schema we were given on the command line
if( exists( $by_id{ $abs_uri } ) || exists( $by_id{ $doc_uri } ) )
{
my $schema = $by_id{ $abs_uri } // $by_id{ $doc_uri };
_message( 6, "Resolver: found pre-loaded schema for ", $by_id{ $abs_uri } ? $abs_uri : $doc_uri );
return( $schema );
}
# Optional: allow relative/local file refs (very common and safe)
if( $opts->{allow_file_refs} && defined( $opts->{schema_base} ) )
{
# If it's not a proper scheme, treat as relative path
if( $doc_uri !~ /^[a-zA-Z][a-zA-Z0-9+.-]*:/ )
{
my $path = file( $doc_uri, base_dir => $opts->{schema_base} );
if( $path->exists )
{
_message( 6, "Resolver: loading local file $path for $doc_uri" );
return( $path->load_json );
}
}
# Also support explicit file:// URIs
elsif( $doc_uri =~ m{^file://}i )
{
my $path = file( $doc_uri );
if( $path->exists )
{
_message( 6, "Resolver: loading file:// URI $path" );
return( $path->load_json );
}
}
}
# Only if user explicitly asked for remote fetching
# Can also be called as --allow_http. It is aliased to --remote_refs
if( $doc_uri =~ m{^https?://}i )
{
# The specs say that, if there is a fragment, we should always fetch the remote document.
my $should_fetch = $has_fragment || $opts->{remote_refs};
if( $should_fetch )
{
_message( 5, "Resolver: fetching remote document $doc_uri (fragment=$has_fragment, remote-refs=", ( $opts->{remote_refs} // 0 ), ")" );
my $doc = _fetch_http_json( $doc_uri, $json );
return( $doc ) if( defined( $doc ) );
}
else
{
_message( 4, "Resolver: refusing to fetch remote $doc_uri (no fragment and --remote-refs not used)" );
return;
}
}
my $path = file( $doc_uri );
# 4. Last resort: maybe it's a bare filename without base?
if( $path->exists && $path->is_file )
{
_message( 6, "Resolver: fallback loading bare path $path" );
return( $path->load_json );
}
# Fail loudly with helpful message
bailout( <<EOF );
Cannot resolve \$ref "$orig_uri"
Document URI: $doc_uri
Fragment : $fragment
This is probably a fictional/internal \$id (very common and correct!).
The validator will NOT auto-fetch such URIs by default (security + correctness).
This looks like a reference to a remote document with a JSON Pointer fragment.
Such references MUST be retrieved (per JSON Schema spec).
Solutions:
⢠Pass --remote-refs to allow HTTP(S) fetching
⢠Provide the referenced schema via additional --schema arguments
⢠Rewrite the schema to use relative references + --schema-base
EOF
});
# Optional: capture $comment into trace (no-op handler by default)
$js->set_comment_handler(sub{});
# Validate instances
my $total_ok = 0;
my $total_fail = 0;
my $run_idx = 0;
if( $opts->{instance}->is_empty )
{
if( $opts->{jsonl} )
{
_message( 5, "No JSON data file was provided, reading from STDIN line by line." );
while( defined( my $line = <STDIN> ) )
{
next if $line =~ /\A\s*\z/;
my $data = _decode_json_or_die( $line, $json, "STDIN: line $.: " );
_run_one( $js, $data, \$total_ok, \$total_fail, ++$run_idx, $json );
}
}
else
{
_message( 5, "No JSON data file was provided, reading from STDIN all in one go." );
my $data = &_get_stdin();
_run_one( $js, $data, \$total_ok, \$total_fail, ++$run_idx, $json );
}
}
else
{
for my $inst_file ( @{$opts->{instance}} )
{
_message( 5, "Processing JSON data file <green>$inst_file</>" );
if( !$inst_file->exists )
{
bailout( "The instance file \"$inst_file\" does not exist." );
}
elsif( $inst_file->is_empty )
{
_message( 1, "The instance file \"$inst_file\" is empty." );
next;
}
my $raw = $inst_file->load( binmode => 'raw' );
if( !defined( $raw ) )
{
_message( 1, "The instance file \"$inst_file\" content could not be retrieved: ", $inst_file->error );
next;
}
if( $opts->{jsonl} )
{
_message( 5, "Processing the file <green>$inst_file</> data line-by-line." );
( run in 1.366 second using v1.01-cache-2.11-cpan-b16cb0d3907 )