diff --git a/bin/build-configs b/bin/build-configs index d952ddb..bbfddf1 100755 --- a/bin/build-configs +++ b/bin/build-configs @@ -6,12 +6,16 @@ declare(strict_types=1); require __DIR__.'/../vendor/autoload.php'; use Illuminate\Support\{Arr, Collection}; +use OliverDaviesLtd\BuildConfigs\ConfigurationData; use OliverDaviesLtd\BuildConfigs\Enum\{Language, WebServer}; -use OliverDaviesLtd\BuildConfigs\Validator\ConfigurationValidator; use Silly\Application; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Filesystem; -use Symfony\Component\Validator\ConstraintViolationInterface; +use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; +use Symfony\Component\Serializer\Serializer; +use Symfony\Component\Validator\{ConstraintViolationInterface, Validation}; use Symfony\Component\Yaml\Yaml; use Twig\Environment; use Twig\Loader\FilesystemLoader; @@ -51,14 +55,20 @@ $app->command( Yaml::parseFile($configFile), ); - $violations = (new ConfigurationValidator())->validate($configurationData); + // Convert the input to a configuration data object. + $normalizer = new ObjectNormalizer(null, new CamelCaseToSnakeCaseNameConverter()); + $serializer = new Serializer([$normalizer], [new JsonEncoder()]); + $configurationDataObject = $serializer->deserialize(json_encode($configurationData), ConfigurationData::class, 'json'); + + $validator = Validation::createValidatorBuilder()->enableAnnotationMapping()->getValidator(); + $violations = $validator->validate($configurationDataObject); if (0 < $violations->count()) { $io->error('Configuration is invalid.'); $io->listing( collect($violations) - ->map(fn (ConstraintViolationInterface $v) => "{$v->getInvalidValue()} - {$v->getMessage()}") + ->map(fn (ConstraintViolationInterface $v) => "{$v->getPropertyPath()} - {$v->getMessage()}") ->toArray() ); diff --git a/composer.json b/composer.json index 6e8fe60..9ddb3b0 100644 --- a/composer.json +++ b/composer.json @@ -4,6 +4,8 @@ "mnapoli/silly": "^1.8", "symfony/console": "^6.2", "symfony/filesystem": "^6.2", + "symfony/property-access": "^6.2", + "symfony/serializer": "^6.2", "symfony/validator": "^6.2", "symfony/yaml": "^6.2", "twig/twig": "^3.5" diff --git a/composer.lock b/composer.lock index a46d98c..d92fc43 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f061fa0f45e2799ba4d741d3232d9b00", + "content-hash": "94f4b4a5ef24faa3bb9781c9792c206c", "packages": [ { "name": "amphp/amp", @@ -3223,6 +3223,276 @@ ], "time": "2022-11-02T09:08:04+00:00" }, + { + "name": "symfony/property-access", + "version": "v6.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-access.git", + "reference": "2ad1e0a07b8cab3e09905659d14f3b248e916374" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-access/zipball/2ad1e0a07b8cab3e09905659d14f3b248e916374", + "reference": "2ad1e0a07b8cab3e09905659d14f3b248e916374", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/property-info": "^5.4|^6.0" + }, + "require-dev": { + "symfony/cache": "^5.4|^6.0" + }, + "suggest": { + "psr/cache-implementation": "To cache access methods." + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyAccess\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides functions to read and write from/to an object or array using a simple string notation", + "homepage": "https://symfony.com", + "keywords": [ + "access", + "array", + "extraction", + "index", + "injection", + "object", + "property", + "property-path", + "reflection" + ], + "support": { + "source": "https://github.com/symfony/property-access/tree/v6.2.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-03-14T15:00:05+00:00" + }, + { + "name": "symfony/property-info", + "version": "v6.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-info.git", + "reference": "400a019b7c05030599fd15f02b3d4ce287631732" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-info/zipball/400a019b7c05030599fd15f02b3d4ce287631732", + "reference": "400a019b7c05030599fd15f02b3d4ce287631732", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/string": "^5.4|^6.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<5.2", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/dependency-injection": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.10.4|^2", + "phpdocumentor/reflection-docblock": "^5.2", + "phpstan/phpdoc-parser": "^1.0", + "symfony/cache": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/serializer": "^5.4|^6.0" + }, + "suggest": { + "phpdocumentor/reflection-docblock": "To use the PHPDoc", + "psr/cache-implementation": "To cache results", + "symfony/doctrine-bridge": "To use Doctrine metadata", + "symfony/serializer": "To use Serializer metadata" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "dunglas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts information about PHP class' properties using metadata of popular sources", + "homepage": "https://symfony.com", + "keywords": [ + "doctrine", + "phpdoc", + "property", + "symfony", + "type", + "validator" + ], + "support": { + "source": "https://github.com/symfony/property-info/tree/v6.2.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-03-14T15:00:05+00:00" + }, + { + "name": "symfony/serializer", + "version": "v6.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/serializer.git", + "reference": "db9d36470bf0990990fda9320b8b001bb582f075" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/serializer/zipball/db9d36470bf0990990fda9320b8b001bb582f075", + "reference": "db9d36470bf0990990fda9320b8b001bb582f075", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "doctrine/annotations": "<1.12", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/dependency-injection": "<5.4", + "symfony/property-access": "<5.4", + "symfony/property-info": "<5.4", + "symfony/uid": "<5.4", + "symfony/yaml": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.12|^2", + "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", + "symfony/cache": "^5.4|^6.0", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/error-handler": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/form": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/mime": "^5.4|^6.0", + "symfony/property-access": "^5.4|^6.0", + "symfony/property-info": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "symfony/validator": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0", + "symfony/var-exporter": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" + }, + "suggest": { + "psr/cache-implementation": "For using the metadata cache.", + "symfony/config": "For using the XML mapping loader.", + "symfony/mime": "For using a MIME type guesser within the DataUriNormalizer.", + "symfony/property-access": "For using the ObjectNormalizer.", + "symfony/property-info": "To deserialize relations.", + "symfony/var-exporter": "For using the metadata compiler.", + "symfony/yaml": "For using the default YAML mapping loader." + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Serializer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/serializer/tree/v6.2.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-03-31T09:14:44+00:00" + }, { "name": "symfony/service-contracts", "version": "v3.2.0", diff --git a/src/ConfigurationData.php b/src/ConfigurationData.php new file mode 100644 index 0000000..8436a11 --- /dev/null +++ b/src/ConfigurationData.php @@ -0,0 +1,38 @@ + new Assert\Optional( + new Assert\All(new Assert\Type('string')) + ), + 'type' => new Assert\Choice(['mariadb', 'mysql']), + 'version' => new Assert\Type('integer'), + ], + allowExtraFields: false, + )] + public array $database; + + #[Assert\Choice(choices: ['node', 'php'])] + #[Assert\NotBlank] + public string $language; + + #[Assert\Length(min: 1)] + #[Assert\NotBlank] + public string $name; + + #[Assert\Choice(choices: ['drupal-project', 'fractal', 'laravel', 'php-library'])] + #[Assert\NotBlank] + public string $type; + + #[Assert\NotBlank] + public ?string $projectRoot; +} diff --git a/src/Validator/ConfigurationValidator.php b/src/Validator/ConfigurationValidator.php deleted file mode 100644 index 353553d..0000000 --- a/src/Validator/ConfigurationValidator.php +++ /dev/null @@ -1,97 +0,0 @@ - [ - 'name' => [ - new Assert\NotNull(), - new Assert\Type('string'), - new Assert\Length(['min' => 1]), - ], - - 'language' => [ - new Assert\NotNull(), - new Assert\Type('string'), - new Assert\Choice([ - 'node', - 'php', - ]), - ], - - 'type' => [ - new Assert\NotNull(), - new Assert\Type('string'), - new Assert\Choice([ - 'drupal-project', - 'fractal', - 'laravel', - 'php-library', - ]), - ], - - 'project_root' => [ - new Assert\NotNull(), - new Assert\Type('string'), - ], - - 'database' => new Assert\Collection( - [ - 'extra_databases' => new Assert\Optional( - [ - new Assert\Type(['type' => 'array']), - new Assert\All( - [ - new Assert\Type(['type' => 'string']) - ] - ) - ], - ), - 'type' => new Assert\Type(['type' => 'string']), - 'version' => new Assert\Type(['type' => 'integer']), - ] - ), - - 'drupal' => new Assert\Optional(), - - 'docker-compose' => new Assert\Optional(), - - 'dockerfile' => new Assert\Optional(), - - // TODO: this should be a boolean if present. - 'justfile' => new Assert\Optional(), - - 'node' => new Assert\Optional(), - - 'php' => new Assert\Optional(), - - 'web' => new Assert\Optional(), - ], - - 'allowExtraFields' => false, - 'allowMissingFields' => true, - ], - ); - - return $validator->validate( - constraints: $constraint, - groups: $groups, - value: $configurationData, - ); - } -} diff --git a/src/Validator/ValidatorInterface.php b/src/Validator/ValidatorInterface.php deleted file mode 100644 index b3bf2f0..0000000 --- a/src/Validator/ValidatorInterface.php +++ /dev/null @@ -1,15 +0,0 @@ - $configurationData - */ - public function validate(array $configurationData): ConstraintViolationListInterface; -}