diff --git a/composer.json b/composer.json index 5ff7404..a6ce258 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,6 @@ "league/commonmark": "^2.4", "league/oauth2-server": "^8.5", "nette/php-generator": "^4.1", - "nette/schema": "^1.2", "nicmart/tree": "^0.7.2", "nyholm/psr7": "^1.8", "nyholm/psr7-server": "^1.1", @@ -48,7 +47,7 @@ "webonyx/graphql-php": "^15.6", "dragonmantank/cron-expression": "^3.3", "league/oauth2-client": "^2.7", - "justinrainbow/json-schema": "^5.2" + "opis/json-schema": "^2.3" }, "require-dev": { "mockery/mockery": "^1.6", diff --git a/composer.lock b/composer.lock index 3c0993b..44312f1 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": "593dce046df08176585b42c57e05193e", + "content-hash": "54c7a151e1eb3558a606574811103c43", "packages": [ { "name": "defuse/php-encryption", @@ -1780,76 +1780,6 @@ ], "time": "2023-12-03T20:05:35+00:00" }, - { - "name": "justinrainbow/json-schema", - "version": "v5.2.13", - "source": { - "type": "git", - "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/fbbe7e5d79f618997bc3332a6f49246036c45793", - "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", - "json-schema/json-schema-test-suite": "1.2.0", - "phpunit/phpunit": "^4.8.35" - }, - "bin": [ - "bin/validate-json" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "JsonSchema\\": "src/JsonSchema/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bruno Prieto Reis", - "email": "bruno.p.reis@gmail.com" - }, - { - "name": "Justin Rainbow", - "email": "justin.rainbow@gmail.com" - }, - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" - }, - { - "name": "Robert Schönthal", - "email": "seroscho@googlemail.com" - } - ], - "description": "A library to validate a json schema.", - "homepage": "https://github.com/justinrainbow/json-schema", - "keywords": [ - "json", - "schema" - ], - "support": { - "issues": "https://github.com/justinrainbow/json-schema/issues", - "source": "https://github.com/justinrainbow/json-schema/tree/v5.2.13" - }, - "time": "2023-09-26T02:20:38+00:00" - }, { "name": "lcobucci/clock", "version": "3.2.0", @@ -2976,6 +2906,196 @@ ], "time": "2023-11-08T09:30:43+00:00" }, + { + "name": "opis/json-schema", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/opis/json-schema.git", + "reference": "c48df6d7089a45f01e1c82432348f2d5976f9bfb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/json-schema/zipball/c48df6d7089a45f01e1c82432348f2d5976f9bfb", + "reference": "c48df6d7089a45f01e1c82432348f2d5976f9bfb", + "shasum": "" + }, + "require": { + "ext-json": "*", + "opis/string": "^2.0", + "opis/uri": "^1.0", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "ext-bcmath": "*", + "ext-intl": "*", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opis\\JsonSchema\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + }, + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + } + ], + "description": "Json Schema Validator for PHP", + "homepage": "https://opis.io/json-schema", + "keywords": [ + "json", + "json-schema", + "schema", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/opis/json-schema/issues", + "source": "https://github.com/opis/json-schema/tree/2.3.0" + }, + "time": "2022-01-08T20:38:03+00:00" + }, + { + "name": "opis/string", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/opis/string.git", + "reference": "9ebf1a1f873f502f6859d11210b25a4bf5d141e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/string/zipball/9ebf1a1f873f502f6859d11210b25a4bf5d141e7", + "reference": "9ebf1a1f873f502f6859d11210b25a4bf5d141e7", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "ext-json": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opis\\String\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "Multibyte strings as objects", + "homepage": "https://opis.io/string", + "keywords": [ + "multi-byte", + "opis", + "string", + "string manipulation", + "utf-8" + ], + "support": { + "issues": "https://github.com/opis/string/issues", + "source": "https://github.com/opis/string/tree/2.0.1" + }, + "time": "2022-01-14T15:42:23+00:00" + }, + { + "name": "opis/uri", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/opis/uri.git", + "reference": "0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/uri/zipball/0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a", + "reference": "0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a", + "shasum": "" + }, + "require": { + "opis/string": "^2.0", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opis\\Uri\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "Build, parse and validate URIs and URI-templates", + "homepage": "https://opis.io", + "keywords": [ + "URI Template", + "parse url", + "punycode", + "uri", + "uri components", + "url", + "validate uri" + ], + "support": { + "issues": "https://github.com/opis/uri/issues", + "source": "https://github.com/opis/uri/tree/1.1.0" + }, + "time": "2021-05-22T15:57:08+00:00" + }, { "name": "paragonie/random_compat", "version": "v9.99.100", diff --git a/config.ini.example b/config.ini.example index 2494f64..8c4c678 100644 --- a/config.ini.example +++ b/config.ini.example @@ -29,9 +29,6 @@ default[timeout] = 1 default[pool_prefill] = false default[pool_size] = 8 -[sentry] -dsn = - [session] cookie_lifespan = 86400 cookie_name = dmsession diff --git a/docs/pages/docs/features/configuration/index.md b/docs/pages/docs/features/configuration/index.md index 2acaaa0..a3b7d11 100644 --- a/docs/pages/docs/features/configuration/index.md +++ b/docs/pages/docs/features/configuration/index.md @@ -160,7 +160,7 @@ readonly class ManifestConfiguration ``` Then you need to define the configuration provider. -[nette/schema](https://doc.nette.org/en/schema) is used for config validation: +[JSON Schema](https://json-schema.org/) is used for config validation: ```php Expect::string()->min(1)->required(), - 'theme_color' => Expect::string()->min(1)->required(), + return new JsonSchema([ + 'type' => 'object', + 'properties' => [ + 'background_color' => [ + 'type' => 'string', + 'minLength' => 1, + ], + 'theme_color' => [ + 'type' => 'string', + 'minLength' => 1, + ] + ], + 'required' => ['background_color', 'theme_color'] ]); } diff --git a/docs/pages/docs/features/validation/index.md b/docs/pages/docs/features/validation/index.md index 2404013..f6dc6f9 100644 --- a/docs/pages/docs/features/validation/index.md +++ b/docs/pages/docs/features/validation/index.md @@ -48,9 +48,7 @@ readonly class BlogPostForm extends InputValidatedData Validators take in any data and check if it adheres to the configuration schema. The `makeSchema()` method must return a -[nette/schema](https://doc.nette.org/en/schema) object. You can follow -their documentation to learn how to create a validation schema (in concept, -it's similar to the [json-schema](https://json-schema.org)). +[JSON Schema](https://json-schema.org/) object. ```php @@ -76,16 +73,26 @@ readonly class BlogPostFormValidator extends InputValidator protected function castValidatedData(mixed $data): BlogPostForm { return new BlogPostForm( - $data->content, - $data->title, + $data['content'], + $data['title'], ); } protected function makeSchema(): Schema { - return Expect::structure([ - 'content' => Expect::string()->min(1)->required(), - 'title' => Expect::string()->min(1)->required(), + return new JsonSchema([ + 'type' => 'object', + 'properties' => [ + 'content' => [ + 'type' => 'string', + 'minLength' => 1 + ], + 'title' => [ + 'type' => 'string', + 'minLength' => 1 + ] + ], + 'required' => ['content', 'title'] ]); } } diff --git a/docs/pages/index.md b/docs/pages/index.md index a46e1ff..804ee2d 100644 --- a/docs/pages/index.md +++ b/docs/pages/index.md @@ -141,24 +141,14 @@ readonly class Homepage implements HttpResponderInterface class="language-php" data-controller="hljs" data-hljs-language-value="php" - >use function Sentry\init as sentryInit; - + > #[ListensTo(HttpServerStarted::class)] #[Singleton(collection: SingletonCollection::EventListener)] -final readonly class InitializeSentry extends EventListener +final readonly class InitializeErrorReporting extends EventListener { - public function __construct( - private ApplicationConfiguration $applicationContext, - private SentryConfiguration $sentryConfiguration, - ) {} - public function handle(EventInterface $event): void { - sentryInit([ - 'default_integrations' => false, - 'dsn' => $this->sentryConfiguration->dsn, - 'environment' => $this->applicationContext->environment->value, - ]); + // ... } } diff --git a/docs/pages/tutorials/session-based-authentication/index.md b/docs/pages/tutorials/session-based-authentication/index.md index b4c883d..cf61a5c 100644 --- a/docs/pages/tutorials/session-based-authentication/index.md +++ b/docs/pages/tutorials/session-based-authentication/index.md @@ -342,7 +342,7 @@ readonly class UsernamePassword extends InputValidatedData ``` We will use this input validator in the HTTP Responder. It uses -[Nette's Data Validator](https://doc.nette.org/en/schema) +[JSON Schema](https://json-schema.org/) to validate the incoming data: ```php file:app/InputValidator/UsernamePasswordValidator.php @@ -353,9 +353,8 @@ namespace App\InputValidator; use App\InputValidatedData\UsernamePassword; use Distantmagic\Resonance\Attribute\Singleton; use Distantmagic\Resonance\InputValidator; +use Distantmagic\Resonance\JsonSchema; use Distantmagic\Resonance\SingletonCollection; -use Nette\Schema\Expect; -use Nette\Schema\Schema; /** * @extends InputValidator Expect::string()->required(), - 'username' => Expect::string()->min(1)->required(), - 'password' => Expect::string()->min(1)->required(), + return new JsonSchema([ + 'type' => 'object', + 'properties' => [ + 'csrf' => [ + 'type' => 'string', + ], + 'username' => [ + 'type' => 'string', + 'minLength' => 1, + ], + 'password' => [ + 'type' => 'string', + 'minLength' => 1, + ] + ], + 'required' => ['csrf', 'username', 'password'] ]); } } diff --git a/resources/css/docs-page-homepage.css b/resources/css/docs-page-homepage.css index 1573878..99ad273 100644 --- a/resources/css/docs-page-homepage.css +++ b/resources/css/docs-page-homepage.css @@ -114,12 +114,10 @@ h2.homepage__example__title { } .homepage__title h1 { - font-family: var(--font-family-lora); line-height: 1; @media screen and (max-width: 1023px) { font-size: 3.5em; - letter-spacing: -5px; } @media screen and (min-width: 1024px) { font-size: 6em; diff --git a/src/Command/Watch.php b/src/Command/Watch.php index f0c5c4e..fce35a4 100644 --- a/src/Command/Watch.php +++ b/src/Command/Watch.php @@ -79,16 +79,19 @@ private function restartChildCommand(string $childCommandName): void $this->process = null; } - $this->process = new Process(static function (Process $worker) use ($childCommandName) { - /** - * @psalm-suppress InvalidArgument false positive - * @psalm-suppress InvalidCast false positive - */ - $worker->exec(PHP_BINARY, [ - realpath(DM_APP_ROOT.'/../bin/resonance.php'), - $childCommandName, - ]); - }); + $this->process = new Process( + callback: static function (Process $worker) use ($childCommandName) { + /** + * @psalm-suppress InvalidArgument false positive + * @psalm-suppress InvalidCast false positive + */ + $worker->exec(PHP_BINARY, [ + realpath(DM_APP_ROOT.'/../bin/resonance.php'), + $childCommandName, + ]); + }, + redirect_stdin_and_stdout: false, + ); $this->process->start(); $this->process->wait(false); diff --git a/src/ExpectUrl.php b/src/ExpectUrl.php deleted file mode 100644 index 99e5e2d..0000000 --- a/src/ExpectUrl.php +++ /dev/null @@ -1,14 +0,0 @@ - */ $jsonSchemaValidationResult = $this->jsonSchemaValidator->validate($this->jsonSchema, $data); + $errors = $jsonSchemaValidationResult->errors; - if ($jsonSchemaValidationResult->validator->isValid()) { + if (empty($errors)) { return new InputValidationResult($this->castValidatedData($jsonSchemaValidationResult->data)); } @@ -47,16 +48,10 @@ public function validateData(mixed $data): InputValidationResult */ $validationResult = new InputValidationResult(); - /** - * @var array{ - * property: string, - * message: string - * } $error - */ - foreach ($jsonSchemaValidationResult->validator->getErrors() as $error) { + foreach ($errors as $propertyName => $propertyErrors) { $validationResult->errors->put( - $error['property'], - $error['message'], + $propertyName, + implode("\n", $propertyErrors), ); } diff --git a/src/JsonSchemaValidationResult.php b/src/JsonSchemaValidationResult.php index 50e52c8..0120fd6 100644 --- a/src/JsonSchemaValidationResult.php +++ b/src/JsonSchemaValidationResult.php @@ -4,18 +4,17 @@ namespace Distantmagic\Resonance; -use JsonSchema\Validator; - /** * @template TValidatedData */ readonly class JsonSchemaValidationResult { /** - * @param TValidatedData $data + * @param TValidatedData $data + * @param array> $errors */ public function __construct( - public Validator $validator, public mixed $data, + public array $errors, ) {} } diff --git a/src/JsonSchemaValidator.php b/src/JsonSchemaValidator.php index b9b21a9..ed0a080 100644 --- a/src/JsonSchemaValidator.php +++ b/src/JsonSchemaValidator.php @@ -5,34 +5,57 @@ namespace Distantmagic\Resonance; use Distantmagic\Resonance\Attribute\Singleton; -use JsonSchema\Constraints\Constraint; -use JsonSchema\Constraints\Factory; -use JsonSchema\Validator; +use Opis\JsonSchema\Errors\ErrorFormatter; +use Opis\JsonSchema\Helper; +use Opis\JsonSchema\ValidationResult; +use Opis\JsonSchema\Validator; #[Singleton] readonly class JsonSchemaValidator { - private Factory $factory; + private ErrorFormatter $errorFormatter; + private Validator $validator; - public function __construct(ApplicationConfiguration $applicationConfiguration) + public function __construct() { - $productionFlags = Constraint::CHECK_MODE_TYPE_CAST | Constraint::CHECK_MODE_APPLY_DEFAULTS; - - $this->factory = new Factory( - checkMode: Environment::Development === $applicationConfiguration->environment - ? $productionFlags | Constraint::CHECK_MODE_VALIDATE_SCHEMA - : $productionFlags, - ); + $this->errorFormatter = new ErrorFormatter(); + $this->validator = new Validator(); } public function validate(JsonSchema $jsonSchema, mixed $data): JsonSchemaValidationResult { - $validator = new Validator($this->factory); - $validator->validate($data, $jsonSchema->schema); + /** + * @var bool|object|string $convertedSchema + */ + $convertedSchema = Helper::toJSON($jsonSchema->schema); + + /** + * @var bool|object|string $convertedData + */ + $convertedData = Helper::toJSON($data); + + $validationResult = $this->validator->validate($convertedData, $convertedSchema); return new JsonSchemaValidationResult( - validator: $validator, - data: $data, + data: $convertedData, + errors: $this->formatErrors($validationResult), ); } + + /** + * @return array> + */ + private function formatErrors(ValidationResult $validationResult): array + { + $error = $validationResult->error(); + + if (empty($error)) { + return []; + } + + /** + * @var array> + */ + return $this->errorFormatter->formatKeyed($error); + } } diff --git a/src/SingletonProvider/ConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider.php index 0ed4bd4..6a2fc30 100644 --- a/src/SingletonProvider/ConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider.php @@ -5,11 +5,13 @@ namespace Distantmagic\Resonance\SingletonProvider; use Distantmagic\Resonance\ConfigurationFile; +use Distantmagic\Resonance\JsonSchema; +use Distantmagic\Resonance\JsonSchemaValidationResult; +use Distantmagic\Resonance\JsonSchemaValidator; use Distantmagic\Resonance\PHPProjectFiles; use Distantmagic\Resonance\SingletonContainer; use Distantmagic\Resonance\SingletonProvider; -use Nette\Schema\Processor; -use Nette\Schema\Schema; +use LogicException; /** * @template TObject of object @@ -19,9 +21,11 @@ */ abstract readonly class ConfigurationProvider extends SingletonProvider { + private JsonSchema $jsonSchema; + abstract protected function getConfigurationKey(): string; - abstract protected function getSchema(): Schema; + abstract protected function makeSchema(): JsonSchema; /** * @param TSchema $validatedData @@ -32,8 +36,10 @@ abstract protected function provideConfiguration($validatedData): object; public function __construct( private ConfigurationFile $configurationFile, - private Processor $processor, - ) {} + private JsonSchemaValidator $jsonSchemaValidator, + ) { + $this->jsonSchema = $this->makeSchema(); + } /** * @return TObject @@ -41,14 +47,33 @@ public function __construct( public function provide(SingletonContainer $singletons, PHPProjectFiles $phpProjectFiles): object { /** - * @var TSchema $validatedData + * @var mixed $data explicitly mixed for typechecks + */ + $data = $this->configurationFile->config->get($this->getConfigurationKey()); + + /** + * @var JsonSchemaValidationResult */ - $validatedData = $this->processor->process( - $this->getSchema(), - $this->configurationFile->config->get($this->getConfigurationKey()), - ); + $jsonSchemaValidationResult = $this->jsonSchemaValidator->validate($this->jsonSchema, $data); + + $errors = $jsonSchemaValidationResult->errors; + + if (empty($errors)) { + return $this->provideConfiguration($jsonSchemaValidationResult->data); + } + + $messages = []; + + foreach ($errors as $propertyName => $propertyErrors) { + foreach ($propertyErrors as $propertyError) { + $messages[] = sprintf('"%s": "%s"', $propertyName, $propertyError); + } + } - return $this->provideConfiguration($validatedData); + throw new LogicException(sprintf( + "Encountered validation errors:\n-> %s", + implode("\n-> ", $messages) + )); } public function shouldRegister(): bool diff --git a/src/SingletonProvider/ConfigurationProvider/ApplicationConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/ApplicationConfigurationProvider.php index f8e850f..2ce54d5 100644 --- a/src/SingletonProvider/ConfigurationProvider/ApplicationConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider/ApplicationConfigurationProvider.php @@ -7,10 +7,8 @@ use Distantmagic\Resonance\ApplicationConfiguration; use Distantmagic\Resonance\Attribute\Singleton; use Distantmagic\Resonance\Environment; -use Distantmagic\Resonance\ExpectUrl; +use Distantmagic\Resonance\JsonSchema; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; -use Nette\Schema\Expect; -use Nette\Schema\Schema; /** * @template-extends ConfigurationProvider Expect::anyOf(...Environment::values())->required(), - 'esbuild_metafile' => Expect::string()->min(1)->default('esbuild-meta.json'), - 'scheme' => Expect::anyOf('http', 'https')->default('https'), - 'url' => Expect::string()->required()->assert(new ExpectUrl()), + return new JsonSchema([ + 'type' => 'object', + 'properties' => [ + 'env' => [ + 'type' => 'string', + 'enum' => Environment::values(), + ], + 'esbuild_metafile' => [ + 'type' => 'string', + 'minLength' => 1, + 'default' => 'esbuild-meta.json', + ], + 'scheme' => [ + 'type' => 'string', + 'enum' => ['http', 'https'], + 'default' => 'https', + ], + 'url' => [ + 'type' => 'string', + 'minLength' => 1, + 'format' => 'uri', + ], + ], + 'required' => ['env', 'url'], ]); } diff --git a/src/SingletonProvider/ConfigurationProvider/DatabaseConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/DatabaseConfigurationProvider.php index abbed87..b04319d 100644 --- a/src/SingletonProvider/ConfigurationProvider/DatabaseConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider/DatabaseConfigurationProvider.php @@ -8,9 +8,8 @@ use Distantmagic\Resonance\DatabaseConfiguration; use Distantmagic\Resonance\DatabaseConnectionPoolConfiguration; use Distantmagic\Resonance\DatabaseConnectionPoolDriverName; +use Distantmagic\Resonance\JsonSchema; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; -use Nette\Schema\Expect; -use Nette\Schema\Schema; /** * @template-extends ConfigurationProvider< @@ -37,24 +36,67 @@ protected function getConfigurationKey(): string return 'database'; } - protected function getSchema(): Schema + protected function makeSchema(): JsonSchema { - $keySchema = Expect::string()->min(1)->required(); + $valueSchema = [ + 'type' => 'object', + 'properties' => [ + 'database' => [ + 'type' => 'string', + 'minLength' => 1, + ], + 'driver' => [ + 'type' => 'string', + 'enum' => DatabaseConnectionPoolDriverName::values(), + ], + 'host' => [ + 'type' => ['string', 'null'], + 'minLength' => 1, + 'default' => null, + ], + 'log_queries' => [ + 'type' => 'boolean', + ], + 'password' => [ + 'type' => 'string', + ], + 'pool_prefill' => [ + 'type' => 'boolean', + ], + 'pool_size' => [ + 'type' => 'integer', + 'minimum' => 1, + ], + 'port' => [ + 'type' => 'integer', + 'minimum' => 1, + 'maximum' => 65535, + 'default' => 3306, + ], + 'unix_socket' => [ + 'type' => ['string', 'null'], + 'default' => null, + ], + 'username' => [ + 'type' => 'string', + 'minLength' => 1, + ], + ], + 'required' => [ + 'database', + 'driver', + 'log_queries', + 'password', + 'pool_prefill', + 'pool_size', + 'username', + ], + ]; - $valueSchema = Expect::structure([ - 'database' => Expect::string()->min(1)->required(), - 'driver' => Expect::anyOf(...DatabaseConnectionPoolDriverName::values())->required(), - 'host' => Expect::string()->min(1)->nullable()->default(null), - 'log_queries' => Expect::bool()->required(), - 'password' => Expect::string()->required(), - 'pool_prefill' => Expect::bool()->required(), - 'pool_size' => Expect::int()->min(1)->required(), - 'port' => Expect::int()->min(1)->max(65535)->default(3306), - 'unix_socket' => Expect::string()->nullable()->default(null), - 'username' => Expect::string()->min(1)->required(), + return new JsonSchema([ + 'type' => 'object', + 'additionalProperties' => $valueSchema, ]); - - return Expect::arrayOf($valueSchema, $keySchema); } protected function provideConfiguration($validatedData): DatabaseConfiguration diff --git a/src/SingletonProvider/ConfigurationProvider/OAuth2ConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/OAuth2ConfigurationProvider.php index 1a7b16c..58fab3f 100644 --- a/src/SingletonProvider/ConfigurationProvider/OAuth2ConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider/OAuth2ConfigurationProvider.php @@ -7,11 +7,10 @@ use Defuse\Crypto\Key; use Distantmagic\Resonance\Attribute\Singleton; use Distantmagic\Resonance\Feature; +use Distantmagic\Resonance\JsonSchema; use Distantmagic\Resonance\OAuth2Configuration; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use League\OAuth2\Server\CryptKey; -use Nette\Schema\Expect; -use Nette\Schema\Schema; use RuntimeException; use Swoole\Coroutine; @@ -37,16 +36,44 @@ protected function getConfigurationKey(): string return 'oauth2'; } - protected function getSchema(): Schema + protected function makeSchema(): JsonSchema { - return Expect::structure([ - 'encryption_key' => Expect::string()->min(1)->required(), - 'jwt_signing_key_passphrase' => Expect::string()->default(null), - 'jwt_signing_key_private' => Expect::string()->min(1)->required(), - 'jwt_signing_key_public' => Expect::string()->min(1)->required(), - 'session_key_authorization_request' => Expect::string()->min(1)->default('oauth2.authorization_request'), - 'session_key_pkce' => Expect::string()->min(1)->default('oauth2.pkce'), - 'session_key_state' => Expect::string()->min(1)->default('oauth2.state'), + return new JsonSchema([ + 'type' => 'object', + 'properties' => [ + 'encryption_key' => [ + 'type' => 'string', + 'minLength' => 1, + ], + 'jwt_signing_key_passphrase' => [ + 'type' => 'string', + 'default' => null, + ], + 'jwt_signing_key_private' => [ + 'type' => 'string', + 'minLength' => 1, + ], + 'jwt_signing_key_public' => [ + 'type' => 'string', + 'minLength' => 1, + ], + 'session_key_authorization_request' => [ + 'type' => 'string', + 'minLength' => 1, + 'default' => 'oauth2.authorization_request', + ], + 'session_key_pkce' => [ + 'type' => 'string', + 'minLength' => 1, + 'default' => 'oauth2.pkce', + ], + 'session_key_state' => [ + 'type' => 'string', + 'minLength' => 1, + 'default' => 'oauth2.state', + ], + ], + 'required' => ['encryption_key', 'jwt_signing_key_private', 'jwt_signing_key_public'], ]); } diff --git a/src/SingletonProvider/ConfigurationProvider/OpenAPIConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/OpenAPIConfigurationProvider.php index cb67d42..3014813 100644 --- a/src/SingletonProvider/ConfigurationProvider/OpenAPIConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider/OpenAPIConfigurationProvider.php @@ -5,10 +5,9 @@ namespace Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\Attribute\Singleton; +use Distantmagic\Resonance\JsonSchema; use Distantmagic\Resonance\OpenAPIConfiguration; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; -use Nette\Schema\Expect; -use Nette\Schema\Schema; /** * @template-extends ConfigurationProvider Expect::string()->min(1)->required(), - 'title' => Expect::string()->min(1)->required(), - 'version' => Expect::string()->min(1)->required(), + return new JsonSchema([ + 'type' => 'object', + 'properties' => [ + 'description' => [ + 'type' => 'string', + 'minLength' => 1, + ], + 'title' => [ + 'type' => 'string', + 'minLength' => 1, + ], + 'version' => [ + 'type' => 'string', + 'minLength' => 1, + ], + ], + 'required' => ['description', 'title', 'version'], ]); } diff --git a/src/SingletonProvider/ConfigurationProvider/RedisConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/RedisConfigurationProvider.php index 950377e..3f8c1e7 100644 --- a/src/SingletonProvider/ConfigurationProvider/RedisConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider/RedisConfigurationProvider.php @@ -5,11 +5,10 @@ namespace Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\Attribute\Singleton; +use Distantmagic\Resonance\JsonSchema; use Distantmagic\Resonance\RedisConfiguration; use Distantmagic\Resonance\RedisConnectionPoolConfiguration; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; -use Nette\Schema\Expect; -use Nette\Schema\Schema; /** * @template-extends ConfigurationProvider< @@ -34,22 +33,59 @@ protected function getConfigurationKey(): string return 'redis'; } - protected function getSchema(): Schema + protected function makeSchema(): JsonSchema { - $keySchema = Expect::string()->min(1)->required(); + $valueSchema = [ + 'type' => 'object', + 'properties' => [ + 'db_index' => [ + 'type' => 'integer', + 'minimum' => 0, + ], + 'host' => [ + 'type' => 'string', + 'minLength' => 1, + ], + 'password' => [ + 'type' => 'string', + ], + 'pool_prefill' => [ + 'type' => 'boolean', + ], + 'pool_size' => [ + 'type' => 'integer', + 'minimum' => 1, + ], + 'port' => [ + 'type' => 'integer', + 'minimum' => 1, + 'maximum' => 65535, + ], + 'prefix' => [ + 'type' => 'string', + 'minLength' => 1, + ], + 'timeout' => [ + 'type' => 'integer', + 'minimum' => 0, + ], + ], + 'required' => [ + 'db_index', + 'host', + 'password', + 'pool_prefill', + 'pool_size', + 'port', + 'prefix', + 'timeout', + ], + ]; - $valueSchema = Expect::structure([ - 'db_index' => Expect::int()->min(0)->required(), - 'host' => Expect::string()->min(1)->required(), - 'password' => Expect::string()->required(), - 'pool_prefill' => Expect::bool()->required(), - 'pool_size' => Expect::int()->min(1)->required(), - 'port' => Expect::int()->min(1)->max(65535)->required(), - 'prefix' => Expect::string()->min(1)->required(), - 'timeout' => Expect::int()->min(0)->required(), + return new JsonSchema([ + 'type' => 'object', + 'additionalProperties' => $valueSchema, ]); - - return Expect::arrayOf($valueSchema, $keySchema); } protected function provideConfiguration($validatedData): RedisConfiguration diff --git a/src/SingletonProvider/ConfigurationProvider/SessionConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/SessionConfigurationProvider.php index 2d17394..6651093 100644 --- a/src/SingletonProvider/ConfigurationProvider/SessionConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider/SessionConfigurationProvider.php @@ -6,12 +6,11 @@ use Distantmagic\Resonance\Attribute\Singleton; use Distantmagic\Resonance\ConfigurationFile; +use Distantmagic\Resonance\JsonSchema; +use Distantmagic\Resonance\JsonSchemaValidator; use Distantmagic\Resonance\RedisConfiguration; use Distantmagic\Resonance\SessionConfiguration; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; -use Nette\Schema\Expect; -use Nette\Schema\Processor; -use Nette\Schema\Schema; /** * @template-extends ConfigurationProviderredisConfiguration @@ -46,11 +45,28 @@ protected function getSchema(): Schema ->toArray() ; - return Expect::structure([ - 'cookie_lifespan' => Expect::int()->min(1)->required(), - 'cookie_name' => Expect::string()->min(1)->required(), - 'cookie_samesite' => Expect::anyOf('lax', 'none', 'strict')->default('lax'), - 'redis_connection_pool' => Expect::anyOf(...$redisConnectionPools), + return new JsonSchema([ + 'type' => 'object', + 'properties' => [ + 'cookie_lifespan' => [ + 'type' => 'integer', + 'minimum' => 1, + ], + 'cookie_name' => [ + 'type' => 'string', + 'minLength' => 1, + ], + 'cookie_samesite' => [ + 'type' => 'string', + 'enum' => ['lax', 'none', 'strict'], + 'default' => 'lax', + ], + 'redis_connection_pool' => [ + 'type' => 'string', + 'enum' => $redisConnectionPools, + ], + ], + 'required' => ['cookie_lifespan', 'cookie_name'], ]); } diff --git a/src/SingletonProvider/ConfigurationProvider/StaticPageConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/StaticPageConfigurationProvider.php index b041f33..b5f87b6 100644 --- a/src/SingletonProvider/ConfigurationProvider/StaticPageConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider/StaticPageConfigurationProvider.php @@ -5,10 +5,9 @@ namespace Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\Attribute\Singleton; +use Distantmagic\Resonance\JsonSchema; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\StaticPageConfiguration; -use Nette\Schema\Expect; -use Nette\Schema\Schema; /** * @template-extends ConfigurationProvider Expect::string()->min(1)->required(), - 'esbuild_metafile' => Expect::string()->min(1)->required(), - 'input_directory' => Expect::string()->min(1)->required(), - 'output_directory' => Expect::string()->min(1)->required(), - 'sitemap' => Expect::string()->min(1)->required(), + return new JsonSchema([ + 'type' => 'object', + 'properties' => [ + 'base_url' => [ + 'type' => 'string', + 'minLength' => 1, + 'required' => true, + ], + 'esbuild_metafile' => [ + 'type' => 'string', + 'minLength' => 1, + 'required' => true, + ], + 'input_directory' => [ + 'type' => 'string', + 'minLength' => 1, + 'required' => true, + ], + 'output_directory' => [ + 'type' => 'string', + 'minLength' => 1, + 'required' => true, + ], + 'sitemap' => [ + 'type' => 'string', + 'minLength' => 1, + 'required' => true, + ], + ], + 'required' => ['base_url', 'esbuild_metafile', 'input_directory', 'output_directory', 'sitemap'], ]); } diff --git a/src/SingletonProvider/ConfigurationProvider/SwooleConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/SwooleConfigurationProvider.php index ca82c0b..ade3163 100644 --- a/src/SingletonProvider/ConfigurationProvider/SwooleConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider/SwooleConfigurationProvider.php @@ -5,10 +5,9 @@ namespace Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\Attribute\Singleton; +use Distantmagic\Resonance\JsonSchema; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\SwooleConfiguration; -use Nette\Schema\Expect; -use Nette\Schema\Schema; /** * @template-extends ConfigurationProvider Expect::string()->min(1)->required(), - 'log_level' => Expect::string()->min(1)->required(), - 'log_requests' => Expect::bool()->default(false), - 'port' => Expect::int()->min(1)->max(65535)->required(), - 'ssl_cert_file' => Expect::string()->min(1)->required(), - 'ssl_key_file' => Expect::string()->min(1)->required(), + return new JsonSchema([ + 'type' => 'object', + 'properties' => [ + 'host' => [ + 'type' => 'string', + 'minLength' => 1, + ], + 'log_level' => [ + 'type' => 'string', + 'minLength' => 1, + ], + 'log_requests' => [ + 'type' => 'boolean', + 'default' => false, + ], + 'port' => [ + 'type' => 'integer', + 'minimum' => 1, + 'maximum' => 65535, + ], + 'ssl_cert_file' => [ + 'type' => 'string', + 'minLength' => 1, + ], + 'ssl_key_file' => [ + 'type' => 'string', + 'minLength' => 1, + ], + ], + 'required' => ['host', 'log_level', 'port', 'ssl_cert_file', 'ssl_key_file'], ]); } diff --git a/src/SingletonProvider/ConfigurationProvider/TranslatorConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/TranslatorConfigurationProvider.php index 7ecacf7..01519a5 100644 --- a/src/SingletonProvider/ConfigurationProvider/TranslatorConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider/TranslatorConfigurationProvider.php @@ -5,10 +5,9 @@ namespace Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\Attribute\Singleton; +use Distantmagic\Resonance\JsonSchema; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\TranslatorConfiguration; -use Nette\Schema\Expect; -use Nette\Schema\Schema; /** * @template-extends ConfigurationProvider Expect::string()->min(1)->required(), - 'default_primary_language' => Expect::string()->min(1)->required(), + return new JsonSchema([ + 'type' => 'object', + 'properties' => [ + 'base_directory' => [ + 'type' => 'string', + 'minLength' => 1, + ], + 'default_primary_language' => [ + 'type' => 'string', + 'minLength' => 1, + ], + ], + 'required' => ['base_directory', 'default_primary_language'], ]); }