The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
use if "$]" >= 5.022, experimental => 're_strict';
no if "$]" >= 5.031009, feature => 'indirect';
no if "$]" >= 5.033001, feature => 'multidimensional';
no if "$]" >= 5.033006, feature => 'bareword_filehandles';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Test::More 0.88;
use if $ENV{AUTHOR_TESTING}, 'Test::Warnings';
use lib 't/lib';
use Helper;
foreach my $config (0, 1) {
note 'stringy_numbers = '.$config;
my $js = JSON::Schema::Modern->new(stringy_numbers => $config);
cmp_deeply(
$js->evaluate(1, { $_ => '1' })->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/'.$_,
error => $_.' value is not a number',
}
],
},
'strings cannot be used in place of numbers in schema for '.$_,
) foreach qw(multipleOf maximum exclusiveMaximum minimum exclusiveMinimum);
my $schema = {
allOf => [
{ type => 'string' },
{ type => 'number' },
{ type => 'integer' },
{ type => [ 'object', 'number' ] },
{ type => [ 'object', 'integer' ] },
],
};
cmp_deeply(
$js->evaluate('blah', $schema)->TO_JSON,
{
valid => false,
errors => my $errors = [
{
instanceLocation => '',
keywordLocation => '/allOf/1/type',
error => 'got string, not number',
},
{
instanceLocation => '',
keywordLocation => '/allOf/2/type',
error => 'got string, not integer',
},
{
instanceLocation => '',
keywordLocation => '/allOf/3/type',
error => 'got string, not one of object, number',
},
{
instanceLocation => '',
keywordLocation => '/allOf/4/type',
error => 'got string, not one of object, integer',
},
{
instanceLocation => '',
keywordLocation => '/allOf',
error => 'subschemas 1, 2, 3, 4 are not valid',
},
],
},
'strings that do not look like numbers are never valid as numbers',
);
cmp_deeply(
$js->evaluate('1.1', $schema)->TO_JSON,
{
valid => false,
errors => $errors,
},
'by default "type": "string" does not accept numbers',
) if not $config;
cmp_deeply(
$js->evaluate('1.1', $schema)->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '',
keywordLocation => '/allOf/2/type',
error => 'got string, not integer',
},
{
instanceLocation => '',
keywordLocation => '/allOf/4/type',
error => 'got string, not one of object, integer',
},
{
instanceLocation => '',
keywordLocation => '/allOf',
error => 'subschemas 2, 4 are not valid',
},
],
},
'using stringy numbers, numeric strings are treated as numbers but are still not always integers',
) if $config;
$schema = {
maximum => 5,
exclusiveMaximum => 5,
minimum => 15,
exclusiveMinimum => 15,
allOf => [
{ multipleOf => 2 },
{ multipleOf => 0.3 },
],
};
$errors = [
{
instanceLocation => '',
keywordLocation => '/allOf/0/multipleOf',
error => 'value is not a multiple of 2',
},
{
instanceLocation => '',
keywordLocation => '/allOf/1/multipleOf',
error => 'value is not a multiple of 0.3',
},
{
instanceLocation => '',
keywordLocation => '/allOf',
error => 'subschemas 0, 1 are not valid',
},
{
instanceLocation => '',
keywordLocation => '/maximum',
error => 'value is larger than 5',
},
{
instanceLocation => '',
keywordLocation => '/exclusiveMaximum',
error => 'value is equal to or larger than 5',
},
{
instanceLocation => '',
keywordLocation => '/minimum',
error => 'value is smaller than 15',
},
{
instanceLocation => '',
keywordLocation => '/exclusiveMinimum',
error => 'value is equal to or smaller than 15',
},
];
my $data = 11e0;
cmp_deeply(
$js->evaluate($data, $schema)->TO_JSON,
{
valid => false,
errors => $errors,
},
'real numbers are always evaluated',
);
$data = '11e0';
cmp_deeply(
$js->evaluate($data, $schema)->TO_JSON,
{ valid => true },
'by default, stringy numbers are not evaluated by numeric keywords',
) if $config == 0;
cmp_deeply(
$js->evaluate($data, $schema)->TO_JSON,
{
valid => false,
errors => $errors,
},
'with the config enabled, stringy numbers are treated as numbers by numeric keywords',
) if $config == 1;
is(JSON::Schema::Modern::Utilities::get_type($data), 'string', 'data was not mutated');
}
done_testing;