From 27ff74671696c463c9fb609d4785ea9cacef59ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Barto=C5=A1?= Date: Tue, 16 Mar 2021 21:36:35 +0100 Subject: [PATCH 1/2] SchemaExtension --- src/DI/Extensions/SchemaExtension.php | 142 ++++++++++++++++++++++ tests/DI/SchemaExtension.phpt | 167 ++++++++++++++++++++++++++ 2 files changed, 309 insertions(+) create mode 100644 src/DI/Extensions/SchemaExtension.php create mode 100644 tests/DI/SchemaExtension.phpt diff --git a/src/DI/Extensions/SchemaExtension.php b/src/DI/Extensions/SchemaExtension.php new file mode 100644 index 000000000..7793f0200 --- /dev/null +++ b/src/DI/Extensions/SchemaExtension.php @@ -0,0 +1,142 @@ +processArray($this->config)) + ->otherItems(Expect::mixed()); + + $this->validateSchema($schema, $this->compiler->getConfig()); + } + + + /** + * @param mixed $value + * @return mixed + */ + private function process($value) + { + if ($value instanceof Statement) { + return $this->processStatement($value); + } + + if (is_array($value)) { + return $this->processArray($value); + } + + return $value; + } + + + private function processStatement(Statement $statement): Schema\Schema + { + if ($statement->entity === 'schema') { + $arguments = []; + foreach ($statement->arguments as $value) { + if (!$value instanceof Statement) { + $valueType = gettype($value); + throw new Nette\InvalidArgumentException("schema() should contain another statement(), $valueType given."); + } + + $arguments[] = $value; + } + + if (count($arguments) === 0) { + throw new Nette\InvalidArgumentException('schema() should have at least one argument.'); + } + + return $this->buildSchemaFromStatements($arguments); + } + + return $this->buildSchemaFromStatements([$statement]); + } + + + /** + * @param mixed[] $array + * @return mixed[] + */ + private function processArray(array $array): array + { + return array_map( + function ($value) { + return $this->process($value); + }, + $array + ); + } + + + /** + * @param Statement[] $statements + */ + private function buildSchemaFromStatements(array $statements): Schema\Schema + { + $schema = null; + foreach ($statements as $statement) { + $processedArguments = array_map( + function ($argument) { + return $this->process($argument); + }, + $statement->arguments + ); + + if ($schema === null) { + $methodName = $statement->getEntity(); + assert(is_string($methodName)); + + $schema = Expect::{$methodName}(...$processedArguments); + assert( + $schema instanceof Schema\Elements\Type || + $schema instanceof Schema\Elements\AnyOf || + $schema instanceof Schema\Elements\Structure + ); + + $schema->required(); + } else { + $schema->{$statement->getEntity()}(...$processedArguments); + } + } + + return $schema; + } + + + /** + * @param mixed[] $config + */ + private function validateSchema(Schema\Elements\Structure $schema, array $config): void + { + $processor = new Schema\Processor; + try { + $processor->process($schema, $config); + } catch (Schema\ValidationException $e) { + throw new Nette\DI\InvalidConfigurationException($e->getMessage()); + } + foreach ($processor->getWarnings() as $warning) { + trigger_error($warning, E_USER_DEPRECATED); + } + } +} diff --git a/tests/DI/SchemaExtension.phpt b/tests/DI/SchemaExtension.phpt new file mode 100644 index 000000000..0e5cd9ddc --- /dev/null +++ b/tests/DI/SchemaExtension.phpt @@ -0,0 +1,167 @@ +addExtension('schema', new SchemaExtension); + + $rootKeys = ['string', 'string2', 'structure', 'notValidatedValue']; + foreach ($rootKeys as $key) { + $compiler->addExtension($key, $voidExtension); + } + + $schemaConfig = $loader->load(Tester\FileMock::create(/** @lang neon */ ' +schema: + string: string() + structure: structure([ + string: string(), + stringWithDefault: schema(string("default value"), required(false)) + int: int(), + float: float(), + bool: bool(), + array: arrayOf(string()) + list: listOf(string()) + type: type("string|int") + schema1: schema(string()) + schema2: schema(string(), nullable()) + schema3: schema(string(), nullable(), required(false)) + schema4: schema(int(), min(10), max(20)) + ]) +', 'neon')); + $compiler->addConfig($schemaConfig); + + $validConfig = $loader->load(Tester\FileMock::create(/** @lang neon */ ' +string: string +structure: + string: text + int: 123 + float: 123.456 + bool: true + array: [key: string, anotherString] + list: [string, anotherString] + type: string + schema1: string + schema2: null + #schema3 is not required + schema4: 15 +notValidatedValue: literally anything +', 'neon')); + $compiler->addConfig($validConfig); + + return $compiler; +}; + +test('no error', static function () use ($createCompiler) { + $compiler = $createCompiler(); + + Assert::noError(static function () use ($compiler) { + eval($compiler->compile()); + }); +}); + +test('all values are required by default', static function () use ($createCompiler, $loader) { + $compiler = $createCompiler(); + + $config = $loader->load(Tester\FileMock::create(/** @lang neon */ ' +schema: + string2: string() + +string2: false +', 'neon')); + + Assert::exception(static function () use ($compiler, $config) { + eval($compiler->addConfig($config)->compile()); + }, DI\InvalidConfigurationException::class, "The item 'string2' expects to be string, false given."); +}); + +test('invalid type', static function () use ($createCompiler, $loader) { + $compiler = $createCompiler(); + + $config = $loader->load(Tester\FileMock::create(/** @lang neon */ ' +structure: + string: false +', 'neon')); + + Assert::exception(static function () use ($compiler, $config) { + eval($compiler->addConfig($config)->compile()); + }, DI\InvalidConfigurationException::class, "The item 'structure › string' expects to be string, false given."); +}); + +test('invalid type argument', static function () use ($createCompiler, $loader) { + $compiler = $createCompiler(); + + $config = $loader->load(Tester\FileMock::create(/** @lang neon */ ' +structure: + list: [arr: ayy!] +', 'neon')); + + Assert::exception(static function () use ($compiler, $config) { + eval($compiler->addConfig($config)->compile()); + }, DI\InvalidConfigurationException::class, "The item 'structure › list' expects to be list, array given."); +}); + +test('invalid type argument 2', static function () use ($createCompiler, $loader) { + $compiler = $createCompiler(); + + $config = $loader->load(Tester\FileMock::create(/** @lang neon */ ' +structure: + schema4: 21 +', 'neon')); + + Assert::exception(static function () use ($compiler, $config) { + eval($compiler->addConfig($config)->compile()); + }, DI\InvalidConfigurationException::class, "The item 'structure › schema4' expects to be in range 10..20, 21 given."); +}); + +test('empty schema()', static function () use ($createCompiler, $loader) { + $compiler = $createCompiler(); + + $config = $loader->load(Tester\FileMock::create(/** @lang neon */ ' +schema: + emptySchema: schema() +', 'neon')); + + Assert::exception(static function () use ($compiler, $config) { + eval($compiler->addConfig($config)->compile()); + }, Nette\InvalidArgumentException::class, 'schema() should have at least one argument.'); +}); + +test('invalid schema() argument', static function () use ($createCompiler, $loader) { + $compiler = $createCompiler(); + + $config = $loader->load(Tester\FileMock::create(/** @lang neon */ ' +schema: + emptySchema: schema(123) +', 'neon')); + + Assert::exception(static function () use ($compiler, $config) { + eval($compiler->addConfig($config)->compile()); + }, Nette\InvalidArgumentException::class, 'schema() should contain another statement(), integer given.'); +}); From 093c9054f42302ecf4ffe58fcda9045d128ddf08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Barto=C5=A1?= Date: Tue, 16 Mar 2021 22:02:14 +0100 Subject: [PATCH 2/2] Compiler:getConfig() remove deprecation --- src/DI/Compiler.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/DI/Compiler.php b/src/DI/Compiler.php index 281d53123..3a2ac1873 100644 --- a/src/DI/Compiler.php +++ b/src/DI/Compiler.php @@ -133,7 +133,6 @@ public function loadConfig(string $file, Config\Loader $loader = null) /** * Returns configuration. - * @deprecated */ public function getConfig(): array {