diff --git a/src/Concerns/BuildsArray.php b/src/Concerns/BuildsArray.php index 6f4394b..4bb6493 100644 --- a/src/Concerns/BuildsArray.php +++ b/src/Concerns/BuildsArray.php @@ -62,9 +62,13 @@ private function unwrapValue(mixed $value): mixed } if ($value instanceof JsonSchema) { - // Inline schemas in OpenAPI must not carry the JSON Schema $schema URI or - // a builder-assigned title, so strip both when embedding. - return $value->toArray(includeSchemaRef: false, includeTitle: false); + // Inline schemas must not carry the JSON Schema $schema URI. Constructor + // titles are builder-assigned names and are stripped; a title() value that + // differs from getInitialTitle() was set deliberately and is kept. + $title = $value->getTitle(); + $includeTitle = $title !== null && $title !== $value->getInitialTitle(); + + return $value->toArray(includeSchemaRef: false, includeTitle: $includeTitle); } if (is_array($value)) { diff --git a/src/Objects/Operation.php b/src/Objects/Operation.php index 816a573..9a86305 100644 --- a/src/Objects/Operation.php +++ b/src/Objects/Operation.php @@ -106,9 +106,13 @@ public function getMethod(): HttpMethod return $this->httpMethod; } - public function tags(string ...$tags): self + public function tags(Tag|string ...$tags): self { - $this->tags = array_values($tags); + $this->tags = []; + + foreach ($tags as $tag) { + $this->tags[] = $tag instanceof Tag ? $tag->getName() : $tag; + } return $this; } diff --git a/src/Objects/Response.php b/src/Objects/Response.php index afafc6a..8180b04 100644 --- a/src/Objects/Response.php +++ b/src/Objects/Response.php @@ -33,7 +33,7 @@ final class Response implements Serializable, HasExtensionsInterface '500' => 'Internal Server Error', ]; - private ?string $description = null; + private ?string $description; /** * @var array diff --git a/tests/Unit/Concerns/BuildsArrayTest.php b/tests/Unit/Concerns/BuildsArrayTest.php index f00e5d0..af193cf 100644 --- a/tests/Unit/Concerns/BuildsArrayTest.php +++ b/tests/Unit/Concerns/BuildsArrayTest.php @@ -195,3 +195,54 @@ public function assemble(array $fields): array ], ]); }); + +it('keeps a JsonSchema title that differs from the constructor argument', function (): void { + $objectSchema = Schema::object('Consult')->title('consults'); + + expect($objectSchema->getTitle())->toBe('consults') + ->and($objectSchema->getInitialTitle())->toBe('Consult'); + + $out = (new BuildsArrayFixture())->assemble([ + 'schema' => $objectSchema, + ]); + + expect($out)->toBe([ + 'schema' => [ + 'type' => 'object', + 'title' => 'consults', + ], + ]); + expect($out['schema'])->not->toHaveKey('$schema'); +}); + +it('keeps a JsonSchema title set on a schema that had no constructor title', function (): void { + $stringSchema = Schema::string()->title('IsoDateTime'); + + expect($stringSchema->getTitle())->toBe('IsoDateTime') + ->and($stringSchema->getInitialTitle())->toBeNull(); + + $out = (new BuildsArrayFixture())->assemble([ + 'schema' => $stringSchema, + ]); + + expect($out)->toBe([ + 'schema' => [ + 'type' => 'string', + 'title' => 'IsoDateTime', + ], + ]); +}); + +it('strips a JsonSchema title that merely restates the constructor argument', function (): void { + $stringSchema = Schema::string('IsoDateTime')->title('IsoDateTime'); + + $out = (new BuildsArrayFixture())->assemble([ + 'schema' => $stringSchema, + ]); + + expect($out)->toBe([ + 'schema' => [ + 'type' => 'string', + ], + ]); +}); diff --git a/tests/Unit/Objects/MediaTypeTest.php b/tests/Unit/Objects/MediaTypeTest.php index b140701..bae6d4b 100644 --- a/tests/Unit/Objects/MediaTypeTest.php +++ b/tests/Unit/Objects/MediaTypeTest.php @@ -38,6 +38,17 @@ ]); }); +it('keeps a deliberate schema title when embedding', function (): void { + $mediaType = MediaType::json()->schema(Schema::object('Consult')->title('consults')); + + expect($mediaType->toArray())->toBe([ + 'schema' => [ + 'type' => 'object', + 'title' => 'consults', + ], + ]); +}); + it('accepts a Reference', function (): void { $mediaType = MediaType::json(Reference::schema('User')); diff --git a/tests/Unit/Objects/OperationTest.php b/tests/Unit/Objects/OperationTest.php index b9f8fd4..ba02f7a 100644 --- a/tests/Unit/Objects/OperationTest.php +++ b/tests/Unit/Objects/OperationTest.php @@ -3,6 +3,7 @@ declare(strict_types=1); use Cortex\JsonSchema\Schema; +use Cortex\OpenApi\Objects\Tag; use Cortex\OpenApi\Objects\Server; use Cortex\OpenApi\Enums\HttpMethod; use Cortex\OpenApi\Objects\Callback; @@ -50,6 +51,22 @@ ]); }); +it('resolves Tag objects to their names', function (): void { + $operation = Operation::get()->tags(Tag::create('Users'), Tag::create('Admin')); + + expect($operation->toArray())->toBe([ + 'tags' => ['Users', 'Admin'], + ]); +}); + +it('accepts a mix of Tag objects and tag name strings', function (): void { + $operation = Operation::get()->tags(Tag::create('Users'), 'Admin'); + + expect($operation->toArray())->toBe([ + 'tags' => ['Users', 'Admin'], + ]); +}); + it('emits parameters, requestBody, and responses', function (): void { $operation = Operation::post() ->parameters(Parameter::query('dry_run', Schema::boolean())) diff --git a/tests/Unit/Objects/ParameterTest.php b/tests/Unit/Objects/ParameterTest.php index 1eaa2fc..933b2b5 100644 --- a/tests/Unit/Objects/ParameterTest.php +++ b/tests/Unit/Objects/ParameterTest.php @@ -37,6 +37,33 @@ ]); }); +it('strips a constructor schema title when embedding', function (): void { + $parameter = Parameter::query('when')->schema(Schema::string('IsoDateTime')); + + expect($parameter->toArray())->toBe([ + 'name' => 'when', + 'in' => 'query', + 'schema' => [ + 'type' => 'string', + ], + ]); +}); + +it('keeps a deliberate schema title when embedding', function (): void { + $parameter = Parameter::query('when')->schema( + Schema::string('IsoDateTime')->title('ISO 8601 date-time'), + ); + + expect($parameter->toArray())->toBe([ + 'name' => 'when', + 'in' => 'query', + 'schema' => [ + 'type' => 'string', + 'title' => 'ISO 8601 date-time', + ], + ]); +}); + it('builds a header parameter', function (): void { $parameter = Parameter::header('X-Trace', Schema::string())->required(true);