diff --git a/docs/pages/index.md b/docs/pages/index.md index 93f35f48847347bfaad3609460a5c6314f873071..ff1078ac44ed549d5561fa7773e7875730118428 100644 --- a/docs/pages/index.md +++ b/docs/pages/index.md @@ -105,11 +105,9 @@ description: > #[Singleton(collection: SingletonCollection::WebSocketRPCResponder)] final readonly class EchoResponder extends WebSocketRPCResponder { - public function getSchema(): JsonSchema + public function getConstraint(): Constraint { - return new JsonSchema([ - 'type' => 'string', - ]); + return new StringConstraint(); } public function onRequest( diff --git a/src/Constraint/AnyConstraint.php b/src/Constraint/AnyConstraint.php index 0baab197962c60fb89649ac9aca82c17a9f8e332..7fa48a30fd16e019425bde5610ed13e91b6df2e7 100644 --- a/src/Constraint/AnyConstraint.php +++ b/src/Constraint/AnyConstraint.php @@ -12,7 +12,7 @@ use Distantmagic\Resonance\ConstraintResult; use Distantmagic\Resonance\ConstraintResultStatus; use stdClass; -readonly class AnyConstraint extends Constraint +final readonly class AnyConstraint extends Constraint { public function default(mixed $defaultValue): self { diff --git a/src/Constraint/BooleanConstraint.php b/src/Constraint/BooleanConstraint.php new file mode 100644 index 0000000000000000000000000000000000000000..8b58e9aa10a95fce6e5b7afd61bb38c2a0de7171 --- /dev/null +++ b/src/Constraint/BooleanConstraint.php @@ -0,0 +1,68 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance\Constraint; + +use Distantmagic\Resonance\Constraint; +use Distantmagic\Resonance\ConstraintDefaultValue; +use Distantmagic\Resonance\ConstraintPath; +use Distantmagic\Resonance\ConstraintReason; +use Distantmagic\Resonance\ConstraintResult; +use Distantmagic\Resonance\ConstraintResultStatus; + +final readonly class BooleanConstraint extends Constraint +{ + public function default(mixed $defaultValue): self + { + return new self( + defaultValue: new ConstraintDefaultValue($defaultValue), + isNullable: $this->isNullable, + isRequired: $this->isRequired, + ); + } + + public function nullable(): self + { + return new self( + defaultValue: $this->defaultValue ?? new ConstraintDefaultValue(null), + isNullable: true, + isRequired: $this->isRequired, + ); + } + + public function optional(): self + { + return new self( + defaultValue: $this->defaultValue, + isNullable: $this->isNullable, + isRequired: false, + ); + } + + protected function doConvertToJsonSchema(): array + { + return [ + 'type' => 'boolean', + ]; + } + + protected function doValidate(mixed $notValidatedData, ConstraintPath $path): ConstraintResult + { + if (!is_bool($notValidatedData)) { + return new ConstraintResult( + castedData: $notValidatedData, + path: $path, + reason: ConstraintReason::InvalidDataType, + status: ConstraintResultStatus::Invalid, + ); + } + + return new ConstraintResult( + castedData: $notValidatedData, + path: $path, + reason: ConstraintReason::Ok, + status: ConstraintResultStatus::Valid, + ); + } +} diff --git a/src/Constraint/BooleanConstraintTest.php b/src/Constraint/BooleanConstraintTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ff422df175d7b0fdb143207e0cc8314f9c2e3c08 --- /dev/null +++ b/src/Constraint/BooleanConstraintTest.php @@ -0,0 +1,51 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance\Constraint; + +use PHPUnit\Framework\TestCase; + +/** + * @coversNothing + * + * @internal + */ +final class BooleanConstraintTest extends TestCase +{ + public function test_is_converted_optionally_to_json_schema(): void + { + $constraint = new BooleanConstraint(); + + self::assertEquals([ + 'type' => 'boolean', + ], $constraint->optional()->toJsonSchema()); + } + + public function test_is_converted_to_json_schema(): void + { + $constraint = new BooleanConstraint(); + + self::assertEquals([ + 'type' => 'boolean', + ], $constraint->toJsonSchema()); + } + + public function test_nullable_is_converted_to_json_schema(): void + { + $constraint = new BooleanConstraint(); + + self::assertEquals([ + 'type' => ['null', 'boolean'], + 'default' => null, + ], $constraint->nullable()->toJsonSchema()); + } + + public function test_validates(): void + { + $constraint = new BooleanConstraint(); + + self::assertTrue($constraint->validate(false)->status->isValid()); + self::assertFalse($constraint->validate(5.5)->status->isValid()); + } +} diff --git a/src/Constraint/ConstConstraint.php b/src/Constraint/ConstConstraint.php index 2bfc053ba4b55a1250ff4efc7d4e866e3ca894ce..9f1e3904c05a9844bcd5cf2855866ee83459bef4 100644 --- a/src/Constraint/ConstConstraint.php +++ b/src/Constraint/ConstConstraint.php @@ -11,7 +11,7 @@ use Distantmagic\Resonance\ConstraintResult; use Distantmagic\Resonance\ConstraintResultStatus; use LogicException; -readonly class ConstConstraint extends Constraint +final readonly class ConstConstraint extends Constraint { /** * @param float|int|non-empty-string $constValue diff --git a/src/Constraint/EnumConstraint.php b/src/Constraint/EnumConstraint.php index bb14cc29abfcc40b8008db08692b52db9ab60bd0..ee10aab8261ca55412205212fcfd4f142dcdd850 100644 --- a/src/Constraint/EnumConstraint.php +++ b/src/Constraint/EnumConstraint.php @@ -11,10 +11,10 @@ use Distantmagic\Resonance\ConstraintReason; use Distantmagic\Resonance\ConstraintResult; use Distantmagic\Resonance\ConstraintResultStatus; -readonly class EnumConstraint extends Constraint +final readonly class EnumConstraint extends Constraint { /** - * @param array<non-empty-string> $values + * @param array<string>|list<string> $values */ public function __construct( public array $values, diff --git a/src/Constraint/IntegerConstraint.php b/src/Constraint/IntegerConstraint.php index 764f0b45a3875a097eb481ea38065e4fe9cfaae2..624488f67569db683b981b658811e26a6527888e 100644 --- a/src/Constraint/IntegerConstraint.php +++ b/src/Constraint/IntegerConstraint.php @@ -11,7 +11,7 @@ use Distantmagic\Resonance\ConstraintReason; use Distantmagic\Resonance\ConstraintResult; use Distantmagic\Resonance\ConstraintResultStatus; -readonly class IntegerConstraint extends Constraint +final readonly class IntegerConstraint extends Constraint { public function default(mixed $defaultValue): self { @@ -49,7 +49,7 @@ readonly class IntegerConstraint extends Constraint protected function doValidate(mixed $notValidatedData, ConstraintPath $path): ConstraintResult { - if (!is_int($notValidatedData)) { + if (!is_numeric($notValidatedData) || (int) $notValidatedData != $notValidatedData) { return new ConstraintResult( castedData: $notValidatedData, path: $path, @@ -59,7 +59,7 @@ readonly class IntegerConstraint extends Constraint } return new ConstraintResult( - castedData: $notValidatedData, + castedData: (int) $notValidatedData, path: $path, reason: ConstraintReason::Ok, status: ConstraintResultStatus::Valid, diff --git a/src/Constraint/IntegerConstraintTest.php b/src/Constraint/IntegerConstraintTest.php index 178148c45aeccd0ec3a89640230e5431fbf1229a..1fb70bf4884ce080ad8b73bfdc7771c1ec3edb77 100644 --- a/src/Constraint/IntegerConstraintTest.php +++ b/src/Constraint/IntegerConstraintTest.php @@ -41,11 +41,17 @@ final class IntegerConstraintTest extends TestCase ], $constraint->nullable()->toJsonSchema()); } - public function test_validates(): void + public function test_validates_failure(): void { $constraint = new IntegerConstraint(); - self::assertTrue($constraint->validate(5)->status->isValid()); self::assertFalse($constraint->validate(5.5)->status->isValid()); } + + public function test_validates_ok(): void + { + $constraint = new IntegerConstraint(); + + self::assertTrue($constraint->validate(5)->status->isValid()); + } } diff --git a/src/Constraint/MapConstraint.php b/src/Constraint/MapConstraint.php new file mode 100644 index 0000000000000000000000000000000000000000..837067b6ba5aa48b3c05e2b66d4e9dad0dc0637f --- /dev/null +++ b/src/Constraint/MapConstraint.php @@ -0,0 +1,131 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance\Constraint; + +use Distantmagic\Resonance\Constraint; +use Distantmagic\Resonance\ConstraintDefaultValue; +use Distantmagic\Resonance\ConstraintPath; +use Distantmagic\Resonance\ConstraintReason; +use Distantmagic\Resonance\ConstraintResult; +use Distantmagic\Resonance\ConstraintResultStatus; + +final readonly class MapConstraint extends Constraint +{ + public function __construct( + public Constraint $valueConstraint, + ?ConstraintDefaultValue $defaultValue = null, + bool $isNullable = false, + bool $isRequired = true, + ) { + parent::__construct( + defaultValue: $defaultValue, + isNullable: $isNullable, + isRequired: $isRequired, + ); + } + + public function default(mixed $defaultValue): self + { + return new self( + valueConstraint: $this->valueConstraint, + defaultValue: new ConstraintDefaultValue($defaultValue), + isNullable: $this->isNullable, + isRequired: $this->isRequired, + ); + } + + public function nullable(): self + { + return new self( + valueConstraint: $this->valueConstraint, + defaultValue: $this->defaultValue ?? new ConstraintDefaultValue(null), + isNullable: true, + isRequired: $this->isRequired, + ); + } + + public function optional(): self + { + return new self( + valueConstraint: $this->valueConstraint, + defaultValue: $this->defaultValue, + isNullable: $this->isNullable, + isRequired: false, + ); + } + + protected function doConvertToJsonSchema(): array + { + return [ + 'type' => 'object', + 'additionalProperties' => $this->valueConstraint->toJsonSchema(), + ]; + } + + protected function doValidate(mixed $notValidatedData, ConstraintPath $path): ConstraintResult + { + if (!is_array($notValidatedData)) { + return new ConstraintResult( + castedData: $notValidatedData, + path: $path, + reason: ConstraintReason::InvalidDataType, + status: ConstraintResultStatus::Invalid, + ); + } + + $ret = []; + + /** + * @var list<ConstraintResult> + */ + $invalidChildStatuses = []; + + /** + * @var mixed $notValidatedKey explicitly mixed for typechecks + * @var mixed $notValidatedValue explicitly mixed for typechecks + */ + foreach ($notValidatedData as $notValidatedKey => $notValidatedValue) { + if (!is_string($notValidatedKey)) { + $invalidChildStatuses[] = new ConstraintResult( + castedData: null, + path: $path, + reason: ConstraintReason::InvalidDataType, + status: ConstraintResultStatus::Invalid, + ); + } else { + $childResult = $this->valueConstraint->validate( + notValidatedData: $notValidatedValue, + path: $path->fork($notValidatedKey) + ); + + if ($childResult->status->isValid()) { + /** + * @var mixed explicitly mixed for typechecks + */ + $ret[$notValidatedKey] = $childResult->castedData; + } else { + $invalidChildStatuses[] = $childResult; + } + } + } + + if (!empty($invalidChildStatuses)) { + return new ConstraintResult( + castedData: $notValidatedData, + nested: $invalidChildStatuses, + path: $path, + reason: ConstraintReason::InvalidNestedConstraint, + status: ConstraintResultStatus::Invalid, + ); + } + + return new ConstraintResult( + castedData: $ret, + path: $path, + reason: ConstraintReason::Ok, + status: ConstraintResultStatus::Valid, + ); + } +} diff --git a/src/Constraint/MapConstraintTest.php b/src/Constraint/MapConstraintTest.php new file mode 100644 index 0000000000000000000000000000000000000000..c63e5744eeae7c62707c0c4d0c9b26c03140579a --- /dev/null +++ b/src/Constraint/MapConstraintTest.php @@ -0,0 +1,89 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance\Constraint; + +use PHPUnit\Framework\TestCase; + +/** + * @coversNothing + * + * @internal + */ +final class MapConstraintTest extends TestCase +{ + public function test_is_converted_optionally_to_json_schema(): void + { + $constraint = new MapConstraint( + valueConstraint: new StringConstraint() + ); + self::assertEquals([ + 'type' => 'object', + 'additionalProperties' => [ + 'type' => 'string', + 'minLength' => 1, + ], + ], $constraint->optional()->toJsonSchema()); + } + + public function test_is_converted_to_json_schema(): void + { + $constraint = new MapConstraint( + valueConstraint: new StringConstraint() + ); + self::assertEquals([ + 'type' => 'object', + 'additionalProperties' => [ + 'type' => 'string', + 'minLength' => 1, + ], + ], $constraint->optional()->toJsonSchema()); + } + + public function test_nullable_is_converted_to_json_schema(): void + { + $constraint = new MapConstraint( + valueConstraint: new StringConstraint() + ); + self::assertEquals([ + 'type' => ['null', 'object'], + 'additionalProperties' => [ + 'type' => 'string', + 'minLength' => 1, + ], + 'default' => null, + ], $constraint->nullable()->toJsonSchema()); + } + + public function test_validates_fail(): void + { + $constraint = new MapConstraint( + valueConstraint: new StringConstraint() + ); + + $validatedResult = $constraint->validate([ + 'aaa' => 'hi', + 'bbb' => 5, + ]); + self::assertFalse($validatedResult->status->isValid()); + self::assertEquals([ + '' => 'invalid_nested_constraint', + 'bbb' => 'invalid_data_type', + ], $validatedResult->getErrors()->toArray()); + } + + public function test_validates_ok(): void + { + $constraint = new MapConstraint( + valueConstraint: new StringConstraint() + ); + + $validatedResult = $constraint->validate([ + 'aaa' => 'hi', + 'bbb' => 'foo', + ]); + + self::assertTrue($validatedResult->status->isValid()); + } +} diff --git a/src/Constraint/NumberConstraint.php b/src/Constraint/NumberConstraint.php index fc39114dffab53b9517fe781431b13cf95931ab4..4f1a9abb03b39ee68a056026fe1eef99f009085f 100644 --- a/src/Constraint/NumberConstraint.php +++ b/src/Constraint/NumberConstraint.php @@ -11,7 +11,7 @@ use Distantmagic\Resonance\ConstraintReason; use Distantmagic\Resonance\ConstraintResult; use Distantmagic\Resonance\ConstraintResultStatus; -readonly class NumberConstraint extends Constraint +final readonly class NumberConstraint extends Constraint { public function default(mixed $defaultValue): self { diff --git a/src/Constraint/StringConstraint.php b/src/Constraint/StringConstraint.php index 47432d07399406d32bdaec2b23484a9c2d43737b..4572046fee85c29b7ac41d38b7a294e6fb85cd6a 100644 --- a/src/Constraint/StringConstraint.php +++ b/src/Constraint/StringConstraint.php @@ -13,7 +13,7 @@ use Distantmagic\Resonance\ConstraintResultStatus; use Distantmagic\Resonance\ConstraintStringFormat; use RuntimeException; -readonly class StringConstraint extends Constraint +final readonly class StringConstraint extends Constraint { public function __construct( public ?ConstraintStringFormat $format = null, diff --git a/src/ConstraintSourceInterface.php b/src/ConstraintSourceInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..10a4ef4c3cb590f5a83c3615ef40d80ecf050693 --- /dev/null +++ b/src/ConstraintSourceInterface.php @@ -0,0 +1,10 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +interface ConstraintSourceInterface +{ + public function getConstraint(): Constraint; +} diff --git a/src/ConstraintValidationException.php b/src/ConstraintValidationException.php new file mode 100644 index 0000000000000000000000000000000000000000..e09148d4b0921c171e14eddd3463c0e358db56ae --- /dev/null +++ b/src/ConstraintValidationException.php @@ -0,0 +1,30 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +use RuntimeException; + +class ConstraintValidationException extends RuntimeException +{ + public function __construct( + string $constraintId, + ConstraintResult $constraintResult, + ) { + $errors = $constraintResult->getErrors(); + $message = []; + + foreach ($errors as $name => $errorCode) { + $message[] = sprintf('"%s" -> %s', $name, $errorCode); + } + + var_dump($constraintResult->castedData); + + parent::__construct(sprintf( + "%s:\n%s", + $constraintId, + implode("\n", $message), + )); + } +} diff --git a/src/SingletonProvider/ConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider.php index 180e202d6f965ca4daf617df1a6d7e1f7fe17882..dbb7d6bb839ef7e0bd5d0d34fc55cdaddd97e483 100644 --- a/src/SingletonProvider/ConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider.php @@ -5,10 +5,8 @@ declare(strict_types=1); namespace Distantmagic\Resonance\SingletonProvider; use Distantmagic\Resonance\ConfigurationFile; -use Distantmagic\Resonance\JsonSchemaSourceInterface; -use Distantmagic\Resonance\JsonSchemaValidationException; -use Distantmagic\Resonance\JsonSchemaValidationResult; -use Distantmagic\Resonance\JsonSchemaValidator; +use Distantmagic\Resonance\ConstraintSourceInterface; +use Distantmagic\Resonance\ConstraintValidationException; use Distantmagic\Resonance\PHPProjectFiles; use Distantmagic\Resonance\SingletonContainer; use Distantmagic\Resonance\SingletonProvider; @@ -19,7 +17,7 @@ use Distantmagic\Resonance\SingletonProvider; * * @template-extends SingletonProvider<TObject> */ -abstract readonly class ConfigurationProvider extends SingletonProvider implements JsonSchemaSourceInterface +abstract readonly class ConfigurationProvider extends SingletonProvider implements ConstraintSourceInterface { abstract protected function getConfigurationKey(): string; @@ -32,7 +30,6 @@ abstract readonly class ConfigurationProvider extends SingletonProvider implemen public function __construct( private ConfigurationFile $configurationFile, - private JsonSchemaValidator $jsonSchemaValidator, ) {} /** @@ -45,18 +42,19 @@ abstract readonly class ConfigurationProvider extends SingletonProvider implemen */ $data = $this->configurationFile->config->get($this->getConfigurationKey()); - /** - * @var JsonSchemaValidationResult<TSchema> - */ - $jsonSchemaValidationResult = $this->jsonSchemaValidator->validate($this, $data); - - $errors = $jsonSchemaValidationResult->errors; + $constraintResult = $this->getConstraint()->validate($data); - if (empty($errors)) { - return $this->provideConfiguration($jsonSchemaValidationResult->data); + if ($constraintResult->status->isValid()) { + /** + * @var TSchema $constraintResult->castedData + */ + return $this->provideConfiguration($constraintResult->castedData); } - throw new JsonSchemaValidationException($errors); + throw new ConstraintValidationException( + $this->getConfigurationKey(), + $constraintResult, + ); } public function shouldRegister(): bool diff --git a/src/SingletonProvider/ConfigurationProvider/ApplicationConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/ApplicationConfigurationProvider.php index d3280e51e07651c43ea219960a336ef691b11153..8fb7c2aea401c215b4a78ebe1e174b192fb80205 100644 --- a/src/SingletonProvider/ConfigurationProvider/ApplicationConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider/ApplicationConfigurationProvider.php @@ -6,13 +6,16 @@ namespace Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\ApplicationConfiguration; use Distantmagic\Resonance\Attribute\Singleton; +use Distantmagic\Resonance\Constraint; +use Distantmagic\Resonance\Constraint\EnumConstraint; +use Distantmagic\Resonance\Constraint\ObjectConstraint; +use Distantmagic\Resonance\Constraint\StringConstraint; use Distantmagic\Resonance\Environment; -use Distantmagic\Resonance\JsonSchema; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use RuntimeException; /** - * @template-extends ConfigurationProvider<ApplicationConfiguration, object{ + * @template-extends ConfigurationProvider<ApplicationConfiguration, array{ * env: string, * esbuild_metafile: non-empty-string, * scheme: non-empty-string, @@ -22,33 +25,16 @@ use RuntimeException; #[Singleton(provides: ApplicationConfiguration::class)] final readonly class ApplicationConfigurationProvider extends ConfigurationProvider { - public function getSchema(): JsonSchema + public function getConstraint(): Constraint { - 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', - ], + return new ObjectConstraint( + properties: [ + 'env' => new EnumConstraint(Environment::values()), + 'esbuild_metafile' => (new StringConstraint())->default('esbuild-meta.json'), + 'scheme' => (new EnumConstraint(['http', 'https']))->default('https'), + 'url' => new StringConstraint(), ], - 'required' => ['env', 'url'], - ]); + ); } protected function getConfigurationKey(): string @@ -58,16 +44,16 @@ final readonly class ApplicationConfigurationProvider extends ConfigurationProvi protected function provideConfiguration($validatedData): ApplicationConfiguration { - $url = rtrim($validatedData->url, '/'); + $url = rtrim($validatedData['url'], '/'); if (empty($url)) { throw new RuntimeException('URL cannot be an empty string'); } return new ApplicationConfiguration( - environment: Environment::from($validatedData->env), - esbuildMetafile: DM_ROOT.'/'.$validatedData->esbuild_metafile, - scheme: $validatedData->scheme, + environment: Environment::from($validatedData['env']), + esbuildMetafile: DM_ROOT.'/'.$validatedData['esbuild_metafile'], + scheme: $validatedData['scheme'], url: $url, ); } diff --git a/src/SingletonProvider/ConfigurationProvider/DatabaseConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/DatabaseConfigurationProvider.php index d7cb2e2d25c23fe9930b258388509294959e0b55..a33537151a3f1af02e509c89391d5d073fdf3c41 100644 --- a/src/SingletonProvider/ConfigurationProvider/DatabaseConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider/DatabaseConfigurationProvider.php @@ -5,16 +5,22 @@ declare(strict_types=1); namespace Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\Attribute\Singleton; +use Distantmagic\Resonance\Constraint; +use Distantmagic\Resonance\Constraint\BooleanConstraint; +use Distantmagic\Resonance\Constraint\EnumConstraint; +use Distantmagic\Resonance\Constraint\IntegerConstraint; +use Distantmagic\Resonance\Constraint\MapConstraint; +use Distantmagic\Resonance\Constraint\ObjectConstraint; +use Distantmagic\Resonance\Constraint\StringConstraint; use Distantmagic\Resonance\DatabaseConfiguration; use Distantmagic\Resonance\DatabaseConnectionPoolConfiguration; use Distantmagic\Resonance\DatabaseConnectionPoolDriverName; -use Distantmagic\Resonance\JsonSchema; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; /** * @template-extends ConfigurationProvider< * DatabaseConfiguration, - * array<non-empty-string, object{ + * array<non-empty-string, array{ * database: string, * driver: string, * host: non-empty-string, @@ -31,67 +37,24 @@ use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; #[Singleton(provides: DatabaseConfiguration::class)] final readonly class DatabaseConfigurationProvider extends ConfigurationProvider { - public function getSchema(): JsonSchema + public function getConstraint(): Constraint { - $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, - ], + $valueConstraint = new ObjectConstraint( + properties: [ + 'database' => new StringConstraint(), + 'driver' => new EnumConstraint(DatabaseConnectionPoolDriverName::values()), + 'host' => (new StringConstraint())->default(null), + 'log_queries' => new BooleanConstraint(), + 'password' => (new StringConstraint())->nullable(), + 'pool_prefill' => (new BooleanConstraint())->default(true), + 'pool_size' => new IntegerConstraint(), + 'port' => (new IntegerConstraint())->nullable()->default(3306), + 'unix_socket' => (new StringConstraint())->nullable(), + 'username' => new StringConstraint(), ], - 'required' => [ - 'database', - 'driver', - 'log_queries', - 'password', - 'pool_prefill', - 'pool_size', - 'username', - ], - ]; + ); - return new JsonSchema([ - 'type' => 'object', - 'additionalProperties' => $valueSchema, - ]); + return new MapConstraint(valueConstraint: $valueConstraint); } protected function getConfigurationKey(): string @@ -107,16 +70,16 @@ final readonly class DatabaseConfigurationProvider extends ConfigurationProvider $databaseconfiguration->connectionPoolConfiguration->put( $name, new DatabaseConnectionPoolConfiguration( - database: $connectionPoolConfiguration->database, - driver: DatabaseConnectionPoolDriverName::from($connectionPoolConfiguration->driver), - host: $connectionPoolConfiguration->host, - logQueries: $connectionPoolConfiguration->log_queries, - password: $connectionPoolConfiguration->password, - poolPrefill: $connectionPoolConfiguration->pool_prefill, - poolSize: $connectionPoolConfiguration->pool_size, - port: $connectionPoolConfiguration->port, - unixSocket: $connectionPoolConfiguration->unix_socket, - username: $connectionPoolConfiguration->username, + database: $connectionPoolConfiguration['database'], + driver: DatabaseConnectionPoolDriverName::from($connectionPoolConfiguration['driver']), + host: $connectionPoolConfiguration['host'], + logQueries: $connectionPoolConfiguration['log_queries'], + password: $connectionPoolConfiguration['password'], + poolPrefill: $connectionPoolConfiguration['pool_prefill'], + poolSize: $connectionPoolConfiguration['pool_size'], + port: $connectionPoolConfiguration['port'], + unixSocket: $connectionPoolConfiguration['unix_socket'], + username: $connectionPoolConfiguration['username'], ), ); } diff --git a/src/SingletonProvider/ConfigurationProvider/LlamaCppConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/LlamaCppConfigurationProvider.php index fad8679964ff770f0b1febea4ee9d0d4b6937f6a..0f7f0b4c7a0f41eb3ae540b0971fb792562fa586 100644 --- a/src/SingletonProvider/ConfigurationProvider/LlamaCppConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider/LlamaCppConfigurationProvider.php @@ -5,12 +5,17 @@ declare(strict_types=1); namespace Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\Attribute\Singleton; -use Distantmagic\Resonance\JsonSchema; +use Distantmagic\Resonance\Constraint; +use Distantmagic\Resonance\Constraint\EnumConstraint; +use Distantmagic\Resonance\Constraint\IntegerConstraint; +use Distantmagic\Resonance\Constraint\NumberConstraint; +use Distantmagic\Resonance\Constraint\ObjectConstraint; +use Distantmagic\Resonance\Constraint\StringConstraint; use Distantmagic\Resonance\LlamaCppConfiguration; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; /** - * @template-extends ConfigurationProvider<LlamaCppConfiguration, object{ + * @template-extends ConfigurationProvider<LlamaCppConfiguration, array{ * api_key: null|non-empty-string, * completion_token_timeout: float, * host: non-empty-string, @@ -21,37 +26,17 @@ use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; #[Singleton(provides: LlamaCppConfiguration::class)] final readonly class LlamaCppConfigurationProvider extends ConfigurationProvider { - public function getSchema(): JsonSchema + public function getConstraint(): Constraint { - return new JsonSchema([ - 'type' => 'object', - 'properties' => [ - 'api_key' => [ - 'type' => 'string', - 'minLength' => 1, - 'default' => null, - ], - 'host' => [ - 'type' => 'string', - 'minLength' => 1, - ], - 'completion_token_timeout' => [ - 'type' => 'number', - 'default' => 1.0, - ], - 'port' => [ - 'type' => 'integer', - 'minimum' => 1, - 'maximum' => 65535, - ], - 'scheme' => [ - 'type' => 'string', - 'enum' => ['http', 'https'], - 'default' => 'http', - ], + return new ObjectConstraint( + properties: [ + 'api_key' => (new StringConstraint())->default(null), + 'completion_token_timeout' => (new NumberConstraint())->default(1.0), + 'host' => new StringConstraint(), + 'port' => new IntegerConstraint(), + 'scheme' => (new EnumConstraint(['http', 'https']))->default('http'), ], - 'required' => ['host', 'port'], - ]); + ); } protected function getConfigurationKey(): string @@ -62,11 +47,11 @@ final readonly class LlamaCppConfigurationProvider extends ConfigurationProvider protected function provideConfiguration($validatedData): LlamaCppConfiguration { return new LlamaCppConfiguration( - apiKey: $validatedData->api_key, - completionTokenTimeout: $validatedData->completion_token_timeout, - host: $validatedData->host, - port: $validatedData->port, - scheme: $validatedData->scheme, + apiKey: $validatedData['api_key'], + completionTokenTimeout: $validatedData['completion_token_timeout'], + host: $validatedData['host'], + port: $validatedData['port'], + scheme: $validatedData['scheme'], ); } } diff --git a/src/SingletonProvider/ConfigurationProvider/MailerConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/MailerConfigurationProvider.php index 34504416e6b05f8823ca6cde6eb894396e0a8af5..4f09ae76bcddd8f2f675071fa3622aab311d2ee1 100644 --- a/src/SingletonProvider/ConfigurationProvider/MailerConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider/MailerConfigurationProvider.php @@ -5,7 +5,10 @@ declare(strict_types=1); namespace Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\Attribute\Singleton; -use Distantmagic\Resonance\JsonSchema; +use Distantmagic\Resonance\Constraint; +use Distantmagic\Resonance\Constraint\MapConstraint; +use Distantmagic\Resonance\Constraint\ObjectConstraint; +use Distantmagic\Resonance\Constraint\StringConstraint; use Distantmagic\Resonance\MailerConfiguration; use Distantmagic\Resonance\MailerTransportConfiguration; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; @@ -13,7 +16,7 @@ use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; /** * @template-extends ConfigurationProvider< * MailerConfiguration, - * array<non-empty-string, object{ + * array<non-empty-string, array{ * dkim_domain_name: null|non-empty-string, * dkim_selector: null|non-empty-string, * dkim_signing_key_passphrase: null|non-empty-string, @@ -26,50 +29,20 @@ use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; #[Singleton(provides: MailerConfiguration::class)] final readonly class MailerConfigurationProvider extends ConfigurationProvider { - public function getSchema(): JsonSchema + public function getConstraint(): Constraint { - $valueSchema = [ - 'type' => 'object', - 'properties' => [ - 'dkim_domain_name' => [ - 'type' => 'string', - 'minLength' => 1, - 'default' => null, - ], - 'dkim_selector' => [ - 'type' => 'string', - 'minLength' => 1, - 'default' => null, - ], - 'dkim_signing_key_passphrase' => [ - 'type' => 'string', - 'minLength' => 1, - 'default' => null, - ], - 'dkim_signing_key_private' => [ - 'type' => 'string', - 'minLength' => 1, - 'default' => null, - ], - 'dkim_signing_key_public' => [ - 'type' => 'string', - 'minLength' => 1, - 'default' => null, - ], - 'transport_dsn' => [ - 'type' => 'string', - 'minLength' => 1, - ], + $valueConstraint = new ObjectConstraint( + properties: [ + 'dkim_domain_name' => (new StringConstraint())->nullable(), + 'dkim_selector' => (new StringConstraint())->nullable(), + 'dkim_signing_key_passphrase' => (new StringConstraint())->nullable(), + 'dkim_signing_key_private' => (new StringConstraint())->nullable(), + 'dkim_signing_key_public' => (new StringConstraint())->nullable(), + 'transport_dsn' => new StringConstraint(), ], - 'required' => [ - 'transport_dsn', - ], - ]; + ); - return new JsonSchema([ - 'type' => 'object', - 'additionalProperties' => $valueSchema, - ]); + return new MapConstraint(valueConstraint: $valueConstraint); } protected function getConfigurationKey(): string @@ -85,12 +58,12 @@ final readonly class MailerConfigurationProvider extends ConfigurationProvider $mailerConfiguration->transportConfiguration->put( $name, new MailerTransportConfiguration( - dkimDomainName: $transportConfiguration->dkim_domain_name, - dkimSelector: $transportConfiguration->dkim_selector, - dkimSigningKeyPassphrase: $transportConfiguration->dkim_signing_key_passphrase, - dkimSigningKeyPrivate: $transportConfiguration->dkim_signing_key_private, - dkimSigningKeyPublic: $transportConfiguration->dkim_signing_key_public, - transportDsn: $transportConfiguration->transport_dsn, + dkimDomainName: $transportConfiguration['dkim_domain_name'], + dkimSelector: $transportConfiguration['dkim_selector'], + dkimSigningKeyPassphrase: $transportConfiguration['dkim_signing_key_passphrase'], + dkimSigningKeyPrivate: $transportConfiguration['dkim_signing_key_private'], + dkimSigningKeyPublic: $transportConfiguration['dkim_signing_key_public'], + transportDsn: $transportConfiguration['transport_dsn'], ) ); } diff --git a/src/SingletonProvider/ConfigurationProvider/OAuth2ConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/OAuth2ConfigurationProvider.php index bdb88003134e430be8d21b5fb103705540ae5137..8df863e28ef5b1e125fea48e72f36b92f5f17f67 100644 --- a/src/SingletonProvider/ConfigurationProvider/OAuth2ConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider/OAuth2ConfigurationProvider.php @@ -7,8 +7,10 @@ namespace Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Defuse\Crypto\Key; use Distantmagic\Resonance\Attribute\GrantsFeature; use Distantmagic\Resonance\Attribute\Singleton; +use Distantmagic\Resonance\Constraint; +use Distantmagic\Resonance\Constraint\ObjectConstraint; +use Distantmagic\Resonance\Constraint\StringConstraint; use Distantmagic\Resonance\Feature; -use Distantmagic\Resonance\JsonSchema; use Distantmagic\Resonance\OAuth2Configuration; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use League\OAuth2\Server\CryptKey; @@ -16,7 +18,7 @@ use RuntimeException; use Swoole\Coroutine; /** - * @template-extends ConfigurationProvider<OAuth2Configuration, object{ + * @template-extends ConfigurationProvider<OAuth2Configuration, array{ * encryption_key: non-empty-string, * jwt_signing_key_passphrase: null|string, * jwt_signing_key_private: non-empty-string, @@ -30,45 +32,19 @@ use Swoole\Coroutine; #[Singleton(provides: OAuth2Configuration::class)] final readonly class OAuth2ConfigurationProvider extends ConfigurationProvider { - public function getSchema(): JsonSchema + public function getConstraint(): Constraint { - 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', - ], + return new ObjectConstraint( + properties: [ + 'encryption_key' => new StringConstraint(), + 'jwt_signing_key_passphrase' => (new StringConstraint())->nullable(), + 'jwt_signing_key_private' => new StringConstraint(), + 'jwt_signing_key_public' => new StringConstraint(), + 'session_key_authorization_request' => (new StringConstraint())->default('oauth2.authorization_request'), + 'session_key_pkce' => (new StringConstraint())->default('oauth2.pkce'), + 'session_key_state' => (new StringConstraint())->default('oauth2.state'), ], - 'required' => ['encryption_key', 'jwt_signing_key_private', 'jwt_signing_key_public'], - ]); + ); } protected function getConfigurationKey(): string @@ -78,25 +54,25 @@ final readonly class OAuth2ConfigurationProvider extends ConfigurationProvider protected function provideConfiguration($validatedData): OAuth2Configuration { - $encryptionKeyContent = Coroutine::readFile($validatedData->encryption_key); + $encryptionKeyContent = Coroutine::readFile($validatedData['encryption_key']); if (!is_string($encryptionKeyContent)) { - throw new RuntimeException('Unable to read encrpytion key file: '.$validatedData->encryption_key); + throw new RuntimeException('Unable to read encrpytion key file: '.$validatedData['encryption_key']); } return new OAuth2Configuration( encryptionKey: Key::loadFromAsciiSafeString($encryptionKeyContent), jwtSigningKeyPrivate: new CryptKey( - DM_ROOT.'/'.$validatedData->jwt_signing_key_private, - $validatedData->jwt_signing_key_passphrase, + DM_ROOT.'/'.$validatedData['jwt_signing_key_private'], + $validatedData['jwt_signing_key_passphrase'], ), jwtSigningKeyPublic: new CryptKey( - DM_ROOT.'/'.$validatedData->jwt_signing_key_public, - $validatedData->jwt_signing_key_passphrase, + DM_ROOT.'/'.$validatedData['jwt_signing_key_public'], + $validatedData['jwt_signing_key_passphrase'], ), - sessionKeyAuthorizationRequest: $validatedData->session_key_authorization_request, - sessionKeyPkce: $validatedData->session_key_pkce, - sessionKeyState: $validatedData->session_key_state, + sessionKeyAuthorizationRequest: $validatedData['session_key_authorization_request'], + sessionKeyPkce: $validatedData['session_key_pkce'], + sessionKeyState: $validatedData['session_key_state'], ); } } diff --git a/src/SingletonProvider/ConfigurationProvider/OpenAPIConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/OpenAPIConfigurationProvider.php index de82092dd8b301cd0f9f1115d2d64a22c2be4d86..d779e780bc70dcf2196fc942e7b178901a69b9c5 100644 --- a/src/SingletonProvider/ConfigurationProvider/OpenAPIConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider/OpenAPIConfigurationProvider.php @@ -5,12 +5,14 @@ declare(strict_types=1); namespace Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\Attribute\Singleton; -use Distantmagic\Resonance\JsonSchema; +use Distantmagic\Resonance\Constraint; +use Distantmagic\Resonance\Constraint\ObjectConstraint; +use Distantmagic\Resonance\Constraint\StringConstraint; use Distantmagic\Resonance\OpenAPIConfiguration; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; /** - * @template-extends ConfigurationProvider<OpenAPIConfiguration, object{ + * @template-extends ConfigurationProvider<OpenAPIConfiguration, array{ * description: non-empty-string, * title: non-empty-string, * version: non-empty-string, @@ -19,26 +21,15 @@ use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; #[Singleton(provides: OpenAPIConfiguration::class)] final readonly class OpenAPIConfigurationProvider extends ConfigurationProvider { - public function getSchema(): JsonSchema + public function getConstraint(): Constraint { - return new JsonSchema([ - 'type' => 'object', - 'properties' => [ - 'description' => [ - 'type' => 'string', - 'minLength' => 1, - ], - 'title' => [ - 'type' => 'string', - 'minLength' => 1, - ], - 'version' => [ - 'type' => 'string', - 'minLength' => 1, - ], + return new ObjectConstraint( + properties: [ + 'description' => new StringConstraint(), + 'title' => new StringConstraint(), + 'version' => new StringConstraint(), ], - 'required' => ['description', 'title', 'version'], - ]); + ); } protected function getConfigurationKey(): string @@ -49,9 +40,9 @@ final readonly class OpenAPIConfigurationProvider extends ConfigurationProvider protected function provideConfiguration($validatedData): OpenAPIConfiguration { return new OpenAPIConfiguration( - description: $validatedData->description, - title: $validatedData->title, - version: $validatedData->version, + description: $validatedData['description'], + title: $validatedData['title'], + version: $validatedData['version'], ); } } diff --git a/src/SingletonProvider/ConfigurationProvider/RedisConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/RedisConfigurationProvider.php index 5b562b226f376865ac55b3ad634499f8d599a6b6..e05c94de07928f1e6259bd007a94bf4d6259793e 100644 --- a/src/SingletonProvider/ConfigurationProvider/RedisConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider/RedisConfigurationProvider.php @@ -5,7 +5,12 @@ declare(strict_types=1); namespace Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\Attribute\Singleton; -use Distantmagic\Resonance\JsonSchema; +use Distantmagic\Resonance\Constraint; +use Distantmagic\Resonance\Constraint\BooleanConstraint; +use Distantmagic\Resonance\Constraint\IntegerConstraint; +use Distantmagic\Resonance\Constraint\MapConstraint; +use Distantmagic\Resonance\Constraint\ObjectConstraint; +use Distantmagic\Resonance\Constraint\StringConstraint; use Distantmagic\Resonance\RedisConfiguration; use Distantmagic\Resonance\RedisConnectionPoolConfiguration; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; @@ -13,10 +18,10 @@ use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; /** * @template-extends ConfigurationProvider< * RedisConfiguration, - * array<string, object{ + * array<string, array{ * db_index: int, * host: non-empty-string, - * password: string, + * password: null|string, * pool_prefill: bool, * pool_size: int, * port: int, @@ -28,59 +33,22 @@ use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; #[Singleton(provides: RedisConfiguration::class)] final readonly class RedisConfigurationProvider extends ConfigurationProvider { - public function getSchema(): JsonSchema + public function getConstraint(): Constraint { - $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, - ], + $valueConstraint = new ObjectConstraint( + properties: [ + 'db_index' => new IntegerConstraint(), + 'host' => new StringConstraint(), + 'password' => (new StringConstraint())->nullable(), + 'pool_prefill' => (new BooleanConstraint())->default(true), + 'pool_size' => new IntegerConstraint(), + 'port' => new IntegerConstraint(), + 'prefix' => new StringConstraint(), + 'timeout' => new IntegerConstraint(), ], - 'required' => [ - 'db_index', - 'host', - 'password', - 'pool_prefill', - 'pool_size', - 'port', - 'prefix', - 'timeout', - ], - ]; + ); - return new JsonSchema([ - 'type' => 'object', - 'additionalProperties' => $valueSchema, - ]); + return new MapConstraint(valueConstraint: $valueConstraint); } protected function getConfigurationKey(): string @@ -96,14 +64,14 @@ final readonly class RedisConfigurationProvider extends ConfigurationProvider $databaseconfiguration->connectionPoolConfiguration->put( $name, new RedisConnectionPoolConfiguration( - dbIndex: $connectionPoolConfiguration->db_index, - host: $connectionPoolConfiguration->host, - password: $connectionPoolConfiguration->password, - poolPrefill: $connectionPoolConfiguration->pool_prefill, - poolSize: $connectionPoolConfiguration->pool_size, - port: $connectionPoolConfiguration->port, - prefix: $connectionPoolConfiguration->prefix, - timeout: $connectionPoolConfiguration->timeout, + dbIndex: $connectionPoolConfiguration['db_index'], + host: $connectionPoolConfiguration['host'], + password: (string) $connectionPoolConfiguration['password'], + poolPrefill: $connectionPoolConfiguration['pool_prefill'], + poolSize: $connectionPoolConfiguration['pool_size'], + port: $connectionPoolConfiguration['port'], + prefix: $connectionPoolConfiguration['prefix'], + timeout: $connectionPoolConfiguration['timeout'], ), ); } diff --git a/src/SingletonProvider/ConfigurationProvider/SQLiteVSSConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/SQLiteVSSConfigurationProvider.php index 654f676b69bf6bea758737a006e41b1cb1453415..e7ab93b4ef59b03f9a0a66573b5951c05c16b489 100644 --- a/src/SingletonProvider/ConfigurationProvider/SQLiteVSSConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider/SQLiteVSSConfigurationProvider.php @@ -5,12 +5,14 @@ declare(strict_types=1); namespace Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\Attribute\Singleton; -use Distantmagic\Resonance\JsonSchema; +use Distantmagic\Resonance\Constraint; +use Distantmagic\Resonance\Constraint\ObjectConstraint; +use Distantmagic\Resonance\Constraint\StringConstraint; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\SQLiteVSSConfiguration; /** - * @template-extends ConfigurationProvider<SQLiteVSSConfiguration, object{ + * @template-extends ConfigurationProvider<SQLiteVSSConfiguration, array{ * extension_vector0: non-empty-string, * extension_vss0: non-empty-string, * }> @@ -18,22 +20,14 @@ use Distantmagic\Resonance\SQLiteVSSConfiguration; #[Singleton(provides: SQLiteVSSConfiguration::class)] final readonly class SQLiteVSSConfigurationProvider extends ConfigurationProvider { - public function getSchema(): JsonSchema + public function getConstraint(): Constraint { - return new JsonSchema([ - 'type' => 'object', - 'properties' => [ - 'extension_vector0' => [ - 'type' => 'string', - 'minLength' => 1, - ], - 'extension_vss0' => [ - 'type' => 'string', - 'minLength' => 1, - ], - ], - 'required' => ['extension_vector0', 'extension_vss0'], - ]); + return new ObjectConstraint( + properties: [ + 'extension_vector0' => new StringConstraint(), + 'extension_vss0' => new StringConstraint(), + ] + ); } protected function getConfigurationKey(): string @@ -44,8 +38,8 @@ final readonly class SQLiteVSSConfigurationProvider extends ConfigurationProvide protected function provideConfiguration($validatedData): SQLiteVSSConfiguration { return new SQLiteVSSConfiguration( - extensionVector0: $validatedData->extension_vector0, - extensionVss0: $validatedData->extension_vss0, + extensionVector0: $validatedData['extension_vector0'], + extensionVss0: $validatedData['extension_vss0'], ); } } diff --git a/src/SingletonProvider/ConfigurationProvider/SessionConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/SessionConfigurationProvider.php index ec00d6ebfbb9400133251f3a034a64f5979a3743..c96ea49bcfe13ff9538fcee97eb9864672067c68 100644 --- a/src/SingletonProvider/ConfigurationProvider/SessionConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider/SessionConfigurationProvider.php @@ -6,14 +6,17 @@ namespace Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\Attribute\Singleton; use Distantmagic\Resonance\ConfigurationFile; -use Distantmagic\Resonance\JsonSchema; -use Distantmagic\Resonance\JsonSchemaValidator; +use Distantmagic\Resonance\Constraint; +use Distantmagic\Resonance\Constraint\EnumConstraint; +use Distantmagic\Resonance\Constraint\IntegerConstraint; +use Distantmagic\Resonance\Constraint\ObjectConstraint; +use Distantmagic\Resonance\Constraint\StringConstraint; use Distantmagic\Resonance\RedisConfiguration; use Distantmagic\Resonance\SessionConfiguration; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; /** - * @template-extends ConfigurationProvider<SessionConfiguration, object{ + * @template-extends ConfigurationProvider<SessionConfiguration, array{ * cookie_lifespan: int, * cookie_name: non-empty-string, * cookie_samesite: string, @@ -25,13 +28,12 @@ final readonly class SessionConfigurationProvider extends ConfigurationProvider { public function __construct( private ConfigurationFile $configurationFile, - private JsonSchemaValidator $jsonSchemaValidator, private RedisConfiguration $redisConfiguration, ) { - parent::__construct($configurationFile, $jsonSchemaValidator); + parent::__construct($configurationFile); } - public function getSchema(): JsonSchema + public function getConstraint(): Constraint { $redisConnectionPools = $this ->redisConfiguration @@ -40,29 +42,14 @@ final readonly class SessionConfigurationProvider extends ConfigurationProvider ->toArray() ; - 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, - ], + return new ObjectConstraint( + properties: [ + 'cookie_lifespan' => new IntegerConstraint(), + 'cookie_name' => new StringConstraint(), + 'cookie_samesite' => (new EnumConstraint(['lax', 'none', 'strict']))->default('lax'), + 'redis_connection_pool' => new EnumConstraint($redisConnectionPools), ], - 'required' => ['cookie_lifespan', 'cookie_name'], - ]); + ); } protected function getConfigurationKey(): string @@ -73,10 +60,10 @@ final readonly class SessionConfigurationProvider extends ConfigurationProvider protected function provideConfiguration($validatedData): SessionConfiguration { return new SessionConfiguration( - cookieLifespan: $validatedData->cookie_lifespan, - cookieName: $validatedData->cookie_name, - cookieSameSite: $validatedData->cookie_samesite, - redisConnectionPool: $validatedData->redis_connection_pool, + cookieLifespan: $validatedData['cookie_lifespan'], + cookieName: $validatedData['cookie_name'], + cookieSameSite: $validatedData['cookie_samesite'], + redisConnectionPool: $validatedData['redis_connection_pool'], ); } } diff --git a/src/SingletonProvider/ConfigurationProvider/StaticPageConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/StaticPageConfigurationProvider.php index 38f90ebf7a51ae512b1e36d27d3e23b36df9a8d9..5f3fca8fce8fc1a1717fd9dcc65379cb19893f77 100644 --- a/src/SingletonProvider/ConfigurationProvider/StaticPageConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider/StaticPageConfigurationProvider.php @@ -5,12 +5,14 @@ declare(strict_types=1); namespace Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\Attribute\Singleton; -use Distantmagic\Resonance\JsonSchema; +use Distantmagic\Resonance\Constraint; +use Distantmagic\Resonance\Constraint\ObjectConstraint; +use Distantmagic\Resonance\Constraint\StringConstraint; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\StaticPageConfiguration; /** - * @template-extends ConfigurationProvider<StaticPageConfiguration, object{ + * @template-extends ConfigurationProvider<StaticPageConfiguration, array{ * base_url: non-empty-string, * esbuild_metafile: non-empty-string, * input_directory: non-empty-string, @@ -21,34 +23,17 @@ use Distantmagic\Resonance\StaticPageConfiguration; #[Singleton(provides: StaticPageConfiguration::class)] final readonly class StaticPageConfigurationProvider extends ConfigurationProvider { - public function getSchema(): JsonSchema + public function getConstraint(): Constraint { - return new JsonSchema([ - 'type' => 'object', - 'properties' => [ - 'base_url' => [ - 'type' => 'string', - 'minLength' => 1, - ], - 'esbuild_metafile' => [ - 'type' => 'string', - 'minLength' => 1, - ], - 'input_directory' => [ - 'type' => 'string', - 'minLength' => 1, - ], - 'output_directory' => [ - 'type' => 'string', - 'minLength' => 1, - ], - 'sitemap' => [ - 'type' => 'string', - 'minLength' => 1, - ], + return new ObjectConstraint( + properties: [ + 'base_url' => new StringConstraint(), + 'esbuild_metafile' => new StringConstraint(), + 'input_directory' => new StringConstraint(), + 'output_directory' => new StringConstraint(), + 'sitemap' => new StringConstraint(), ], - 'required' => ['base_url', 'esbuild_metafile', 'input_directory', 'output_directory', 'sitemap'], - ]); + ); } protected function getConfigurationKey(): string @@ -59,12 +44,12 @@ final readonly class StaticPageConfigurationProvider extends ConfigurationProvid protected function provideConfiguration($validatedData): StaticPageConfiguration { return new StaticPageConfiguration( - baseUrl: $validatedData->base_url, - esbuildMetafile: DM_ROOT.'/'.$validatedData->esbuild_metafile, - inputDirectory: DM_ROOT.'/'.$validatedData->input_directory, - outputDirectory: DM_ROOT.'/'.$validatedData->output_directory, - sitemap: DM_ROOT.'/'.$validatedData->sitemap, - stripOutputPrefix: $validatedData->output_directory.'/', + baseUrl: $validatedData['base_url'], + esbuildMetafile: DM_ROOT.'/'.$validatedData['esbuild_metafile'], + inputDirectory: DM_ROOT.'/'.$validatedData['input_directory'], + outputDirectory: DM_ROOT.'/'.$validatedData['output_directory'], + sitemap: DM_ROOT.'/'.$validatedData['sitemap'], + stripOutputPrefix: $validatedData['output_directory'].'/', ); } } diff --git a/src/SingletonProvider/ConfigurationProvider/SwooleConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/SwooleConfigurationProvider.php index bf91df2168e8fca0604cdb689103cce9d0a1c421..deb95764d6d58f70e050003877e58712c8fde294 100644 --- a/src/SingletonProvider/ConfigurationProvider/SwooleConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider/SwooleConfigurationProvider.php @@ -5,14 +5,18 @@ declare(strict_types=1); namespace Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\Attribute\Singleton; -use Distantmagic\Resonance\JsonSchema; +use Distantmagic\Resonance\Constraint; +use Distantmagic\Resonance\Constraint\BooleanConstraint; +use Distantmagic\Resonance\Constraint\IntegerConstraint; +use Distantmagic\Resonance\Constraint\ObjectConstraint; +use Distantmagic\Resonance\Constraint\StringConstraint; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\SwooleConfiguration; /** - * @template-extends ConfigurationProvider<SwooleConfiguration, object{ + * @template-extends ConfigurationProvider<SwooleConfiguration, array{ * host: non-empty-string, - * log_level: non-empty-string, + * log_level: int, * log_requests: boolean, * port: int, * ssl_cert_file: non-empty-string, @@ -23,50 +27,19 @@ use Distantmagic\Resonance\SwooleConfiguration; #[Singleton(provides: SwooleConfiguration::class)] final readonly class SwooleConfigurationProvider extends ConfigurationProvider { - public function getSchema(): JsonSchema + public function getConstraint(): Constraint { - 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, - ], - 'task_worker_num' => [ - 'type' => 'integer', - 'min' => 1, - 'default' => 4, - ], + return new ObjectConstraint( + properties: [ + 'host' => new StringConstraint(), + 'log_level' => new IntegerConstraint(), + 'log_requests' => (new BooleanConstraint())->default(false), + 'port' => new IntegerConstraint(), + 'ssl_cert_file' => new StringConstraint(), + 'ssl_key_file' => new StringConstraint(), + 'task_worker_num' => (new IntegerConstraint())->default(4), ], - 'required' => [ - 'host', - 'log_level', - 'port', - 'ssl_cert_file', - 'ssl_key_file', - ], - ]); + ); } protected function getConfigurationKey(): string @@ -77,13 +50,13 @@ final readonly class SwooleConfigurationProvider extends ConfigurationProvider protected function provideConfiguration($validatedData): SwooleConfiguration { return new SwooleConfiguration( - host: $validatedData->host, - logLevel: (int) ($validatedData->log_level), - logRequests: $validatedData->log_requests, - port: $validatedData->port, - sslCertFile: $validatedData->ssl_cert_file, - sslKeyFile: $validatedData->ssl_key_file, - taskWorkerNum: $validatedData->task_worker_num, + host: $validatedData['host'], + logLevel: $validatedData['log_level'], + logRequests: $validatedData['log_requests'], + port: $validatedData['port'], + sslCertFile: $validatedData['ssl_cert_file'], + sslKeyFile: $validatedData['ssl_key_file'], + taskWorkerNum: $validatedData['task_worker_num'], ); } } diff --git a/src/SingletonProvider/ConfigurationProvider/TranslatorConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/TranslatorConfigurationProvider.php index 87c4216ae645ec28d5c7d0d75c8f1497d0da8252..f090d1763aedc4c7514c2e53325142a3f61eb001 100644 --- a/src/SingletonProvider/ConfigurationProvider/TranslatorConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider/TranslatorConfigurationProvider.php @@ -5,12 +5,14 @@ declare(strict_types=1); namespace Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\Attribute\Singleton; -use Distantmagic\Resonance\JsonSchema; +use Distantmagic\Resonance\Constraint; +use Distantmagic\Resonance\Constraint\ObjectConstraint; +use Distantmagic\Resonance\Constraint\StringConstraint; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\TranslatorConfiguration; /** - * @template-extends ConfigurationProvider<TranslatorConfiguration, object{ + * @template-extends ConfigurationProvider<TranslatorConfiguration, array{ * base_directory: non-empty-string, * default_primary_language: non-empty-string, * }> @@ -18,22 +20,14 @@ use Distantmagic\Resonance\TranslatorConfiguration; #[Singleton(provides: TranslatorConfiguration::class)] final readonly class TranslatorConfigurationProvider extends ConfigurationProvider { - public function getSchema(): JsonSchema + public function getConstraint(): Constraint { - return new JsonSchema([ - 'type' => 'object', - 'properties' => [ - 'base_directory' => [ - 'type' => 'string', - 'minLength' => 1, - ], - 'default_primary_language' => [ - 'type' => 'string', - 'minLength' => 1, - ], + return new ObjectConstraint( + properties: [ + 'base_directory' => new StringConstraint(), + 'default_primary_language' => new StringConstraint(), ], - 'required' => ['base_directory', 'default_primary_language'], - ]); + ); } protected function getConfigurationKey(): string @@ -44,8 +38,8 @@ final readonly class TranslatorConfigurationProvider extends ConfigurationProvid protected function provideConfiguration($validatedData): TranslatorConfiguration { return new TranslatorConfiguration( - baseDirectory: $validatedData->base_directory, - defaultPrimaryLanguage: $validatedData->default_primary_language, + baseDirectory: $validatedData['base_directory'], + defaultPrimaryLanguage: $validatedData['default_primary_language'], ); } } diff --git a/src/SingletonProvider/ConfigurationProvider/WebSocketConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/WebSocketConfigurationProvider.php index 8757cc3cafdf60303814d30d082a50baa797fa26..40540e109f30313171e4ad2e0ef4250d9434920c 100644 --- a/src/SingletonProvider/ConfigurationProvider/WebSocketConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider/WebSocketConfigurationProvider.php @@ -6,13 +6,15 @@ namespace Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\Attribute\GrantsFeature; use Distantmagic\Resonance\Attribute\Singleton; +use Distantmagic\Resonance\Constraint; +use Distantmagic\Resonance\Constraint\IntegerConstraint; +use Distantmagic\Resonance\Constraint\ObjectConstraint; use Distantmagic\Resonance\Feature; -use Distantmagic\Resonance\JsonSchema; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\WebSocketConfiguration; /** - * @template-extends ConfigurationProvider<WebSocketConfiguration, object{ + * @template-extends ConfigurationProvider<WebSocketConfiguration, array{ * max_connections: int, * }> */ @@ -20,19 +22,16 @@ use Distantmagic\Resonance\WebSocketConfiguration; #[Singleton(provides: WebSocketConfiguration::class)] final readonly class WebSocketConfigurationProvider extends ConfigurationProvider { - public function getSchema(): JsonSchema + public function getConstraint(): Constraint { - return new JsonSchema([ - 'type' => 'object', - 'properties' => [ - 'max_connections' => [ - 'type' => 'integer', - 'minimum' => 1, - 'maximum' => 65535, - 'default' => 10000, - ], + return new ObjectConstraint( + // 'minimum' => 1, + // 'maximum' => 65535, + // 'default' => 10000, + properties: [ + 'max_connections' => (new IntegerConstraint())->default(10000), ], - ]); + ); } protected function getConfigurationKey(): string @@ -43,7 +42,7 @@ final readonly class WebSocketConfigurationProvider extends ConfigurationProvide protected function provideConfiguration($validatedData): WebSocketConfiguration { return new WebSocketConfiguration( - maxConnections: $validatedData->max_connections, + maxConnections: $validatedData['max_connections'], ); } }