From 82dd1c1f7f7272bb5b1a035bff8dd409bfdc0cba Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 5 Mar 2012 23:24:59 +0100 Subject: [PATCH] Add relaxed schema for application packages that do not need to be distributed --- res/composer-schema-lax.json | 190 ++++++++++++++++++ src/Composer/Command/ValidateCommand.php | 17 ++ src/Composer/Factory.php | 2 +- src/Composer/Json/JsonFile.php | 18 +- src/Composer/Json/JsonValidationException.php | 34 ++++ 5 files changed, 254 insertions(+), 7 deletions(-) create mode 100644 res/composer-schema-lax.json create mode 100644 src/Composer/Json/JsonValidationException.php diff --git a/res/composer-schema-lax.json b/res/composer-schema-lax.json new file mode 100644 index 000000000..097434381 --- /dev/null +++ b/res/composer-schema-lax.json @@ -0,0 +1,190 @@ +{ + "name": "Package", + "type": "object", + "additionalProperties": true, + "properties": { + "name": { + "type": "string", + "description": "Package name, including 'vendor-name/' prefix." + }, + "type": { + "description": "Package type, either 'library' for common packages, 'composer-installer' for custom installers, or a custom type defined by whatever project this package applies to.", + "type": "string" + }, + "target-dir": { + "description": "Forces the package to be installed into the given subdirectory path. This is used for autoloading PSR-0 packages that do not contain their full path. Use forward slashes for cross-platform compatibility.", + "type": "string" + }, + "description": { + "type": "string", + "description": "Short package description." + }, + "keywords": { + "type": "array", + "items": { + "type": "string", + "description": "A tag/keyword that this package relates to." + } + }, + "homepage": { + "type": "string", + "description": "Homepage URL for the project.", + "format": "uri" + }, + "version": { + "type": "string", + "description": "Package version, see http://packagist.org/about for more info on valid schemes." + }, + "time": { + "type": "string", + "description": "Package release date, in 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS' format." + }, + "license": { + "type": ["string", "array"], + "description": "License name. Or an array of license names." + }, + "authors": { + "type": "array", + "description": "List of authors that contributed to the package. This is typically the main maintainers, not the full list.", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "Full name of the author.", + "required": true + }, + "email": { + "type": "string", + "description": "Email address of the author.", + "format": "email" + }, + "homepage": { + "type": "string", + "description": "Homepage URL for the author.", + "format": "uri" + } + } + } + }, + "require": { + "type": "object", + "description": "This is a hash of package name (keys) and version constraints (values) that are required to run this package.", + "additionalProperties": true + }, + "replace": { + "type": "object", + "description": "This is a hash of package name (keys) and version constraints (values) that can be replaced by this package.", + "additionalProperties": true + }, + "conflict": { + "type": "object", + "description": "This is a hash of package name (keys) and version constraints (values) that conflict with this package.", + "additionalProperties": true + }, + "provide": { + "type": "object", + "description": "This is a hash of package name (keys) and version constraints (values) that this package provides in addition to this package's name.", + "additionalProperties": true + }, + "recommend": { + "type": "object", + "description": "This is a hash of package name (keys) and version constraints (values) that this package recommends to be installed (typically this will be installed as well).", + "additionalProperties": true + }, + "suggest": { + "type": "object", + "description": "This is a hash of package name (keys) and version constraints (values) that this package suggests work well with it (typically this will only be suggested to the user).", + "additionalProperties": true + }, + "config": { + "type": ["object"], + "description": "Composer options.", + "properties": { + "vendor-dir": { + "type": "string", + "description": "The location where all packages are installed, defaults to \"vendor\"." + }, + "bin-dir": { + "type": "string", + "description": "The location where all binaries are linked, defaults to \"vendor/bin\"." + } + } + }, + "extra": { + "type": ["object", "array"], + "description": "Arbitrary extra data that can be used by custom installers, for example, package of type composer-installer must have a 'class' key defining the installer class name.", + "additionalProperties": true + }, + "autoload": { + "type": "object", + "description": "Description of how the package can be autoloaded.", + "properties": { + "psr-0": { + "type": "object", + "description": "This is a hash of namespaces (keys) and the directories they can be found into (values) by the autoloader.", + "additionalProperties": true + } + } + }, + "repositories": { + "type": ["object", "array"], + "description": "A set of additional repositories where packages can be found.", + "additionalProperties": true + }, + "bin": { + "type": ["array"], + "description": "A set of files that should be treated as binaries and symlinked into bin-dir (from config).", + "items": { + "type": "string" + } + }, + "scripts": { + "type": ["object"], + "description": "Scripts listeners that will be executed before/after some events.", + "properties": { + "pre-install-cmd": { + "type": ["array", "string"], + "description": "Occurs before the install command is executed, contains one or more Class::method callables." + }, + "post-install-cmd": { + "type": ["array", "string"], + "description": "Occurs after the install command is executed, contains one or more Class::method callables." + }, + "pre-update-cmd": { + "type": ["array", "string"], + "description": "Occurs before the update command is executed, contains one or more Class::method callables." + }, + "post-update-cmd": { + "type": ["array", "string"], + "description": "Occurs after the update command is executed, contains one or more Class::method callables." + }, + "pre-package-install": { + "type": ["array", "string"], + "description": "Occurs before a package is installed, contains one or more Class::method callables." + }, + "post-package-install": { + "type": ["array", "string"], + "description": "Occurs after a package is installed, contains one or more Class::method callables." + }, + "pre-package-update": { + "type": ["array", "string"], + "description": "Occurs before a package is updated, contains one or more Class::method callables." + }, + "post-package-update": { + "type": ["array", "string"], + "description": "Occurs after a package is updated, contains one or more Class::method callables." + }, + "pre-package-uninstall": { + "type": ["array", "string"], + "description": "Occurs before a package has been uninstalled, contains one or more Class::method callables." + }, + "post-package-uninstall": { + "type": ["array", "string"], + "description": "Occurs after a package has been uninstalled, contains one or more Class::method callables." + } + } + } + } +} diff --git a/src/Composer/Command/ValidateCommand.php b/src/Composer/Command/ValidateCommand.php index 61725bc4a..878a058aa 100644 --- a/src/Composer/Command/ValidateCommand.php +++ b/src/Composer/Command/ValidateCommand.php @@ -16,6 +16,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Output\OutputInterface; use Composer\Json\JsonFile; +use Composer\Json\JsonValidationException; /** * @author Robert Schönthal @@ -52,11 +53,27 @@ EOT return 1; } + $laxValid = false; try { $json = new JsonFile($file); $json->read(); + + $json->validateSchema(JsonFile::LAX_SCHEMA); + $laxValid = true; $json->validateSchema(); + } catch (JsonValidationException $e) { + if ($laxValid) { + $output->writeln(''.$file.' is valid for simple usage with composer but has'); + $output->writeln('strict errors that make it unable to be published as a package:'); + } else { + $output->writeln(''.$file.' is invalid, the following errors were found:'); + } + foreach ($e->getErrors() as $message) { + $output->writeln(''.$message.''); + } + return 1; } catch (\Exception $e) { + $output->writeln(''.$file.' contains a JSON Syntax Error:'); $output->writeln(''.$e->getMessage().''); return 1; } diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 4f990f0ca..faab0add1 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -56,7 +56,7 @@ class Factory ); $packageConfig = $file->read(); - $file->validateSchema(); + $file->validateSchema(JsonFile::LAX_SCHEMA); if (isset($packageConfig['config']) && is_array($packageConfig['config'])) { $packageConfig['config'] = array_merge($composerConfig, $packageConfig['config']); diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php index ab1844672..aca967a97 100644 --- a/src/Composer/Json/JsonFile.php +++ b/src/Composer/Json/JsonFile.php @@ -36,6 +36,9 @@ if (!defined('JSON_UNESCAPED_UNICODE')) { */ class JsonFile { + const LAX_SCHEMA = 1; + const STRICT_SCHEMA = 2; + private $path; /** @@ -111,11 +114,11 @@ class JsonFile /** * Validates the schema of the current json file according to composer-schema.json rules * - * @param string $json + * @param int $schema a JsonFile::*_SCHEMA constant * @return Boolean true on success * @throws \UnexpectedValueException */ - public function validateSchema() + public function validateSchema($schema = self::STRICT_SCHEMA) { $content = file_get_contents($this->path); $data = json_decode($content); @@ -124,17 +127,20 @@ class JsonFile self::validateSyntax($content); } - $schema = json_decode(file_get_contents(__DIR__ . '/../../../res/composer-schema.json')); + $schemaFile = __DIR__ . '/../../../res/composer-schema'.($schema === self::LAX_SCHEMA ? '-lax' : '').'.json'; + $schema = json_decode(file_get_contents($schemaFile)); $validator = new Validator(); $validator->check($data, $schema); + // TODO add more specific checks for common errors if needed + if (!$validator->isValid()) { - $msg = "\n"; + $errors = array(); foreach ((array) $validator->getErrors() as $error) { - $msg .= ($error['property'] ? $error['property'].' : ' : '').$error['message']."\n"; + $errors[] = ($error['property'] ? $error['property'].' : ' : '').$error['message']; } - throw new \UnexpectedValueException('Your composer.json is invalid. The following errors were found:' . $msg); + throw new JsonValidationException($errors); } return true; diff --git a/src/Composer/Json/JsonValidationException.php b/src/Composer/Json/JsonValidationException.php new file mode 100644 index 000000000..30bef88b6 --- /dev/null +++ b/src/Composer/Json/JsonValidationException.php @@ -0,0 +1,34 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Json; + +use Exception; + +/** + * @author Jordi Boggiano + */ +class JsonValidationException extends Exception +{ + protected $errors; + + public function __construct(array $errors) + { + parent::__construct(implode("\n", $errors)); + $this->errors = $errors; + } + + public function getErrors() + { + return $this->errors; + } +}