From e1747b4a127bae512552c49deed06ed56d7ed081 Mon Sep 17 00:00:00 2001 From: codeliner Date: Tue, 28 May 2019 20:33:36 +0200 Subject: [PATCH] Improve schema detection and customizing --- src/JsonSchemaAwareCollection.php | 11 ++ src/JsonSchemaAwareCollectionLogic.php | 67 +++++++ src/JsonSchemaAwareRecord.php | 2 +- src/JsonSchemaAwareRecordLogic.php | 49 ++++-- src/RecordLogic/TypeDetector.php | 66 +++++++ tests/BasicTestCase.php | 11 ++ tests/JsonSchemaAwareRecordLogicTest.php | 156 +++++++++++++++++ tests/Stub/ArrayItemRecord.php | 30 ++++ .../Stub/CollectionItemAllowNestedRecord.php | 30 ++++ tests/Stub/CollectionItemRecord.php | 25 +++ ...llableArrayItemRecordAllowNestedRecord.php | 36 ++++ tests/Stub/NullableArrayItemRecordRecord.php | 30 ++++ tests/Stub/NullableScalarPropsRecord.php | 64 +++++++ tests/Stub/ScalarPropsRecord.php | 64 +++++++ ...ScalarPropsRecordAllowNestedCollection.php | 117 +++++++++++++ tests/Stub/ScalarPropsRecordCollection.php | 112 ++++++++++++ tests/Stub/VoOptionalPropsRecord.php | 85 +++++++++ tests/Stub/VoProp/Age.php | 39 +++++ tests/Stub/VoProp/Member.php | 39 +++++ tests/Stub/VoProp/Score.php | 39 +++++ tests/Stub/VoProp/UserId.php | 39 +++++ tests/Stub/VoPropsRecord.php | 73 ++++++++ tests/Type/FloatTypeTest.php | 83 +++++++++ tests/Type/IntTypeTest.php | 83 +++++++++ tests/Type/ObjectTypeTest.php | 165 ++++++++++++++++++ 25 files changed, 1499 insertions(+), 16 deletions(-) create mode 100644 src/JsonSchemaAwareCollection.php create mode 100644 src/JsonSchemaAwareCollectionLogic.php create mode 100644 src/RecordLogic/TypeDetector.php create mode 100644 tests/BasicTestCase.php create mode 100644 tests/JsonSchemaAwareRecordLogicTest.php create mode 100644 tests/Stub/ArrayItemRecord.php create mode 100644 tests/Stub/CollectionItemAllowNestedRecord.php create mode 100644 tests/Stub/CollectionItemRecord.php create mode 100644 tests/Stub/NullableArrayItemRecordAllowNestedRecord.php create mode 100644 tests/Stub/NullableArrayItemRecordRecord.php create mode 100644 tests/Stub/NullableScalarPropsRecord.php create mode 100644 tests/Stub/ScalarPropsRecord.php create mode 100644 tests/Stub/ScalarPropsRecordAllowNestedCollection.php create mode 100644 tests/Stub/ScalarPropsRecordCollection.php create mode 100644 tests/Stub/VoOptionalPropsRecord.php create mode 100644 tests/Stub/VoProp/Age.php create mode 100644 tests/Stub/VoProp/Member.php create mode 100644 tests/Stub/VoProp/Score.php create mode 100644 tests/Stub/VoProp/UserId.php create mode 100644 tests/Stub/VoPropsRecord.php create mode 100644 tests/Type/FloatTypeTest.php create mode 100644 tests/Type/IntTypeTest.php create mode 100644 tests/Type/ObjectTypeTest.php diff --git a/src/JsonSchemaAwareCollection.php b/src/JsonSchemaAwareCollection.php new file mode 100644 index 0000000..85995cd --- /dev/null +++ b/src/JsonSchemaAwareCollection.php @@ -0,0 +1,11 @@ +implementsInterface(ImmutableRecord::class)) { - return \call_user_func([$classOrType, '__type']); - } - - return self::convertClassToTypeName($classOrType); + return TypeDetector::getTypeFromClass($classOrType, self::__allowNestedSchema()); } /** diff --git a/src/RecordLogic/TypeDetector.php b/src/RecordLogic/TypeDetector.php new file mode 100644 index 0000000..315606c --- /dev/null +++ b/src/RecordLogic/TypeDetector.php @@ -0,0 +1,66 @@ +implementsInterface(JsonSchemaAwareRecord::class)) { + + if($allowNestedSchema) { + return \call_user_func([$classOrType, '__schema']); + } + + return new Type\TypeRef(\call_user_func([$classOrType, '__type'])); + } + + if($refObj->implementsInterface(JsonSchemaAwareCollection::class)) { + return JsonSchema::array(\call_user_func([$classOrType, '__itemSchema'])); + } + + if($scalarSchemaType = self::determineScalarTypeIfPossible($classOrType)) { + return $scalarSchemaType; + } + + return self::convertClassToType($classOrType); + } + + private static function determineScalarTypeIfPossible(string $class): ?Type + { + if(is_callable([$class, 'fromString'])) { + return JsonSchema::string(); + } + + if(is_callable([$class, 'fromInt'])) { + return JsonSchema::integer(); + } + + if(is_callable([$class, 'fromFloat'])) { + return JsonSchema::float(); + } + + if(is_callable([$class, 'fromBool'])) { + return JsonSchema::boolean(); + } + + return null; + } + + private static function convertClassToType(string $class): Type + { + return new Type\TypeRef(\substr(\strrchr($class, '\\'), 1)); + } +} diff --git a/tests/BasicTestCase.php b/tests/BasicTestCase.php new file mode 100644 index 0000000..5a4e424 --- /dev/null +++ b/tests/BasicTestCase.php @@ -0,0 +1,11 @@ + JsonSchema::string(), + 'age' => JsonSchema::integer(), + 'member' => JsonSchema::boolean(), + 'score' => JsonSchema::float() + ]); + + $this->assertEquals($expected->toArray(), $schema->toArray()); + } + + /** + * @test + */ + public function it_marks_props_as_nullable() + { + $schema = NullableScalarPropsRecord::__schema(); + + $expected = JsonSchema::object([ + 'userId' => JsonSchema::string()->asNullable(), + 'age' => JsonSchema::integer()->asNullable(), + 'member' => JsonSchema::boolean()->asNullable(), + 'score' => JsonSchema::float()->asNullable() + ]); + + $this->assertEquals($expected->toArray(), $schema->toArray()); + } + + /** + * @test + */ + public function it_handles_array_item() + { + $schema = ArrayItemRecord::__schema(); + + $expected = JsonSchema::object([ + 'friends' => JsonSchema::array(JsonSchema::string()) + ]); + + $this->assertEquals($expected->toArray(), $schema->toArray()); + } + + /** + * @test + */ + public function it_handles_nullable_array_item_record() + { + $schema = NullableArrayItemRecordRecord::__schema(); + + $expected = JsonSchema::object([ + 'friends' => JsonSchema::array(JsonSchema::typeRef(ScalarPropsRecord::class))->asNullable() + ]); + + $this->assertEquals($expected->toArray(), $schema->toArray()); + + $schema = NullableArrayItemRecordAllowNestedRecord::__schema(); + + $expected = JsonSchema::object([ + 'friends' => JsonSchema::array(JsonSchema::object([ + 'userId' => JsonSchema::string(), + 'age' => JsonSchema::integer(), + 'member' => JsonSchema::boolean(), + 'score' => JsonSchema::float(), + ]))->asNullable() + ]); + + $this->assertEquals($expected->toArray(), $schema->toArray()); + } + + /** + * @test + */ + public function it_uses_item_schema_from_collection() + { + $schema = CollectionItemRecord::__schema(); + + $expected = JsonSchema::object([ + 'friends' => JsonSchema::array(JsonSchema::typeRef(ScalarPropsRecord::__type())) + ]); + + $this->assertEquals($expected->toArray(), $schema->toArray()); + + $schema = CollectionItemAllowNestedRecord::__schema(); + + $expected = JsonSchema::object([ + 'friends' => JsonSchema::array(JsonSchema::object([ + 'userId' => JsonSchema::string(), + 'age' => JsonSchema::integer(), + 'member' => JsonSchema::boolean(), + 'score' => JsonSchema::float(), + ])) + ]); + + $this->assertEquals($expected->toArray(), $schema->toArray()); + } + + /** + * @test + */ + public function it_detects_scalar_types_through_method_analysis_of_vo_classes() + { + $schema = VoPropsRecord::__schema(); + + $expected = JsonSchema::object([ + 'userId' => JsonSchema::string(), + 'age' => JsonSchema::integer(), + 'member' => JsonSchema::boolean(), + 'score' => JsonSchema::float() + ]); + + $this->assertEquals($expected->toArray(), $schema->toArray()); + } + + /** + * @test + */ + public function it_respects_optional_properties() + { + $schema = VoOptionalPropsRecord::__schema(); + + $expected = JsonSchema::object([ + 'userId' => JsonSchema::string(), + 'age' => JsonSchema::integer(), + 'member' => JsonSchema::boolean(), + ], [ + 'score' => JsonSchema::float() + ]); + + $this->assertEquals($expected->toArray(), $schema->toArray()); + } +} diff --git a/tests/Stub/ArrayItemRecord.php b/tests/Stub/ArrayItemRecord.php new file mode 100644 index 0000000..9b40849 --- /dev/null +++ b/tests/Stub/ArrayItemRecord.php @@ -0,0 +1,30 @@ + 'string']; + } + + /** + * @var string[] + */ + private $friends; + + /** + * @return string[] + */ + public function friends(): array + { + return $this->friends; + } +} diff --git a/tests/Stub/CollectionItemAllowNestedRecord.php b/tests/Stub/CollectionItemAllowNestedRecord.php new file mode 100644 index 0000000..3bb40ac --- /dev/null +++ b/tests/Stub/CollectionItemAllowNestedRecord.php @@ -0,0 +1,30 @@ +friends; + } + + private static function __allowNestedSchema(): bool + { + return true; + } +} diff --git a/tests/Stub/CollectionItemRecord.php b/tests/Stub/CollectionItemRecord.php new file mode 100644 index 0000000..7652301 --- /dev/null +++ b/tests/Stub/CollectionItemRecord.php @@ -0,0 +1,25 @@ +friends; + } +} diff --git a/tests/Stub/NullableArrayItemRecordAllowNestedRecord.php b/tests/Stub/NullableArrayItemRecordAllowNestedRecord.php new file mode 100644 index 0000000..d2f7421 --- /dev/null +++ b/tests/Stub/NullableArrayItemRecordAllowNestedRecord.php @@ -0,0 +1,36 @@ + ScalarPropsRecord::class]; + } + + private static function __allowNestedSchema(): bool + { + return true; + } + + + /** + * @var ScalarPropsRecord[]|null + */ + private $friends; + + /** + * @return ScalarPropsRecord[]|null + */ + public function friends(): ?array + { + return $this->friends; + } +} diff --git a/tests/Stub/NullableArrayItemRecordRecord.php b/tests/Stub/NullableArrayItemRecordRecord.php new file mode 100644 index 0000000..358fe81 --- /dev/null +++ b/tests/Stub/NullableArrayItemRecordRecord.php @@ -0,0 +1,30 @@ + ScalarPropsRecord::class]; + } + + /** + * @var ScalarPropsRecord[]|null + */ + private $friends; + + /** + * @return ScalarPropsRecord[]|null + */ + public function friends(): ?array + { + return $this->friends; + } +} diff --git a/tests/Stub/NullableScalarPropsRecord.php b/tests/Stub/NullableScalarPropsRecord.php new file mode 100644 index 0000000..d66725c --- /dev/null +++ b/tests/Stub/NullableScalarPropsRecord.php @@ -0,0 +1,64 @@ +userId; + } + + /** + * @return int|null + */ + public function age(): ?int + { + return $this->age; + } + + /** + * @return bool|null + */ + public function member(): ?bool + { + return $this->member; + } + + /** + * @return float|null + */ + public function score(): ?float + { + return $this->score; + } +} diff --git a/tests/Stub/ScalarPropsRecord.php b/tests/Stub/ScalarPropsRecord.php new file mode 100644 index 0000000..fb8f244 --- /dev/null +++ b/tests/Stub/ScalarPropsRecord.php @@ -0,0 +1,64 @@ +userId; + } + + /** + * @return int + */ + public function age(): int + { + return $this->age; + } + + /** + * @return bool + */ + public function member(): bool + { + return $this->member; + } + + /** + * @return float + */ + public function score(): float + { + return $this->score; + } +} diff --git a/tests/Stub/ScalarPropsRecordAllowNestedCollection.php b/tests/Stub/ScalarPropsRecordAllowNestedCollection.php new file mode 100644 index 0000000..d843b8e --- /dev/null +++ b/tests/Stub/ScalarPropsRecordAllowNestedCollection.php @@ -0,0 +1,117 @@ +items = $items; + } + + public function push(ScalarPropsRecord $item): self + { + $copy = clone $this; + $copy->items[] = $item; + return $copy; + } + + public function pop(): self + { + $copy = clone $this; + \array_pop($copy->items); + return $copy; + } + + public function first(): ?ScalarPropsRecord + { + return $this->items[0] ?? null; + } + + public function last(): ?ScalarPropsRecord + { + if (count($this->items) === 0) { + return null; + } + + return $this->items[count($this->items) - 1]; + } + + public function contains(ScalarPropsRecord $item): bool + { + foreach ($this->items as $existingItem) { + if ($existingItem->equals($item)) { + return true; + } + } + + return false; + } + + /** + * @return ScalarPropsRecord[] + */ + public function items(): array + { + return $this->items; + } + + public function toArray(): array + { + return \array_map(function (ScalarPropsRecord $item) { + return $item->toArray(); + }, $this->items); + } + + public function equals($other): bool + { + if (!$other instanceof self) { + return false; + } + + return $this->toArray() === $other->toArray(); + } + + public function __toString(): string + { + return \json_encode($this->toArray()); + } +} diff --git a/tests/Stub/ScalarPropsRecordCollection.php b/tests/Stub/ScalarPropsRecordCollection.php new file mode 100644 index 0000000..85a698b --- /dev/null +++ b/tests/Stub/ScalarPropsRecordCollection.php @@ -0,0 +1,112 @@ +items = $items; + } + + public function push(ScalarPropsRecord $item): self + { + $copy = clone $this; + $copy->items[] = $item; + return $copy; + } + + public function pop(): self + { + $copy = clone $this; + \array_pop($copy->items); + return $copy; + } + + public function first(): ?ScalarPropsRecord + { + return $this->items[0] ?? null; + } + + public function last(): ?ScalarPropsRecord + { + if (count($this->items) === 0) { + return null; + } + + return $this->items[count($this->items) - 1]; + } + + public function contains(ScalarPropsRecord $item): bool + { + foreach ($this->items as $existingItem) { + if ($existingItem->equals($item)) { + return true; + } + } + + return false; + } + + /** + * @return ScalarPropsRecord[] + */ + public function items(): array + { + return $this->items; + } + + public function toArray(): array + { + return \array_map(function (ScalarPropsRecord $item) { + return $item->toArray(); + }, $this->items); + } + + public function equals($other): bool + { + if (!$other instanceof self) { + return false; + } + + return $this->toArray() === $other->toArray(); + } + + public function __toString(): string + { + return \json_encode($this->toArray()); + } +} diff --git a/tests/Stub/VoOptionalPropsRecord.php b/tests/Stub/VoOptionalPropsRecord.php new file mode 100644 index 0000000..48191c3 --- /dev/null +++ b/tests/Stub/VoOptionalPropsRecord.php @@ -0,0 +1,85 @@ +score) { + $this->score = Score::fromFloat(1.0); + } + } + + /** + * @return UserId + */ + public function userId(): UserId + { + return $this->userId; + } + + /** + * @return Age + */ + public function age(): Age + { + return $this->age; + } + + /** + * @return Member + */ + public function member(): Member + { + return $this->member; + } + + /** + * @return Score + */ + public function score(): Score + { + return $this->score; + } +} diff --git a/tests/Stub/VoProp/Age.php b/tests/Stub/VoProp/Age.php new file mode 100644 index 0000000..acb9229 --- /dev/null +++ b/tests/Stub/VoProp/Age.php @@ -0,0 +1,39 @@ +age = $age; + } + + public function toInt(): int + { + return $this->age; + } + + public function equals($other): bool + { + if(!$other instanceof self) { + return false; + } + + return $this->age === $other->age; + } + + public function __toString(): string + { + return (string)$this->age; + } + +} diff --git a/tests/Stub/VoProp/Member.php b/tests/Stub/VoProp/Member.php new file mode 100644 index 0000000..af5e5fb --- /dev/null +++ b/tests/Stub/VoProp/Member.php @@ -0,0 +1,39 @@ +flag = $flag; + } + + public function toBool(): bool + { + return $this->flag; + } + + public function equals($other): bool + { + if(!$other instanceof self) { + return false; + } + + return $this->flag === $other->flag; + } + + public function __toString(): string + { + return $this->flag ? 'TRUE' : 'FALSE'; + } + +} diff --git a/tests/Stub/VoProp/Score.php b/tests/Stub/VoProp/Score.php new file mode 100644 index 0000000..3badd7c --- /dev/null +++ b/tests/Stub/VoProp/Score.php @@ -0,0 +1,39 @@ +score = $score; + } + + public function toFloat(): float + { + return $this->score; + } + + public function equals($other): bool + { + if(!$other instanceof self) { + return false; + } + + return $this->score === $other->score; + } + + public function __toString(): string + { + return (string)$this->score; + } + +} diff --git a/tests/Stub/VoProp/UserId.php b/tests/Stub/VoProp/UserId.php new file mode 100644 index 0000000..6a7b2b5 --- /dev/null +++ b/tests/Stub/VoProp/UserId.php @@ -0,0 +1,39 @@ +userId = $userId; + } + + public function toString(): string + { + return $this->userId; + } + + public function equals($other): bool + { + if(!$other instanceof self) { + return false; + } + + return $this->userId === $other->userId; + } + + public function __toString(): string + { + return $this->userId; + } + +} diff --git a/tests/Stub/VoPropsRecord.php b/tests/Stub/VoPropsRecord.php new file mode 100644 index 0000000..a3407c7 --- /dev/null +++ b/tests/Stub/VoPropsRecord.php @@ -0,0 +1,73 @@ +userId; + } + + /** + * @return Age + */ + public function age(): Age + { + return $this->age; + } + + /** + * @return Member + */ + public function member(): Member + { + return $this->member; + } + + /** + * @return Score + */ + public function score(): Score + { + return $this->score; + } +} diff --git a/tests/Type/FloatTypeTest.php b/tests/Type/FloatTypeTest.php new file mode 100644 index 0000000..9feec21 --- /dev/null +++ b/tests/Type/FloatTypeTest.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace EventEngineTest\JsonSchema\Type; + +use EventEngine\JsonSchema\Type\FloatType; +use EventEngineTest\JsonSchema\BasicTestCase; + +final class FloatTypeTest extends BasicTestCase +{ + /** + * @test + */ + public function it_creates_float_type_with_minimum() + { + $floatType = (new FloatType())->withMinimum(0.2); + + $this->assertEquals( + [ + 'type' => 'number', + 'minimum' => 0.2, + ], + $floatType->toArray() + ); + } + + /** + * @test + */ + public function it_creates_float_type_with_maximum() + { + $floatType = (new FloatType())->withMaximum(0.7); + + $this->assertEquals( + [ + 'type' => 'number', + 'maximum' => 0.7, + ], + $floatType->toArray() + ); + } + + /** + * @test + */ + public function it_creates_float_type_with_range() + { + $floatType = (new FloatType())->withRange(0.2, 0.7); + + $this->assertEquals( + [ + 'type' => 'number', + 'minimum' => 0.2, + 'maximum' => 0.7, + ], + $floatType->toArray() + ); + } + + /** + * @test + */ + public function it_creates_float_with_custom_validation_through_constructor() + { + $floatType = new FloatType(['multipleOf' => 2.5]); + + $this->assertEquals( + [ + 'type' => 'number', + 'multipleOf' => 2.5, + ], + $floatType->toArray() + ); + } +} diff --git a/tests/Type/IntTypeTest.php b/tests/Type/IntTypeTest.php new file mode 100644 index 0000000..5c7ad12 --- /dev/null +++ b/tests/Type/IntTypeTest.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace EventEngineTest\JsonSchema\Type; + +use EventEngine\JsonSchema\Type\IntType; +use EventEngineTest\JsonSchema\BasicTestCase; + +final class IntTypeTest extends BasicTestCase +{ + /** + * @test + */ + public function it_creates_int_type_with_minimum() + { + $floatType = (new IntType())->withMinimum(2); + + $this->assertEquals( + [ + 'type' => 'integer', + 'minimum' => 2, + ], + $floatType->toArray() + ); + } + + /** + * @test + */ + public function it_creates_int_type_with_maximum() + { + $floatType = (new IntType())->withMaximum(7); + + $this->assertEquals( + [ + 'type' => 'integer', + 'maximum' => 7, + ], + $floatType->toArray() + ); + } + + /** + * @test + */ + public function it_creates_int_type_with_range() + { + $floatType = (new IntType())->withRange(2, 7); + + $this->assertEquals( + [ + 'type' => 'integer', + 'minimum' => 2, + 'maximum' => 7, + ], + $floatType->toArray() + ); + } + + /** + * @test + */ + public function it_creates_int_with_custom_validation_through_constructor() + { + $floatType = new IntType(['multipleOf' => 25]); + + $this->assertEquals( + [ + 'type' => 'integer', + 'multipleOf' => 25, + ], + $floatType->toArray() + ); + } +} diff --git a/tests/Type/ObjectTypeTest.php b/tests/Type/ObjectTypeTest.php new file mode 100644 index 0000000..f07a1c5 --- /dev/null +++ b/tests/Type/ObjectTypeTest.php @@ -0,0 +1,165 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace EventEngineTest\JsonSchema\Type; + +use EventEngine\JsonSchema\JsonSchema; +use EventEngine\JsonSchema\Type\ObjectType; +use EventEngineTest\JsonSchema\BasicTestCase; + +final class ObjectTypeTest extends BasicTestCase +{ + /** + * @test + */ + public function it_converts_to_array() + { + $object = new ObjectType([ + 'name' => JsonSchema::string(), + ]); + + $this->assertEquals([ + 'type' => JsonSchema::TYPE_OBJECT, + 'properties' => [ + 'name' => [ + 'type' => JsonSchema::TYPE_STRING, + ], + ], + 'required' => ['name'], + 'additionalProperties' => false, + ], $object->toArray()); + } + + /** + * @test + */ + public function it_can_be_a_nullable_type() + { + $object = (new ObjectType([ + 'name' => JsonSchema::string(), + ]))->asNullable(); + + $this->assertEquals([ + 'type' => [JsonSchema::TYPE_OBJECT, JsonSchema::TYPE_NULL], + 'properties' => [ + 'name' => [ + 'type' => JsonSchema::TYPE_STRING, + ], + ], + 'required' => ['name'], + 'additionalProperties' => false, + ], $object->toArray()); + } + + /** + * @test + */ + public function it_can_have_optional_properties() + { + $object = (new ObjectType( + [ + 'name' => JsonSchema::string(), + ], + [ + 'age' => JsonSchema::integer()->withRange(18, 150), + ]))->asNullable(); + + $this->assertEquals([ + 'type' => [JsonSchema::TYPE_OBJECT, JsonSchema::TYPE_NULL], + 'properties' => [ + 'name' => [ + 'type' => JsonSchema::TYPE_STRING, + ], + 'age' => [ + 'type' => JsonSchema::TYPE_INT, + 'minimum' => 18, + 'maximum' => 150, + ], + ], + 'required' => ['name'], + 'additionalProperties' => false, + ], $object->toArray()); + } + + /** + * @test + */ + public function it_can_only_have_optional_props() + { + $object = (new ObjectType())->withMergedOptionalProps(['age' => JsonSchema::integer()->withRange(18, 150)]); + + $this->assertEquals([ + 'type' => JsonSchema::TYPE_OBJECT, + 'properties' => [ + 'age' => [ + 'type' => JsonSchema::TYPE_INT, + 'minimum' => 18, + 'maximum' => 150, + ], + ], + 'required' => [], + 'additionalProperties' => false, + ], $object->toArray()); + } + + /** + * @test + */ + public function it_can_add_more_required_props() + { + $object = new ObjectType([ + 'name' => JsonSchema::string(), + ]); + + $object = $object->withMergedRequiredProps(['id' => JsonSchema::string()]); + + $this->assertEquals([ + 'type' => JsonSchema::TYPE_OBJECT, + 'properties' => [ + 'name' => [ + 'type' => JsonSchema::TYPE_STRING, + ], + 'id' => [ + 'type' => JsonSchema::TYPE_STRING, + ], + ], + 'required' => ['name', 'id'], + 'additionalProperties' => false, + ], $object->toArray()); + } + + /** + * @test + */ + public function it_can_be_annotated() + { + $object = (new ObjectType([ + 'name' => JsonSchema::string()->entitled('Name')->describedAs('The name'), + ])) + ->entitled('Object') + ->describedAs('An object'); + + $this->assertEquals([ + 'type' => JsonSchema::TYPE_OBJECT, + 'properties' => [ + 'name' => [ + 'type' => JsonSchema::TYPE_STRING, + 'title' => 'Name', + 'description' => 'The name', + ], + ], + 'required' => ['name'], + 'additionalProperties' => false, + 'title' => 'Object', + 'description' => 'An object', + ], $object->toArray()); + } +}