Skip to content

Commit a48282a

Browse files
Feature/json schema improvements (#57609)
* feat: Support backed enums for json schema cases * feat: Add format to json schema string type * style: Formatting * fix: Validate class-string for enum method * style: Formatting * formatting --------- Co-authored-by: Taylor Otwell <[email protected]>
1 parent 264ae5b commit a48282a

File tree

7 files changed

+104
-3
lines changed

7 files changed

+104
-3
lines changed

src/Illuminate/JsonSchema/Types/StringType.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ class StringType extends Type
1919
*/
2020
protected ?string $pattern = null;
2121

22+
/**
23+
* The format of the string.
24+
*/
25+
protected ?string $format = null;
26+
2227
/**
2328
* Set the minimum length (inclusive).
2429
*/
@@ -49,6 +54,18 @@ public function pattern(string $value): static
4954
return $this;
5055
}
5156

57+
/**
58+
* Set the format of the string.
59+
*
60+
* {@link https://json-schema.org/understanding-json-schema/reference/type#built-in-formats}
61+
*/
62+
public function format(string $value): static
63+
{
64+
$this->format = $value;
65+
66+
return $this;
67+
}
68+
5269
/**
5370
* Set the type's default value.
5471
*/

src/Illuminate/JsonSchema/Types/Type.php

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
namespace Illuminate\JsonSchema\Types;
44

5+
use BackedEnum;
56
use Illuminate\JsonSchema\JsonSchema;
67
use Illuminate\JsonSchema\Serializer;
8+
use InvalidArgumentException;
79

810
abstract class Type extends JsonSchema
911
{
@@ -86,11 +88,19 @@ public function description(string $value): static
8688
/**
8789
* Restrict the value to one of the provided enumerated values.
8890
*
89-
* @param array<int, mixed> $values
91+
* @param class-string<\BackedEnum>|array<int, mixed> $values
9092
*/
91-
public function enum(array $values): static
93+
public function enum(array|string $values): static
9294
{
93-
// Keep order and allow complex values (arrays/objects) without forcing uniqueness...
95+
if (is_string($values)) {
96+
if (! is_subclass_of($values, BackedEnum::class)) {
97+
throw new InvalidArgumentException('The provided class must be a BackedEnum.');
98+
}
99+
100+
$values = array_column($values::cases(), 'value');
101+
}
102+
103+
// Keep order and allow complex values (arrays / objects) without forcing uniqueness...
94104
$this->enum = array_values($values);
95105

96106
return $this;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\JsonSchema\Fixtures\Enums;
4+
5+
enum IntBackedEnum: int
6+
{
7+
case One = 1;
8+
case Two = 2;
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\JsonSchema\Fixtures\Enums;
4+
5+
enum StringBackedEnum: string
6+
{
7+
case One = 'one';
8+
case Two = 'two';
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\JsonSchema\Fixtures\Enums;
4+
5+
enum UnitEnum
6+
{
7+
case One;
8+
case Two;
9+
}

tests/JsonSchema/StringTypeTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,17 @@ public function test_it_sets_pattern()
3939
], $type->toArray());
4040
}
4141

42+
public function test_it_sets_format()
43+
{
44+
$type = (new StringType)->default('foo')->format('date');
45+
46+
$this->assertEquals([
47+
'type' => 'string',
48+
'default' => 'foo',
49+
'format' => 'date',
50+
], $type->toArray());
51+
}
52+
4253
public function test_it_sets_enum()
4354
{
4455
$type = (new StringType)->enum(['draft', 'published']);

tests/JsonSchema/TypeTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@
33
namespace Illuminate\Tests\JsonSchema;
44

55
use Illuminate\JsonSchema\JsonSchema;
6+
use Illuminate\Tests\JsonSchema\Fixtures\Enums\IntBackedEnum;
7+
use Illuminate\Tests\JsonSchema\Fixtures\Enums\StringBackedEnum;
8+
use Illuminate\Tests\JsonSchema\Fixtures\Enums\UnitEnum;
9+
use InvalidArgumentException;
610
use Opis\JsonSchema\Resolvers\SchemaResolver;
711
use Opis\JsonSchema\SchemaLoader;
812
use Opis\JsonSchema\Validator;
913
use Opis\Uri\Uri;
1014
use PHPUnit\Framework\Attributes\DataProvider;
1115
use PHPUnit\Framework\TestCase;
16+
use stdClass;
1217
use Stringable;
1318

1419
class TypeTest extends TestCase
@@ -102,6 +107,33 @@ public function test_types_in_object_schema(): void
102107
$this->assertInstanceOf(JsonSchema::class, $schema);
103108
}
104109

110+
public function test_throws_with_invalid_enum_string(): void
111+
{
112+
$this->expectException(InvalidArgumentException::class);
113+
$this->expectExceptionMessage('The provided class must be a BackedEnum.');
114+
$this->expectExceptionCode(0);
115+
116+
JsonSchema::string()->enum('NonExistentEnumClass');
117+
}
118+
119+
public function test_throws_with_not_an_enum_class(): void
120+
{
121+
$this->expectException(InvalidArgumentException::class);
122+
$this->expectExceptionMessage('The provided class must be a BackedEnum.');
123+
$this->expectExceptionCode(0);
124+
125+
JsonSchema::string()->enum(stdClass::class);
126+
}
127+
128+
public function test_throws_with_unit_enum_class(): void
129+
{
130+
$this->expectException(InvalidArgumentException::class);
131+
$this->expectExceptionMessage('The provided class must be a BackedEnum.');
132+
$this->expectExceptionCode(0);
133+
134+
JsonSchema::string()->enum(UnitEnum::class);
135+
}
136+
105137
public static function validSchemasProvider(): array
106138
{
107139
return [
@@ -118,6 +150,7 @@ public static function validSchemasProvider(): array
118150
[JsonSchema::string()->min(1)->max(3), 'a'], // boundary at min
119151
[JsonSchema::string()->pattern('^[A-Z]{2}[0-9]{2}$'), 'AB12'], // complex pattern
120152
[JsonSchema::string()->enum(['', 'x', 'y']), ''], // enum including empty string
153+
[JsonSchema::string()->enum(StringBackedEnum::class), 'one'], // string backed enum cases
121154
[JsonSchema::string()->nullable(), null],
122155
[JsonSchema::string()->nullable(false), ''],
123156

@@ -132,6 +165,7 @@ public static function validSchemasProvider(): array
132165
[JsonSchema::integer()->max(10), 9], // below max
133166
[JsonSchema::integer()->min(1)->max(3), 3], // boundary at max
134167
[JsonSchema::integer()->enum([0, -1, 5]), 0], // enum with zero
168+
[JsonSchema::integer()->enum(IntBackedEnum::class), 1], // integer backed enum cases
135169
[JsonSchema::integer()->default(0), 0], // default value
136170
[JsonSchema::integer()->nullable(), null],
137171
[JsonSchema::integer()->nullable(false), 0],
@@ -265,6 +299,7 @@ public static function invalidSchemasProvider(): array
265299
[JsonSchema::string()->max(0), 'a'], // too long for zero max
266300
[JsonSchema::string()->pattern('^[a]+$'), 'ab'], // pattern mismatch
267301
[JsonSchema::string()->enum(['a', 'b']), 'A'], // case sensitive mismatch
302+
[JsonSchema::string()->enum(StringBackedEnum::class), 'three'], // string backed enum cases mismatch
268303
[JsonSchema::string(), null], // null not allowed
269304
[JsonSchema::string()->nullable(false), null], // not nullable
270305

@@ -279,6 +314,7 @@ public static function invalidSchemasProvider(): array
279314
[JsonSchema::integer()->max(0), 1], // above max boundary
280315
[JsonSchema::integer(), 3.14], // not an integer
281316
[JsonSchema::integer()->enum([1, 2]), 2.5], // not in enum and not an integer
317+
[JsonSchema::integer()->enum(IntBackedEnum::class), 3], // integer backed enum cases mismatch
282318
[JsonSchema::integer()->default(1), null], // wrong type
283319
[JsonSchema::integer()->nullable(false), null], // not nullable
284320

0 commit comments

Comments
 (0)