Skip to content

Commit

Permalink
fix: Succeed format validation if the type is not in the set of given…
Browse files Browse the repository at this point in the history
… instance types (#773)

See https://json-schema.org/draft-04/draft-fge-json-schema-validation-00#rfc.section.7.1: If the type of the instance to validate is not in this set, validation for this format attribute and instance SHOULD succeed.
  • Loading branch information
W0rma committed Jan 11, 2025
1 parent 14e41d8 commit 369def0
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix wrong combined paths when traversing upward, fixes #557 ([#652](https://github.com/jsonrainbow/json-schema/pull/652))
- Correct PHPStan baseline ([#764](https://github.com/jsonrainbow/json-schema/pull/764))
- Correct spacing issue in `README.md` ([#763](https://github.com/jsonrainbow/json-schema/pull/763))
- Format attribute: do not validate data instances that aren't the instance type to validate ([#773](https://github.com/jsonrainbow/json-schema/pull/773))

### Changed
- Bump to minimum PHP 7.2 ([#746](https://github.com/jsonrainbow/json-schema/pull/746))
Expand Down
29 changes: 22 additions & 7 deletions src/JsonSchema/Constraints/FormatConstraint.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public function check(&$element, $schema = null, ?JsonPointer $path = null, $i =

switch ($schema->format) {
case 'date':
if (!$date = $this->validateDateTime($element, 'Y-m-d')) {
if (is_string($element) && !$date = $this->validateDateTime($element, 'Y-m-d')) {
$this->addError(ConstraintError::FORMAT_DATE(), $path, [
'date' => $element,
'format' => $schema->format
Expand All @@ -45,7 +45,7 @@ public function check(&$element, $schema = null, ?JsonPointer $path = null, $i =
break;

case 'time':
if (!$this->validateDateTime($element, 'H:i:s')) {
if (is_string($element) && !$this->validateDateTime($element, 'H:i:s')) {
$this->addError(ConstraintError::FORMAT_TIME(), $path, [
'time' => json_encode($element),
'format' => $schema->format,
Expand All @@ -55,7 +55,7 @@ public function check(&$element, $schema = null, ?JsonPointer $path = null, $i =
break;

case 'date-time':
if (null === Rfc3339::createFromString($element)) {
if (is_string($element) && null === Rfc3339::createFromString($element)) {
$this->addError(ConstraintError::FORMAT_DATE_TIME(), $path, [
'dateTime' => json_encode($element),
'format' => $schema->format
Expand Down Expand Up @@ -101,14 +101,14 @@ public function check(&$element, $schema = null, ?JsonPointer $path = null, $i =
break;

case 'uri':
if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) {
if (is_string($element) && null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) {
$this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]);
}
break;

case 'uriref':
case 'uri-reference':
if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) {
if (is_string($element) && null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) {
// FILTER_VALIDATE_URL does not conform to RFC-3986, and cannot handle relative URLs, but
// the json-schema spec uses RFC-3986, so need a bit of hackery to properly validate them.
// See https://tools.ietf.org/html/rfc3986#section-4.2 for additional information.
Expand All @@ -133,6 +133,9 @@ public function check(&$element, $schema = null, ?JsonPointer $path = null, $i =
break;

case 'email':
if (!is_string($element)) {
break;
}
$filterFlags = FILTER_NULL_ON_FAILURE;
if (defined('FILTER_FLAG_EMAIL_UNICODE')) {
// Only available from PHP >= 7.1.0, so ignore it for coverage checks
Expand All @@ -145,13 +148,13 @@ public function check(&$element, $schema = null, ?JsonPointer $path = null, $i =

case 'ip-address':
case 'ipv4':
if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4)) {
if (is_string($element) && null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4)) {
$this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]);
}
break;

case 'ipv6':
if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6)) {
if (is_string($element) && null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6)) {
$this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]);
}
break;
Expand Down Expand Up @@ -191,11 +194,19 @@ protected function validateDateTime($datetime, $format)

protected function validateRegex($regex)
{
if (!is_string($regex)) {
return true;
}

return false !== @preg_match(self::jsonPatternToPhpRegex($regex), '');
}

protected function validateColor($color)
{
if (!is_string($color)) {
return true;
}

if (in_array(strtolower($color), ['aqua', 'black', 'blue', 'fuchsia',
'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple',
'red', 'silver', 'teal', 'white', 'yellow'])) {
Expand All @@ -220,6 +231,10 @@ protected function validatePhone($phone)

protected function validateHostname($host)
{
if (!is_string($host)) {
return true;
}

$hostnameRegex = '/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/i';

return preg_match($hostnameRegex, $host);
Expand Down
29 changes: 29 additions & 0 deletions tests/Constraints/FormatTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,14 @@ public function getValidFormats(): array
return [
['2001-01-23', 'date'],
['2000-02-29', 'date'],
[42, 'date'],
[4.2, 'date'],

['12:22:01', 'time'],
['00:00:00', 'time'],
['23:59:59', 'time'],
[42, 'time'],
[4.2, 'time'],

['2000-05-01T12:12:12Z', 'date-time'],
['2000-05-01T12:12:12+0100', 'date-time'],
Expand All @@ -114,6 +118,8 @@ public function getValidFormats(): array
['2000-05-01T12:12:12.0Z', 'date-time'],
['2000-05-01T12:12:12.000Z', 'date-time'],
['2000-05-01T12:12:12.000000Z', 'date-time'],
[42, 'date-time'],
[4.2, 'date-time'],

['0', 'utc-millisec'],

Expand All @@ -136,6 +142,8 @@ public function getValidFormats(): array
['yellow', 'color'],
['#fff', 'color'],
['#00cc00', 'color'],
[42, 'color'],
[4.2, 'color'],

['background: blue', 'style'],
['color: #000;', 'style'],
Expand All @@ -149,18 +157,39 @@ public function getValidFormats(): array
['./relative:PathReference/', 'uri-reference'],
['relativePathReference/', 'uri-reference'],
['relative/Path:Reference/', 'uri-reference'],
[42, 'uri-reference'],
[4.2, 'uri-reference'],

['info@something.edu', 'email'],
[42, 'email'],
[4.2, 'email'],

['10.10.10.10', 'ip-address'],
['127.0.0.1', 'ip-address'],
[42, 'ip-address'],
[4.2, 'ip-address'],

['127.0.0.1', 'ipv4'],
[42, 'ipv4'],
[4.2, 'ipv4'],

['::ff', 'ipv6'],
[42, 'ipv6'],
[4.2, 'ipv6'],

['www.example.com', 'host-name'],
['3v4l.org', 'host-name'],
['a-valid-host.com', 'host-name'],
['localhost', 'host-name'],
[42, 'host-name'],
[4.2, 'host-name'],

['www.example.com', 'hostname'],
['3v4l.org', 'hostname'],
['a-valid-host.com', 'hostname'],
['localhost', 'hostname'],
[42, 'hostname'],
[4.2, 'hostname'],

['anything', '*'],
['unknown', '*'],
Expand Down

0 comments on commit 369def0

Please sign in to comment.