Skip to content

Commit

Permalink
add enum and const
Browse files Browse the repository at this point in the history
  • Loading branch information
tymondesigns committed Jan 13, 2025
1 parent e4f3af5 commit 5593a6b
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 3 deletions.
3 changes: 3 additions & 0 deletions src/Exceptions/SchemaException.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ public function getError(): ValidationError
return $this->error;
}

/**
* @return array<string, string>
*/
public function getErrors(): array
{
return (new ErrorFormatter())->format($this->error);
Expand Down
6 changes: 6 additions & 0 deletions src/Types/AbstractSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use Cortex\JsonSchema\Contracts\Schema;
use Cortex\JsonSchema\Enums\SchemaType;
use Cortex\JsonSchema\Types\Concerns\HasEnum;
use Cortex\JsonSchema\Types\Concerns\HasConst;
use Cortex\JsonSchema\Types\Concerns\HasTitle;
use Cortex\JsonSchema\Types\Concerns\HasFormat;
use Cortex\JsonSchema\Types\Concerns\HasRequired;
Expand All @@ -21,6 +23,8 @@ abstract class AbstractSchema implements Schema
use HasReadWrite;
use HasValidation;
use HasDescription;
use HasEnum;
use HasConst;

protected string $schemaVersion = 'http://json-schema.org/draft-07/schema#';

Expand Down Expand Up @@ -93,6 +97,8 @@ public function toArray(bool $includeSchemaRef = true, bool $includeTitle = true
$schema = $this->addTitleToSchema($schema, $includeTitle);
$schema = $this->addFormatToSchema($schema);
$schema = $this->addDescriptionToSchema($schema);
$schema = $this->addEnumToSchema($schema);
$schema = $this->addConstToSchema($schema);

return $this->addReadWriteToSchema($schema);
}
Expand Down
44 changes: 44 additions & 0 deletions src/Types/Concerns/HasConst.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Cortex\JsonSchema\Types\Concerns;

trait HasConst
{
/**
* @var int|string|bool|float|null
*/
protected mixed $const = null;

protected bool $hasConst = false;

/**
* Set the constant value.
*
* @param int|string|bool|float|null $value
*/
public function const(mixed $value): static
{
$this->const = $value;
$this->hasConst = true;

return $this;
}

/**
* Add const value to schema array.
*
* @param array<string, mixed> $schema
*
* @return array<string, mixed>
*/
protected function addConstToSchema(array $schema): array
{
if ($this->hasConst) {
$schema['const'] = $this->const;
}

return $schema;
}
}
41 changes: 41 additions & 0 deletions src/Types/Concerns/HasEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Cortex\JsonSchema\Types\Concerns;

trait HasEnum
{
/**
* @var non-empty-array<int|string|bool|float|null>|null
*/
protected ?array $enum = null;

/**
* Set the allowed enum values.
*
* @param non-empty-array<int|string|bool|float|null> $values
*/
public function enum(array $values): static
{
$this->enum = array_values(array_unique($values, SORT_REGULAR));

return $this;
}

/**
* Add enum values to schema array.
*
* @param array<string, mixed> $schema
*
* @return array<string, mixed>
*/
protected function addEnumToSchema(array $schema): array
{
if ($this->enum !== null) {
$schema['enum'] = $this->enum;
}

return $schema;
}
}
1 change: 0 additions & 1 deletion src/Types/Concerns/HasValidation.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use Opis\JsonSchema\Helper;
use InvalidArgumentException;
use Opis\JsonSchema\Validator;
use Opis\JsonSchema\Errors\ErrorFormatter;
use Cortex\JsonSchema\Exceptions\SchemaException;
use Opis\JsonSchema\Exceptions\SchemaException as OpisSchemaException;

Expand Down
91 changes: 91 additions & 0 deletions tests/Unit/Targets/NumberSchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,94 @@
'The data (string) must match the type: number, null',
);
});

it('can create a number schema with enum values', function (): void {
$schema = Schema::number('rating')
->description('Product rating')
->enum([1.0, 2.5, 3.0, 4.5, 5.0]);

$schemaArray = $schema->toArray();

expect($schemaArray)->toHaveKey('type', 'number');
expect($schemaArray)->toHaveKey('title', 'rating');
expect($schemaArray)->toHaveKey('description', 'Product rating');
expect($schemaArray)->toHaveKey('enum', [1.0, 2.5, 3.0, 4.5, 5.0]);

// Validation tests
expect(fn() => $schema->validate(3.5))->toThrow(
SchemaException::class,
'The data should match one item from enum',
);

expect(fn() => $schema->validate(1.0))->not->toThrow(SchemaException::class);
expect(fn() => $schema->validate(2.5))->not->toThrow(SchemaException::class);
expect(fn() => $schema->validate(5.0))->not->toThrow(SchemaException::class);
});

it('can create a nullable number schema with enum values', function (): void {
$schema = Schema::number('discount')
->description('Product discount percentage')
->enum([0.1, 0.25, 0.5, null])
->nullable();

$schemaArray = $schema->toArray();

expect($schemaArray)->toHaveKey('type', ['number', 'null']);
expect($schemaArray)->toHaveKey('title', 'discount');
expect($schemaArray)->toHaveKey('description', 'Product discount percentage');
expect($schemaArray)->toHaveKey('enum', [0.1, 0.25, 0.5, null]);

// Validation tests
expect(fn() => $schema->validate(0.75))->toThrow(
SchemaException::class,
'The data should match one item from enum',
);

expect(fn() => $schema->validate(0.1))->not->toThrow(SchemaException::class);
expect(fn() => $schema->validate(0.25))->not->toThrow(SchemaException::class);
expect(fn() => $schema->validate(0.5))->not->toThrow(SchemaException::class);
expect(fn() => $schema->validate(null))->not->toThrow(SchemaException::class);
});

it('can create a number schema with const value', function (): void {
$schema = Schema::number('tax_rate')
->description('Fixed tax rate percentage')
->const(0.21); // 21% VAT

$schemaArray = $schema->toArray();

expect($schemaArray)->toHaveKey('type', 'number');
expect($schemaArray)->toHaveKey('title', 'tax_rate');
expect($schemaArray)->toHaveKey('description', 'Fixed tax rate percentage');
expect($schemaArray)->toHaveKey('const', 0.21);

// Validation tests
expect(fn() => $schema->validate(0.20))->toThrow(
SchemaException::class,
'The data must match the const value',
);

expect(fn() => $schema->validate(0.21))->not->toThrow(SchemaException::class);
});

it('can create a nullable number schema with const value', function (): void {
$schema = Schema::number('standard_fee')
->description('Standard processing fee')
->nullable()
->const(null);

$schemaArray = $schema->toArray();

expect($schemaArray)->toHaveKey('type', ['number', 'null']);
expect($schemaArray)->toHaveKey('title', 'standard_fee');
expect($schemaArray)->toHaveKey('description', 'Standard processing fee');
expect($schemaArray)->toHaveKey('const', null);

// Validation tests
expect(fn() => $schema->validate(0.0))->toThrow(
SchemaException::class,
'The data must match the const value',
);

expect(fn() => $schema->validate(null))->not->toThrow(SchemaException::class);
});
3 changes: 1 addition & 2 deletions tests/Unit/Targets/ObjectSchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace Cortex\JsonSchema\Tests\Unit;

use Cortex\JsonSchema\Enums\SchemaFormat;
use Opis\JsonSchema\Errors\ErrorFormatter;
use Cortex\JsonSchema\SchemaFactory as Schema;
use Cortex\JsonSchema\Exceptions\SchemaException;

Expand Down Expand Up @@ -95,7 +94,7 @@
expect($e->getMessage())->toBe('The properties must match schema: email');
expect($e->getErrors())->toBe([
'/email' => [
'The data must match the \'email\' format'
"The data must match the 'email' format",
],
]);

Expand Down
48 changes: 48 additions & 0 deletions tests/Unit/Targets/StringSchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,51 @@

expect(fn() => $schema->validate('2024-03-14T12:00:00Z'))->not->toThrow(SchemaException::class);
});

it('can create a string schema with enum values', function (): void {
$schema = Schema::string('status')
->description('Current status of the record')
->enum(['draft', 'published', 'archived']);

$schemaArray = $schema->toArray();

expect($schemaArray)->toHaveKey('type', 'string');
expect($schemaArray)->toHaveKey('title', 'status');
expect($schemaArray)->toHaveKey('description', 'Current status of the record');
expect($schemaArray)->toHaveKey('enum', ['draft', 'published', 'archived']);

// Validation tests
expect(fn() => $schema->validate('pending'))->toThrow(
SchemaException::class,
'The data should match one item from enum',
);

expect(fn() => $schema->validate('draft'))->not->toThrow(SchemaException::class);
expect(fn() => $schema->validate('published'))->not->toThrow(SchemaException::class);
expect(fn() => $schema->validate('archived'))->not->toThrow(SchemaException::class);
});

it('can create a nullable string schema with enum values', function (): void {
$schema = Schema::string('priority')
->description('Task priority level')
->enum(['low', 'medium', 'high', null])
->nullable();

$schemaArray = $schema->toArray();

expect($schemaArray)->toHaveKey('type', ['string', 'null']);
expect($schemaArray)->toHaveKey('title', 'priority');
expect($schemaArray)->toHaveKey('description', 'Task priority level');
expect($schemaArray)->toHaveKey('enum', ['low', 'medium', 'high', null]);

// Validation tests
expect(fn() => $schema->validate('critical'))->toThrow(
SchemaException::class,
'The data should match one item from enum',
);

expect(fn() => $schema->validate('low'))->not->toThrow(SchemaException::class);
expect(fn() => $schema->validate('medium'))->not->toThrow(SchemaException::class);
expect(fn() => $schema->validate('high'))->not->toThrow(SchemaException::class);
expect(fn() => $schema->validate(null))->not->toThrow(SchemaException::class);
});

0 comments on commit 5593a6b

Please sign in to comment.