1
0
Fork 0

Merge remote-tracking branch 'digitalkaoz/json_schema_validator'

pull/390/head
Jordi Boggiano 2012-03-05 20:08:18 +01:00
commit 07a82c9223
7 changed files with 86 additions and 66 deletions

View File

@ -19,6 +19,8 @@
],
"require": {
"php": ">=5.3.0",
"justinrainbow/json-schema": ">=1.1.0",
"seld/jsonlint": "*",
"symfony/console": "dev-master",
"symfony/finder": "dev-master",
"symfony/process": "dev-master"

View File

@ -35,8 +35,7 @@
},
"version": {
"type": "string",
"description": "Package version, see http://packagist.org/about for more info on valid schemes.",
"required": true
"description": "Package version, see http://packagist.org/about for more info on valid schemes."
},
"time": {
"type": "string",

View File

@ -53,7 +53,7 @@ EOT
}
try {
JsonFile::parseJson(file_get_contents($file));
JsonFile::parseJson(file_get_contents($file), true);
} catch (\Exception $e) {
$output->writeln('<error>'.$e->getMessage().'</error>');
return 1;

View File

@ -55,7 +55,7 @@ class Factory
'process-timeout' => 300,
);
$packageConfig = $file->read();
$packageConfig = $file->read(true);
if (isset($packageConfig['config']) && is_array($packageConfig['config'])) {
$packageConfig['config'] = array_merge($composerConfig, $packageConfig['config']);

View File

@ -14,6 +14,8 @@ namespace Composer\Json;
use Composer\Repository\RepositoryManager;
use Composer\Composer;
use JsonSchema\Validator;
use Seld\JsonLint\JsonParser;
use Composer\Util\StreamContextFactory;
if (!defined('JSON_UNESCAPED_SLASHES')) {
@ -68,7 +70,7 @@ class JsonFile
*
* @return array
*/
public function read()
public function read($validate = false)
{
$ctx = StreamContextFactory::getContext(array(
'http' => array(
@ -80,7 +82,7 @@ class JsonFile
throw new \RuntimeException('Could not read '.$this->path.', you are probably offline');
}
return static::parseJson($json);
return static::parseJson($json, $validate);
}
/**
@ -215,59 +217,64 @@ class JsonFile
/**
* Parses json string and returns hash.
*
* @param string $json json string
* @param string $json json string
* @param boolean $validateSchema wether to validate the json schema
*
* @return mixed
*/
static public function parseJson($json)
public static function parseJson($json, $validateSchema=false)
{
$data = json_decode($json, true);
$data = static::validateSyntax($json);
if (null === $data && 'null' !== strtolower($json)) {
switch (json_last_error()) {
case JSON_ERROR_NONE:
$msg = 'No error has occurred, is your composer.json file empty?';
break;
case JSON_ERROR_DEPTH:
$msg = 'The maximum stack depth has been exceeded';
break;
case JSON_ERROR_STATE_MISMATCH:
$msg = 'Invalid or malformed JSON';
break;
case JSON_ERROR_CTRL_CHAR:
$msg = 'Control character error, possibly incorrectly encoded';
break;
case JSON_ERROR_SYNTAX:
$msg = 'Syntax error';
$charOffset = 0;
if (preg_match('#["}\]]\s*(,)\s*[}\]]#', $json, $match, PREG_OFFSET_CAPTURE)) {
$msg .= ', extra comma';
} elseif (preg_match('#((?<=[^\\\\])\\\\(?!["\\\\/bfnrt]|u[a-f0-9]{4}))#i', $json, $match, PREG_OFFSET_CAPTURE)) {
$msg .= ', unescaped backslash (\\)';
} elseif (preg_match('#(["}\]])(?: *\r?\n *)+"#', $json, $match, PREG_OFFSET_CAPTURE)) {
$msg .= ', missing comma';
$charOffset = 1;
} elseif (preg_match('#^ *([a-z0-9_-]+) *:#mi', $json, $match, PREG_OFFSET_CAPTURE)) {
$msg .= ', you must use double quotes (") around keys';
} elseif (preg_match('#(\'.+?\' *:|: *\'.+?\')#', $json, $match, PREG_OFFSET_CAPTURE)) {
$msg .= ', use double quotes (") instead of single quotes (\')';
} elseif (preg_match('#(\[".*?":.*?\])#', $json, $match, PREG_OFFSET_CAPTURE)) {
$msg .= ', you must use the hash syntax (e.g. {"foo": "bar"}) instead of array syntax (e.g. ["foo", "bar"])';
} elseif (preg_match('#".*?"( *["{\[])#', $json, $match, PREG_OFFSET_CAPTURE)) {
$msg .= ', missing colon';
}
if (isset($match[1][1])) {
$preError = substr($json, 0, $match[1][1]);
$msg .= ' on line '.(substr_count($preError, "\n")+1).', char '.abs(strrpos($preError, "\n") - strlen($preError) - $charOffset);
}
break;
case JSON_ERROR_UTF8:
$msg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
break;
}
throw new \UnexpectedValueException('JSON Parse Error: '.$msg);
if ($validateSchema) {
static::validateSchema($json);
}
return $data;
}
/**
* validates a composer.json against the schema
*
* @param string $json
* @return boolean
* @throws \UnexpectedValueException
*/
public static function validateSchema($json)
{
$data = json_decode($json);
$schema = json_decode(file_get_contents(__DIR__ . '/../../../res/composer-schema.json'));
$validator = new Validator();
$validator->check($data, $schema);
if (!$validator->isValid()) {
$msg = "\n";
foreach ((array) $validator->getErrors() as $error) {
$msg .= ($error['property'] ? $error['property'].' : ' : '').$error['message']."\n";
}
throw new \UnexpectedValueException('Your composer.json did not validate against the schema. The following mistakes were found:'.$msg);
}
}
/**
* validates the json syntax
*
* @param string $json
* @return array
* @throws \UnexpectedValueException
*/
public static function validateSyntax($json)
{
$parser = new JsonParser();
$result = $parser->lint($json);
if (null === $result) {
return json_decode($json, true);
}
throw $result;
}
}

View File

@ -12,6 +12,7 @@
namespace Composer\Test\Json;
use Seld\JsonLint\ParsingException;
use Composer\Json\JsonFile;
class JsonFileTest extends \PHPUnit_Framework_TestCase
@ -21,7 +22,7 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase
$json = '{
"foo": "bar",
}';
$this->expectParseException('extra comma on line 2, char 21', $json);
$this->expectParseException('Parse error on line 2', $json);
}
public function testParseErrorDetectExtraCommaInArray()
@ -31,7 +32,7 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase
"bar",
]
}';
$this->expectParseException('extra comma on line 3, char 18', $json);
$this->expectParseException('Parse error on line 3', $json);
}
public function testParseErrorDetectUnescapedBackslash()
@ -39,7 +40,7 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase
$json = '{
"fo\o": "bar"
}';
$this->expectParseException('unescaped backslash (\\) on line 2, char 12', $json);
$this->expectParseException('Parse error on line 1', $json);
}
public function testParseErrorSkipsEscapedBackslash()
@ -48,7 +49,7 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase
"fo\\\\o": "bar"
"a": "b"
}';
$this->expectParseException('missing comma on line 2, char 23', $json);
$this->expectParseException('Parse error on line 2', $json);
}
public function testParseErrorDetectSingleQuotes()
@ -56,7 +57,7 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase
$json = '{
\'foo\': "bar"
}';
$this->expectParseException('use double quotes (") instead of single quotes (\') on line 2, char 9', $json);
$this->expectParseException('Parse error on line 1', $json);
}
public function testParseErrorDetectMissingQuotes()
@ -64,7 +65,7 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase
$json = '{
foo: "bar"
}';
$this->expectParseException('must use double quotes (") around keys on line 2, char 9', $json);
$this->expectParseException('Parse error on line 1', $json);
}
public function testParseErrorDetectArrayAsHash()
@ -72,7 +73,7 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase
$json = '{
"foo": ["bar": "baz"]
}';
$this->expectParseException('you must use the hash syntax (e.g. {"foo": "bar"}) instead of array syntax (e.g. ["foo", "bar"]) on line 2, char 16', $json);
$this->expectParseException('Parse error on line 2', $json);
}
public function testParseErrorDetectMissingComma()
@ -81,7 +82,18 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase
"foo": "bar"
"bar": "foo"
}';
$this->expectParseException('missing comma on line 2, char 21', $json);
$this->expectParseException('Parse error on line 2', $json);
}
public function testSchemaValidation()
{
$json = file_get_contents(__DIR__.'/../../../../composer.json');
try {
$this->assertNull(JsonFile::validateSchema($json));
} catch (\UnexpectedValueException $e) {
$this->fail('invalid schema');
}
}
public function testParseErrorDetectMissingCommaMultiline()
@ -91,7 +103,7 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase
"bar": "foo"
}';
$this->expectParseException('missing comma on line 2, char 24', $json);
$this->expectParseException('Parse error on line 2', $json);
}
public function testParseErrorDetectMissingColon()
@ -100,7 +112,7 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase
"foo": "bar",
"bar" "foo"
}';
$this->expectParseException('missing colon on line 3, char 14', $json);
$this->expectParseException('Parse error on line 3', $json);
}
public function testSimpleJsonString()
@ -166,7 +178,7 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase
try {
JsonFile::parseJson($json);
$this->fail();
} catch (\UnexpectedValueException $e) {
} catch (ParsingException $e) {
$this->assertContains($text, $e->getMessage());
}
}