Skip to content

Commit 1afced8

Browse files
authored
refactor: Enum converter (#6)
1 parent 8daf032 commit 1afced8

File tree

9 files changed

+89
-60
lines changed

9 files changed

+89
-60
lines changed

README.md

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -973,15 +973,10 @@ $schema->toJson();
973973
```json
974974
{
975975
"$schema": "http://json-schema.org/draft-07/schema#",
976-
"type": "object",
976+
"type": "integer",
977+
"title": "PostType",
977978
"description": "This is the description of the enum",
978-
"properties": {
979-
"PostType": {
980-
"type": "integer",
981-
"enum": [1, 2, 3]
982-
}
983-
},
984-
"required": ["PostType"]
979+
"enum": [1, 2, 3]
985980
}
986981
```
987982

src/Contracts/Converter.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,10 @@
44

55
namespace Cortex\JsonSchema\Contracts;
66

7-
use Cortex\JsonSchema\Types\ObjectSchema;
8-
97
interface Converter
108
{
119
/**
12-
* Convert the value to an object schema.
10+
* Convert the value to a schema instance.
1311
*/
14-
public function convert(): ObjectSchema;
12+
public function convert(): Schema;
1513
}

src/Converters/EnumConverter.php

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@
44

55
namespace Cortex\JsonSchema\Converters;
66

7-
use BackedEnum;
87
use ReflectionEnum;
98
use Cortex\JsonSchema\Support\DocParser;
10-
use Cortex\JsonSchema\Types\ObjectSchema;
119
use Cortex\JsonSchema\Types\StringSchema;
1210
use Cortex\JsonSchema\Contracts\Converter;
1311
use Cortex\JsonSchema\Types\IntegerSchema;
@@ -26,17 +24,29 @@ class EnumConverter implements Converter
2624
public function __construct(
2725
protected string $enum,
2826
) {
29-
// @phpstan-ignore function.alreadyNarrowedType
30-
if (! is_subclass_of($this->enum, BackedEnum::class)) {
27+
$this->reflection = new ReflectionEnum($this->enum);
28+
29+
if (! $this->reflection->isBacked()) {
3130
throw new SchemaException('Enum must be a backed enum');
3231
}
33-
34-
$this->reflection = new ReflectionEnum($this->enum);
3532
}
3633

37-
public function convert(): ObjectSchema
34+
public function convert(): StringSchema|IntegerSchema
3835
{
39-
$schema = new ObjectSchema();
36+
// Get the basename of the enum namespace
37+
$enumName = basename(str_replace('\\', '/', $this->enum));
38+
39+
// Determine the backing type
40+
$schema = match ($this->reflection->getBackingType()?->getName()) {
41+
'string' => new StringSchema($enumName),
42+
'int' => new IntegerSchema($enumName),
43+
default => throw new SchemaException('Unsupported enum backing type. Only "int" or "string" are supported.'),
44+
};
45+
46+
/** @var non-empty-array<int, string|int> $values */
47+
$values = array_column($this->enum::cases(), 'value');
48+
49+
$schema->enum($values);
4050

4151
// Get the description from the doc parser
4252
$description = $this->getDocParser($this->reflection)?->description() ?? null;
@@ -46,19 +56,7 @@ public function convert(): ObjectSchema
4656
$schema->description($description);
4757
}
4858

49-
// Get the basename of the enum namespace
50-
$parts = explode('\\', $this->enum);
51-
$enumName = end($parts);
52-
53-
/** @var non-empty-array<int, string|int> $values */
54-
$values = array_column($this->enum::cases(), 'value');
55-
56-
// Determine the backing type
57-
return match ($this->reflection->getBackingType()?->getName()) {
58-
'string' => $schema->properties((new StringSchema($enumName))->enum($values)->required()),
59-
'int' => $schema->properties((new IntegerSchema($enumName))->enum($values)->required()),
60-
default => throw new SchemaException('Unsupported enum backing type. Only "int" or "string" are supported.'),
61-
};
59+
return $schema;
6260
}
6361

6462
/**

src/SchemaFactory.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
namespace Cortex\JsonSchema;
66

77
use Closure;
8+
use BackedEnum;
9+
use Cortex\JsonSchema\Contracts\Schema;
810
use Cortex\JsonSchema\Enums\SchemaType;
911
use Cortex\JsonSchema\Types\NullSchema;
1012
use Cortex\JsonSchema\Types\ArraySchema;
@@ -16,6 +18,7 @@
1618
use Cortex\JsonSchema\Types\IntegerSchema;
1719
use Cortex\JsonSchema\Converters\EnumConverter;
1820
use Cortex\JsonSchema\Converters\ClassConverter;
21+
use Cortex\JsonSchema\Exceptions\SchemaException;
1922
use Cortex\JsonSchema\Converters\ClosureConverter;
2023

2124
class SchemaFactory
@@ -91,8 +94,21 @@ public static function fromClass(object|string $class, bool $publicOnly = true):
9194
*
9295
* @param class-string<\BackedEnum> $enum
9396
*/
94-
public static function fromEnum(string $enum): ObjectSchema
97+
public static function fromEnum(string $enum): StringSchema|IntegerSchema
9598
{
9699
return (new EnumConverter($enum))->convert();
97100
}
101+
102+
/**
103+
* Create a schema from a given value.
104+
*/
105+
public static function from(mixed $value): Schema
106+
{
107+
return match (true) {
108+
$value instanceof Closure => self::fromClosure($value),
109+
is_string($value) && enum_exists($value) && is_subclass_of($value, BackedEnum::class) => self::fromEnum($value),
110+
is_string($value) && class_exists($value) || is_object($value) => self::fromClass($value),
111+
default => throw new SchemaException('Unsupported value type. Only closures, enums, and classes are supported.'),
112+
};
113+
}
98114
}

tests/ArchitectureTest.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@
55
namespace Cortex\JsonSchema\Tests;
66

77
use Throwable;
8+
use Cortex\JsonSchema\Contracts\Schema;
9+
use Cortex\JsonSchema\Contracts\Converter;
810

911
arch()->preset()->php();
1012
arch()->preset()->security();
1113

1214
arch()->expect('Cortex\JsonSchema\Contracts')->toBeInterfaces();
1315
arch()->expect('Cortex\JsonSchema\Enums')->toBeEnums();
14-
arch()->expect('Cortex\JsonSchema\Exceptions')->toExtend(Throwable::class);
16+
arch()->expect('Cortex\JsonSchema\Exceptions')->toImplement(Throwable::class);
17+
arch()->expect('Cortex\JsonSchema\Converters')->classes()->toImplement(Converter::class);
18+
arch()->expect('Cortex\JsonSchema\Types')->classes()->toImplement(Schema::class);

tests/Unit/Converters/EnumConverterTest.php

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
namespace Cortex\JsonSchema\Tests\Unit\Converters;
66

7-
use Cortex\JsonSchema\Types\ObjectSchema;
7+
use Cortex\JsonSchema\Types\StringSchema;
8+
use Cortex\JsonSchema\Types\IntegerSchema;
89
use Cortex\JsonSchema\Converters\EnumConverter;
910
use Cortex\JsonSchema\Exceptions\SchemaException;
1011

@@ -21,20 +22,13 @@ enum PostStatus: string
2122

2223
$schema = (new EnumConverter(PostStatus::class))->convert();
2324

24-
expect($schema)->toBeInstanceOf(ObjectSchema::class);
25+
expect($schema)->toBeInstanceOf(StringSchema::class);
2526
expect($schema->toArray())->toBe([
26-
'type' => 'object',
27+
'type' => 'string',
2728
'$schema' => 'http://json-schema.org/draft-07/schema#',
29+
'title' => 'PostStatus',
2830
'description' => 'This is the description of the string backed enum',
29-
'properties' => [
30-
'PostStatus' => [
31-
'type' => 'string',
32-
'enum' => ['draft', 'published', 'archived'],
33-
],
34-
],
35-
'required' => [
36-
'PostStatus',
37-
],
31+
'enum' => ['draft', 'published', 'archived'],
3832
]);
3933
});
4034

@@ -49,20 +43,13 @@ enum PostType: int
4943

5044
$schema = (new EnumConverter(PostType::class))->convert();
5145

52-
expect($schema)->toBeInstanceOf(ObjectSchema::class);
46+
expect($schema)->toBeInstanceOf(IntegerSchema::class);
5347
expect($schema->toArray())->toBe([
54-
'type' => 'object',
48+
'type' => 'integer',
5549
'$schema' => 'http://json-schema.org/draft-07/schema#',
50+
'title' => 'PostType',
5651
'description' => 'This is the description of the integer backed enum',
57-
'properties' => [
58-
'PostType' => [
59-
'type' => 'integer',
60-
'enum' => [1, 2, 3],
61-
],
62-
],
63-
'required' => [
64-
'PostType',
65-
],
52+
'enum' => [1, 2, 3],
6653
]);
6754
});
6855

tests/Unit/SchemaFactoryTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@
9191
]);
9292

9393
expect($schema->toJson())->toBe(json_encode($schema->toArray()));
94+
95+
// Assert that the from method behaves in the same way as the fromClosure method
96+
expect(Schema::from($closure))->toEqual($schema);
9497
});
9598

9699
it('can create a schema from a class', function (): void {
@@ -123,4 +126,32 @@ public function __construct(
123126
'age',
124127
],
125128
]);
129+
130+
// Assert that the from method behaves in the same way as the fromClass method
131+
expect(Schema::from($class))->toEqual($schema);
132+
});
133+
134+
it('can create a schema from an enum', function (): void {
135+
/** This is a custom enum for testing */
136+
enum UserRole: string
137+
{
138+
case Admin = 'admin';
139+
case Editor = 'editor';
140+
case Viewer = 'viewer';
141+
case Guest = 'guest';
142+
}
143+
144+
$schema = Schema::fromEnum(UserRole::class);
145+
146+
expect($schema)->toBeInstanceOf(StringSchema::class);
147+
expect($schema->toArray())->toBe([
148+
'type' => 'string',
149+
'$schema' => 'http://json-schema.org/draft-07/schema#',
150+
'title' => 'UserRole',
151+
'description' => 'This is a custom enum for testing',
152+
'enum' => ['admin', 'editor', 'viewer', 'guest'],
153+
]);
154+
155+
// Assert that the from method behaves in the same way as the fromEnum method
156+
expect(Schema::from(UserRole::class))->toEqual($schema);
126157
});

tests/Unit/Targets/IntegerSchemaTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
namespace Cortex\JsonSchema\Tests\Unit;
66

7+
use Cortex\JsonSchema\Types\IntegerSchema;
78
use Cortex\JsonSchema\SchemaFactory as Schema;
89
use Cortex\JsonSchema\Exceptions\SchemaException;
9-
use Cortex\JsonSchema\Types\IntegerSchema;
1010

1111
covers(IntegerSchema::class);
1212

tests/Unit/Targets/StringSchemaTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
namespace Cortex\JsonSchema\Tests\Unit;
66

77
use Cortex\JsonSchema\Enums\SchemaFormat;
8+
use Cortex\JsonSchema\Types\StringSchema;
89
use Cortex\JsonSchema\SchemaFactory as Schema;
910
use Cortex\JsonSchema\Exceptions\SchemaException;
10-
use Cortex\JsonSchema\Types\StringSchema;
1111

1212
covers(StringSchema::class);
1313

0 commit comments

Comments
 (0)