Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

New Format constraint added

This constraint satisfies the "format" requirement

 * http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.23
  • Loading branch information...
commit 58964578d1d80817ff9403da3f7e649a4703d4e9 1 parent 607e055
@justinrainbow authored
View
3  composer.json
@@ -29,6 +29,5 @@
},
"autoload": {
"psr-0": { "JsonSchema": "src/" }
- },
- "bin": ["bin/jsonschema"]
+ }
}
View
4 docs/index.rst
@@ -17,7 +17,7 @@ Installation
.. code-block:: console
- git clone --recursive https://github.com/justinrainbow/json-schema.git
+ git clone https://github.com/justinrainbow/json-schema.git
Composer method
^^^^^^^^^^^^^^^
@@ -28,7 +28,7 @@ Add the `justinrainbow/json-schema` package to your `composer.json` file.
{
"require": {
- "justinrainbow/json-schema": ">=1.0.0-dev"
+ "justinrainbow/json-schema": "1.1.*"
}
}
View
17 src/JsonSchema/Constraints/Constraint.php
@@ -68,6 +68,15 @@ public function isValid()
}
/**
+ * Clears any reported errors. Should be used between
+ * multiple validation checks.
+ */
+ public function reset()
+ {
+ $this->errors = array();
+ }
+
+ /**
* Bubble down the path
*
* @param string $path Current path
@@ -204,4 +213,12 @@ protected function checkEnum($value, $schema = null, $path = null, $i = null)
$this->addErrors($validator->getErrors());
}
+
+ protected function checkFormat($value, $schema = null, $path = null, $i = null)
+ {
+ $validator = new Format($this->checkMode);
+ $validator->check($value, $schema, $path, $i);
+
+ $this->addErrors($validator->getErrors());
+ }
}
View
158 src/JsonSchema/Constraints/Format.php
@@ -0,0 +1,158 @@
+<?php
+
+/*
+ * This file is part of the JsonSchema package.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace JsonSchema\Constraints;
+
+/**
+ * Validates against the "format" property
+ *
+ * @author Justin Rainbow <justin.rainbow@gmail.com>
+ * @link http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.23
+ */
+class Format extends Constraint
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function check($element, $schema = null, $path = null, $i = null)
+ {
+ if (!isset($schema->format)) {
+ return;
+ }
+
+ switch ($schema->format) {
+ case 'date':
+ if (!$date = $this->validateDateTime($element, 'Y-m-d')) {
+ $this->addError($path, sprintf('Invalid date %s, expected format YYYY-MM-DD', json_encode($element)));
+ }
+ break;
+
+ case 'time':
+ if (!$this->validateDateTime($element, 'H:i:s')) {
+ $this->addError($path, sprintf('Invalid time %s, expected format HH:MM:SS', json_encode($element)));
+ }
+ break;
+
+ case 'date-time':
+ if (!$this->validateDateTime($element, 'Y-m-d\TH:i:s\Z')) {
+ $this->addError($path, sprintf('Invalid date time %s, expected format YYYY-MM-DDTHH:MM:SSZ', json_encode($element)));
+ }
+ break;
+
+ case 'utc-millisec':
+ if (!$this->validateDateTime($element, 'U')) {
+ $this->addError($path, sprintf('Invalid time %s, expected integer of milliseconds since Epoch', json_encode($element)));
+ }
+ break;
+
+ case 'regex':
+ if (!$this->validateRegex($element, $schema->pattern)) {
+ $this->addError($path, "Invalid regex format");
+ }
+ break;
+
+ case 'color':
+ if (!$this->validateColor($element)) {
+ $this->addError($path, "Invalid color");
+ }
+ break;
+
+ case 'style':
+ if (!$this->validateStyle($element)) {
+ $this->addError($path, "Invalid style");
+ }
+ break;
+
+ case 'phone':
+ if (!$this->validatePhone($element)) {
+ $this->addError($path, "Invalid phone number");
+ }
+ break;
+
+ case 'uri':
+ if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) {
+ $this->addError($path, "Invalid URL format");
+ }
+ break;
+
+ case 'email':
+ if (null === filter_var($element, FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE)) {
+ $this->addError($path, "Invalid email");
+ }
+ break;
+
+ case 'ip-address':
+ if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4)) {
+ $this->addError($path, "Invalid IP address");
+ }
+ break;
+
+ case 'ipv6':
+ if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6)) {
+ $this->addError($path, "Invalid IP address");
+ }
+ break;
+
+ case 'host-name':
+ if (!$this->validateHostname($element)) {
+ $this->addError($path, "Invalid hostname");
+ }
+ break;
+
+ default:
+ $this->addError($path, "Unknown format: " . json_encode($schema->format));
+ break;
+ }
+ }
+
+ protected function validateDateTime($datetime, $format)
+ {
+ $dt = \DateTime::createFromFormat($format, $datetime);
+
+ if (!$dt) {
+ return false;
+ }
+
+ return $datetime === $dt->format($format);
+ }
+
+ protected function validateRegex($string, $regex)
+ {
+ return preg_match('/' . $regex . '/', $string);
+ }
+
+ protected function validateColor($color)
+ {
+ if (in_array(strtolower($color), array('aqua', 'black', 'blue', 'fuchsia',
+ 'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple',
+ 'red', 'silver', 'teal', 'white', 'yellow'))) {
+ return true;
+ }
+
+ return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color);
+ }
+
+ protected function validateStyle($style)
+ {
+ $properties = explode(';', rtrim($style, ';'));
+ $invalidEntries = preg_grep('/^\s*[-a-z]+\s*:\s*.+$/i', $properties, PREG_GREP_INVERT);
+
+ return empty($invalidEntries);
+ }
+
+ protected function validatePhone($phone)
+ {
+ return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone);
+ }
+
+ protected function validateHostname($host)
+ {
+ return preg_match('/^[_a-z]+\.([_a-z]+\.?)+$/i', $host);
+ }
+}
View
2  src/JsonSchema/Constraints/Number.php
@@ -36,5 +36,7 @@ public function check($element, $schema = null, $path = null, $i = null)
if (isset($schema->divisibleBy) && $element % $schema->divisibleBy != 0) {
$this->addError($path, "is not divisible by " . $schema->divisibleBy);
}
+
+ $this->checkFormat($element, $schema, $path, $i);
}
}
View
2  src/JsonSchema/Constraints/String.php
@@ -36,5 +36,7 @@ public function check($element, $schema = null, $path = null, $i = null)
if (isset($schema->pattern) && !preg_match('/' . $schema->pattern . '/', $element)) {
$this->addError($path, "does not match the regex pattern " . $schema->pattern);
}
+
+ $this->checkFormat($element, $schema, $path, $i);
}
}
View
8 src/JsonSchema/Constraints/Undefined.php
@@ -53,9 +53,9 @@ public function validateTypes($value, $schema = null, $path = null, $i = null)
// check object
if (is_object($value) && (isset($schema->properties) || isset($schema->patternProperties))) {
$this->checkObject(
- $value,
- isset($schema->properties) ? $schema->properties : null,
- $path,
+ $value,
+ isset($schema->properties) ? $schema->properties : null,
+ $path,
isset($schema->additionalProperties) ? $schema->additionalProperties : null,
isset($schema->patternProperties) ? $schema->patternProperties : null
);
@@ -93,7 +93,7 @@ protected function validateCommonProperties($value, $schema = null, $path = null
}
// Verify required values
- if (is_object($value) && $value instanceOf Undefined) {
+ if (is_object($value) && $value instanceof Undefined) {
if (isset($schema->required) && $schema->required) {
$this->addError($path, "is missing and it is required");
}
View
205 tests/JsonSchema/Tests/Constraints/FormatTest.php
@@ -0,0 +1,205 @@
+<?php
+
+/*
+ * This file is part of the JsonSchema package.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace JsonSchema\Tests\Constraints;
+
+use JsonSchema\Constraints\Format;
+
+class FormatTest extends BaseTestCase
+{
+ public function testNullThing()
+ {
+ $validator = new Format();
+ $schema = new \stdClass;
+
+ $validator->check('10', $schema);
+ $this->assertEmpty($validator->getErrors());
+ }
+
+ public function testRegex()
+ {
+ $validator = new Format();
+ $schema = new \stdClass;
+ $schema->format = 'regex';
+ $schema->pattern = '\d+';
+
+ $validator->check('10', $schema);
+ $this->assertEmpty($validator->getErrors());
+
+ $validator->check('ten', $schema);
+ $this->assertCount(1, $validator->getErrors());
+ }
+
+ /**
+ * @dataProvider getValidFormats
+ */
+ public function testValidFormat($string, $format)
+ {
+ $validator = new Format();
+ $schema = new \stdClass;
+ $schema->format = $format;
+
+ $validator->check($string, $schema);
+ $this->assertEmpty($validator->getErrors());
+ }
+
+ /**
+ * @dataProvider getInvalidFormats
+ */
+ public function testInvalidFormat($string, $format)
+ {
+ $validator = new Format();
+ $schema = new \stdClass;
+ $schema->format = $format;
+
+ $validator->check($string, $schema);
+ $this->assertEquals(1, count($validator->getErrors()), 'Expected 1 error');
+ }
+
+ public function getValidFormats()
+ {
+ return array(
+ array('2001-01-23', 'date'),
+ array('2000-02-29', 'date'),
+
+ array('12:22:01', 'time'),
+ array('00:00:00', 'time'),
+ array('23:59:59', 'time'),
+
+ array('2000-05-01T12:12:12Z', 'date-time'),
+
+ array('0', 'utc-millisec'),
+
+ array('aqua', 'color'),
+ array('black', 'color'),
+ array('blue', 'color'),
+ array('fuchsia', 'color'),
+ array('gray', 'color'),
+ array('green', 'color'),
+ array('lime', 'color'),
+ array('maroon', 'color'),
+ array('navy', 'color'),
+ array('olive', 'color'),
+ array('orange', 'color'),
+ array('purple', 'color'),
+ array('red', 'color'),
+ array('silver', 'color'),
+ array('teal', 'color'),
+ array('white', 'color'),
+ array('yellow', 'color'),
+ array('#fff', 'color'),
+ array('#00cc00', 'color'),
+
+ array('background: blue', 'style'),
+ array('color: #000;', 'style'),
+
+ array('555 320 1212', 'phone'),
+
+ array('http://bluebox.org', 'uri'),
+
+ array('info@something.edu', 'email'),
+
+ array('10.10.10.10', 'ip-address'),
+ array('127.0.0.1', 'ip-address'),
+
+ array('::ff', 'ipv6'),
+
+ array('www.example.com', 'host-name'),
+ );
+ }
+
+ public function getInvalidFormats()
+ {
+ return array(
+ array('January 1st, 1910', 'date'),
+ array('199-01-1', 'date'),
+ array('2012-0-11', 'date'),
+ array('2012-10-1', 'date'),
+
+ array('24:01:00', 'time'),
+ array('00:00:60', 'time'),
+ array('25:00:00', 'time'),
+
+ array('1999-1-11T00:00:00Z', 'date-time'),
+
+ array('-1', 'utc-millisec'),
+ array(PHP_INT_MAX, 'utc-millisec'),
+
+ array('grey', 'color'),
+ array('#HHH', 'color'),
+ array('#000a', 'color'),
+ array('#aa', 'color'),
+
+ array('background; blue', 'style'),
+
+ array('1 123 4424', 'phone'),
+
+ array('htt:/bluebox.org', 'uri'),
+
+ array('info@somewhere', 'email'),
+
+ array('256.2.2.2', 'ip-address'),
+
+ array(':::ff', 'ipv6'),
+
+ array('localhost', 'host-name'),
+
+ array('anything', '*'),
+ );
+ }
+
+ public function getValidTests()
+ {
+ return array(
+ array(
+ '{ "counter": "10" }',
+ '{
+ "type": "object",
+ "properties": {
+ "counter": {
+ "type": "string",
+ "format": "regex",
+ "pattern": "[0-9]+"
+ }
+ }
+ }'),
+ );
+ }
+
+ public function getInvalidTests()
+ {
+ return array(
+ array(
+ '{ "counter": "blue" }',
+ '{
+ "type": "object",
+ "properties": {
+ "counter": {
+ "type": "string",
+ "format": "regex",
+ "pattern": "[0-9]+"
+ }
+ }
+ }'
+ ),
+ array(
+ '{ "color": "blueberry" }',
+ '{
+ "type": "object",
+ "properties": {
+ "color": {
+ "type": "string",
+ "format": "color"
+ }
+ }
+ }'
+ )
+ );
+ }
+}

0 comments on commit 5896457

Please sign in to comment.
Something went wrong with that request. Please try again.