Skip to content
Snippets Groups Projects
Commit 4be3a445 authored by Mateusz Charytoniuk's avatar Mateusz Charytoniuk
Browse files

chore: move configs to constraints schema

parent 2b668904
No related branches found
No related tags found
No related merge requests found
Showing
with 528 additions and 264 deletions
......@@ -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(
......
......@@ -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
{
......
<?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,
);
}
}
<?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());
}
}
......@@ -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
......
......@@ -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,
......
......@@ -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,
......
......@@ -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());
}
}
<?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,
);
}
}
<?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());
}
}
......@@ -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
{
......
......@@ -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,
......
<?php
declare(strict_types=1);
namespace Distantmagic\Resonance;
interface ConstraintSourceInterface
{
public function getConstraint(): Constraint;
}
<?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),
));
}
}
......@@ -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
......
......@@ -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,
);
}
......
......@@ -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'],
),
);
}
......
......@@ -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'],
);
}
}
......@@ -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'],
)
);
}
......
......@@ -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'],
);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment