diff --git a/docs/openapi/components.mdx b/docs/openapi/components.mdx index 147f51b..317a8c5 100644 --- a/docs/openapi/components.mdx +++ b/docs/openapi/components.mdx @@ -153,15 +153,15 @@ $components = Components::create() ); ``` -Reference them from operations with `Response::ref()`. A reference carries no status code, so pass it to `response()` along with the code it answers: +Reference them from operations with `->ref()`, which keeps the status code from the named constructor: ```php Operation::get() ->responses( Response::ok()->json(Reference::schema('User')), - ) - ->response(404, Response::ref('NotFound')) - ->response(401, Response::ref('Unauthorized')); + Response::notFound()->ref('NotFound'), + Response::unauthorized()->ref('Unauthorized'), + ); ``` ## Request Bodies @@ -184,8 +184,8 @@ $components = Components::create() ); // Reference -Operation::post()->requestBody(RequestBody::ref('CreateUser')); -Operation::patch()->requestBody(RequestBody::ref('UpdateUser')); +Operation::post()->requestBody(Reference::requestBody('CreateUser')); +Operation::patch()->requestBody(Reference::requestBody('UpdateUser')); ``` ## Security Schemes diff --git a/docs/openapi/paths-and-operations.mdx b/docs/openapi/paths-and-operations.mdx index cd0c638..519151b 100644 --- a/docs/openapi/paths-and-operations.mdx +++ b/docs/openapi/paths-and-operations.mdx @@ -138,7 +138,7 @@ Operation::get() ### Attaching Responses -`responses()` takes `Response` objects and keys them from their own status code, so you rarely repeat yourself. A `$ref` has no status code of its own, so pass those to `response()` with an explicit key: +`responses()` takes `Response` objects and keys them from their own status code, so you rarely repeat yourself. Point a status code at a reusable response with `->ref()`: ```php use Cortex\OpenApi\Objects\Response; @@ -147,12 +147,17 @@ use Cortex\OpenApi\Objects\Reference; Operation::get() ->responses( Response::ok()->json(Reference::schema('Article')), - ) - ->response(404, Response::ref('NotFound')) - ->response(401, Response::ref('Unauthorized')); + Response::notFound()->ref('NotFound'), + ); ``` -`responses()` replaces the whole map, so call it first and add any references with `response()` afterwards. +`response()` sets a single status key, which is useful for a code with no named constructor or when adding to an existing map — `responses()` replaces the map wholesale: + +```php +Operation::get() + ->responses(Response::ok()->json(Reference::schema('Article'))) + ->response(418, Reference::response('Teapot')); +``` See [Responses](/openapi/responses) for headers, links, and reusable response components. diff --git a/docs/openapi/quickstart.mdx b/docs/openapi/quickstart.mdx index 11f1dd8..fa852ba 100644 --- a/docs/openapi/quickstart.mdx +++ b/docs/openapi/quickstart.mdx @@ -277,14 +277,14 @@ Reference::example('AdminUser') // #/components/examples/AdminUser Reference::callback('EventWebhook') // #/components/callbacks/EventWebhook Reference::pathItem('LegacyPets') // #/components/pathItems/LegacyPets -// Generic reference — use when the bucket isn't covered above -Reference::to('#/components/schemas/Pet') - -// Shortcut on the target class (equivalent, and communicates intent) -Response::ref('NotFound') // pass to Operation::response($status, ...) -Parameter::ref('PetId') -RequestBody::ref('CreateUser') -Example::ref('AdminUser') +// Generic reference — for a pointer outside the component buckets +Reference::to('./common.yaml#/components/schemas/Pet') +``` + +Every reference is built here, so there is one place to look and one spelling to remember. The exception is a response inside `Operation::responses()`, which needs a status code to key on — see [Responses](/openapi/responses): + +```php +Response::notFound()->ref('NotFound') ``` ## Vendor Extensions diff --git a/docs/openapi/request-bodies.mdx b/docs/openapi/request-bodies.mdx index 5c23bfb..2d47de2 100644 --- a/docs/openapi/request-bodies.mdx +++ b/docs/openapi/request-bodies.mdx @@ -223,7 +223,7 @@ $components = Components::create() // Reference from an operation Operation::post() ->operationId('articles.create') - ->requestBody(RequestBody::ref('CreateArticle')); + ->requestBody(Reference::requestBody('CreateArticle')); ``` ## Complete Example diff --git a/docs/openapi/responses.mdx b/docs/openapi/responses.mdx index f4b6537..8bdbb5d 100644 --- a/docs/openapi/responses.mdx +++ b/docs/openapi/responses.mdx @@ -6,7 +6,7 @@ icon: 'file-down' ## Response -`Response` describes a single HTTP status code outcome. Pass one or more of them to `Operation::responses()` and each is keyed from its own status code. References carry no status code, so attach those with `->response()`: +`Response` describes a single HTTP status code outcome. Pass one or more of them to `Operation::responses()` and each is keyed from its own status code: ```php use Cortex\OpenApi\Objects\Response; @@ -18,13 +18,23 @@ Operation::get() Response::ok()->json(Reference::schema('User')), Response::notFound()->json(Reference::schema('Error')), ); +``` + +To answer a status code with a reusable response from `Components`, use `->ref()`. The status still comes from the named constructor, so it belongs in the same `responses()` call: -// A reusable component response — the status code is the first argument +```php Operation::get() - ->responses(Response::ok()->json(Reference::schema('User'))) - ->response(404, Response::ref('NotFound')); + ->responses( + Response::ok()->json(Reference::schema('User')), + Response::notFound()->ref('NotFound'), + Response::unauthorized()->ref('Unauthorized'), + ); ``` + +Elsewhere, references are built with `Reference::response('NotFound')` — see [Working with References](/openapi/quickstart). `->ref()` exists on `Response` because `responses()` keys each entry by status code, and a bare reference has none. Slots that supply the status themselves, such as `Components::response()` and `Operation::response(404, ...)`, take `Reference::response()`. + + ### Named Constructors Every common HTTP status code has a named constructor that pre-populates the standard description. Override it with `->description()`. @@ -202,9 +212,11 @@ $components = Components::create() // Reference from an operation Operation::get() - ->responses(Response::ok()->json($schema)) - ->response(404, Response::ref('NotFound')) - ->response(401, Response::ref('Unauthorized')); + ->responses( + Response::ok()->json($schema), + Response::notFound()->ref('NotFound'), + Response::unauthorized()->ref('Unauthorized'), + ); ``` ## Inline Examples on Responses diff --git a/docs/openapi/webhooks-and-callbacks.mdx b/docs/openapi/webhooks-and-callbacks.mdx index 02a3419..edeb4ca 100644 --- a/docs/openapi/webhooks-and-callbacks.mdx +++ b/docs/openapi/webhooks-and-callbacks.mdx @@ -211,7 +211,7 @@ Operation::post() ```php Operation::post() - ->callback('onEvent', Callback::ref('EventWebhook')) + ->callback('onEvent', Reference::callback('EventWebhook')) ->callback('onError', Callback::create()->expression( '{$request.body#/errorUrl}', PathItem::create('')->operations( @@ -257,7 +257,7 @@ $components = Components::create() Operation::post() ->operationId('hooks.subscribe') ->callbacks([ - 'onEvent' => Callback::ref('EventWebhook'), + 'onEvent' => Reference::callback('EventWebhook'), ]); ``` @@ -339,7 +339,7 @@ $doc = OpenApi::create() ) ->responses(Response::created()) ->callbacks([ - 'onEvent' => Callback::ref('GenericWebhook'), + 'onEvent' => Reference::callback('GenericWebhook'), ]), ), ); diff --git a/src/Objects/Callback.php b/src/Objects/Callback.php index daa296c..a5814cb 100644 --- a/src/Objects/Callback.php +++ b/src/Objects/Callback.php @@ -22,11 +22,6 @@ public static function create(): self return new self(); } - public static function ref(string $name, ?string $summary = null, ?string $description = null): Reference - { - return Reference::callback($name, $summary, $description); - } - public function expression(string $runtimeExpression, PathItem $pathItem): self { $this->expressions[$runtimeExpression] = $pathItem; diff --git a/src/Objects/Example.php b/src/Objects/Example.php index e4f3ee3..f6ea206 100644 --- a/src/Objects/Example.php +++ b/src/Objects/Example.php @@ -29,11 +29,6 @@ public static function create(): self return new self(); } - public static function ref(string $name, ?string $summary = null, ?string $description = null): Reference - { - return Reference::example($name, $summary, $description); - } - public function summary(?string $summary): self { $this->summary = $summary; diff --git a/src/Objects/Header.php b/src/Objects/Header.php index ff1b60a..8ac0e1d 100644 --- a/src/Objects/Header.php +++ b/src/Objects/Header.php @@ -54,11 +54,6 @@ public static function create(): self return new self(); } - public static function ref(string $name, ?string $summary = null, ?string $description = null): Reference - { - return Reference::header($name, $summary, $description); - } - public function description(?string $description): self { $this->description = $description; diff --git a/src/Objects/Link.php b/src/Objects/Link.php index aa30412..e195e3d 100644 --- a/src/Objects/Link.php +++ b/src/Objects/Link.php @@ -36,11 +36,6 @@ public static function create(): self return new self(); } - public static function ref(string $name, ?string $summary = null, ?string $description = null): Reference - { - return Reference::link($name, $summary, $description); - } - public function operationRef(?string $operationRef): self { $this->operationRef = $operationRef; diff --git a/src/Objects/Parameter.php b/src/Objects/Parameter.php index c8310be..7bc1134 100644 --- a/src/Objects/Parameter.php +++ b/src/Objects/Parameter.php @@ -90,11 +90,6 @@ public static function cookie(string $name, JsonSchema|array|Reference|null $sch return new self($name, In::Cookie, $schema); } - public static function ref(string $name, ?string $summary = null, ?string $description = null): Reference - { - return Reference::parameter($name, $summary, $description); - } - public function getName(): string { return $this->name; diff --git a/src/Objects/PathItem.php b/src/Objects/PathItem.php index 95bc558..43c6e42 100644 --- a/src/Objects/PathItem.php +++ b/src/Objects/PathItem.php @@ -42,11 +42,6 @@ public static function create(string $path): self return new self($path); } - public static function ref(string $name, ?string $summary = null, ?string $description = null): Reference - { - return Reference::pathItem($name, $summary, $description); - } - public function getPath(): string { return $this->path; diff --git a/src/Objects/RequestBody.php b/src/Objects/RequestBody.php index 9cc8c5c..4615bf2 100644 --- a/src/Objects/RequestBody.php +++ b/src/Objects/RequestBody.php @@ -29,11 +29,6 @@ public static function create(): self return new self(); } - public static function ref(string $name, ?string $summary = null, ?string $description = null): Reference - { - return Reference::requestBody($name, $summary, $description); - } - public function description(?string $description): self { $this->description = $description; diff --git a/src/Objects/Response.php b/src/Objects/Response.php index 8180b04..0983b03 100644 --- a/src/Objects/Response.php +++ b/src/Objects/Response.php @@ -35,6 +35,8 @@ final class Response implements Serializable, HasExtensionsInterface private ?string $description; + private ?Reference $reference = null; + /** * @var array */ @@ -126,9 +128,18 @@ public static function internalServerError(): self return new self('500'); } - public static function ref(string $name, ?string $summary = null, ?string $description = null): Reference + /** + * Answer this status code with a reusable response from Components. + * + * The status code is kept, so the result can go straight into Operation::responses(). + * The document carries the $ref alone — a Reference Object has no room for the other + * fields set on this response. + */ + public function ref(string $name, ?string $summary = null, ?string $description = null): self { - return Reference::response($name, $summary, $description); + $this->reference = Reference::response($name, $summary, $description); + + return $this; } public function getStatusCode(): string @@ -203,6 +214,10 @@ public function link(string $name, Link|Reference $link): self */ public function toArray(): array { + if ($this->reference instanceof Reference) { + return $this->reference->toArray(); + } + return $this->buildArray([ 'description' => $this->description, 'headers' => $this->headers, diff --git a/src/Objects/SecurityScheme.php b/src/Objects/SecurityScheme.php index 640768f..ffef819 100644 --- a/src/Objects/SecurityScheme.php +++ b/src/Objects/SecurityScheme.php @@ -72,11 +72,6 @@ public static function mutualTls(): self return new self(SecuritySchemeType::MutualTls); } - public static function ref(string $name, ?string $summary = null, ?string $description = null): Reference - { - return Reference::securityScheme($name, $summary, $description); - } - public function getType(): SecuritySchemeType { return $this->securitySchemeType; diff --git a/tests/Unit/Objects/CallbackTest.php b/tests/Unit/Objects/CallbackTest.php index a2da579..259daa1 100644 --- a/tests/Unit/Objects/CallbackTest.php +++ b/tests/Unit/Objects/CallbackTest.php @@ -37,9 +37,3 @@ ], ]); }); - -it('supports ref() shortcut', function (): void { - expect(Callback::ref('Webhook')->toArray())->toBe([ - '$ref' => '#/components/callbacks/Webhook', - ]); -}); diff --git a/tests/Unit/Objects/ExampleTest.php b/tests/Unit/Objects/ExampleTest.php index 8ce7296..00df292 100644 --- a/tests/Unit/Objects/ExampleTest.php +++ b/tests/Unit/Objects/ExampleTest.php @@ -37,12 +37,6 @@ ]); }); -it('supports ref() shortcut', function (): void { - expect(Example::ref('SampleUser')->toArray())->toBe([ - '$ref' => '#/components/examples/SampleUser', - ]); -}); - it('clears a value when explicitly cleared', function (): void { $example = Example::create()->value('a'); diff --git a/tests/Unit/Objects/HeaderTest.php b/tests/Unit/Objects/HeaderTest.php index 78f4a3d..b6b97c4 100644 --- a/tests/Unit/Objects/HeaderTest.php +++ b/tests/Unit/Objects/HeaderTest.php @@ -82,12 +82,6 @@ ]); }); -it('supports ref() shortcut', function (): void { - expect(Header::ref('RateLimitRemaining')->toArray())->toBe([ - '$ref' => '#/components/headers/RateLimitRemaining', - ]); -}); - it('accepts a Style enum for style()', function (): void { $header = Header::create()->style(Style::Simple); diff --git a/tests/Unit/Objects/LinkTest.php b/tests/Unit/Objects/LinkTest.php index 294ade0..5010ca3 100644 --- a/tests/Unit/Objects/LinkTest.php +++ b/tests/Unit/Objects/LinkTest.php @@ -47,12 +47,6 @@ ]); }); -it('supports ref() shortcut', function (): void { - expect(Link::ref('Foo')->toArray())->toBe([ - '$ref' => '#/components/links/Foo', - ]); -}); - it('inserts requestBody after parameters when parameters are present', function (): void { $link = Link::create() ->operationId('users.create') diff --git a/tests/Unit/Objects/OperationTest.php b/tests/Unit/Objects/OperationTest.php index ba02f7a..aa2873a 100644 --- a/tests/Unit/Objects/OperationTest.php +++ b/tests/Unit/Objects/OperationTest.php @@ -153,6 +153,22 @@ expect($operation->toArray()['responses'])->toHaveKeys(['200', '404']); }); +it('responses() accepts a referenced response and keys it by status', function (): void { + $operation = Operation::get()->responses( + Response::ok(), + Response::notFound()->ref('NotFound'), + ); + + expect($operation->toArray()['responses'])->toBe([ + '200' => [ + 'description' => 'OK', + ], + '404' => [ + '$ref' => '#/components/responses/NotFound', + ], + ]); +}); + it('adds a response by explicit status key, accepting Response or Reference', function (): void { $operation = Operation::get() ->response('200', Response::ok()) @@ -179,7 +195,7 @@ it('adds callbacks one at a time with callback()', function (): void { $operation = Operation::post() ->callback('onData', Callback::create()->expression('{$url}', PathItem::create('/hook'))) - ->callback('onError', Callback::ref('OnError')); + ->callback('onError', Reference::callback('OnError')); $arr = $operation->toArray(); expect($arr['callbacks'])->toHaveKey('onData'); diff --git a/tests/Unit/Objects/ParameterTest.php b/tests/Unit/Objects/ParameterTest.php index 933b2b5..1fea150 100644 --- a/tests/Unit/Objects/ParameterTest.php +++ b/tests/Unit/Objects/ParameterTest.php @@ -109,12 +109,6 @@ ]); }); -it('supports ref() shortcut', function (): void { - expect(Parameter::ref('PageSize')->toArray())->toBe([ - '$ref' => '#/components/parameters/PageSize', - ]); -}); - it('required() defaults to true', function (): void { expect(Parameter::query('test', Schema::string())->required()->toArray())->toMatchArray([ 'required' => true, diff --git a/tests/Unit/Objects/PathItemTest.php b/tests/Unit/Objects/PathItemTest.php index 1359a52..e832a91 100644 --- a/tests/Unit/Objects/PathItemTest.php +++ b/tests/Unit/Objects/PathItemTest.php @@ -66,12 +66,6 @@ ]); }); -it('supports ref() shortcut', function (): void { - expect(PathItem::ref('UserById')->toArray())->toBe([ - '$ref' => '#/components/pathItems/UserById', - ]); -}); - it('emits vendor extensions', function (): void { $pathItem = PathItem::create('/users') ->x('internal', true); diff --git a/tests/Unit/Objects/RequestBodyTest.php b/tests/Unit/Objects/RequestBodyTest.php index c1f6456..e7de989 100644 --- a/tests/Unit/Objects/RequestBodyTest.php +++ b/tests/Unit/Objects/RequestBodyTest.php @@ -56,12 +56,6 @@ ]); }); -it('supports ref() shortcut', function (): void { - expect(RequestBody::ref('Create')->toArray())->toBe([ - '$ref' => '#/components/requestBodies/Create', - ]); -}); - it('required() defaults to true', function (): void { $requestBody = RequestBody::create() ->required() diff --git a/tests/Unit/Objects/ResponseTest.php b/tests/Unit/Objects/ResponseTest.php index cd716bc..d052008 100644 --- a/tests/Unit/Objects/ResponseTest.php +++ b/tests/Unit/Objects/ResponseTest.php @@ -80,9 +80,33 @@ ]); }); -it('supports ref() shortcut', function (): void { - expect(Response::ref('Unauthorized')->toArray())->toBe([ - '$ref' => '#/components/responses/Unauthorized', +it('refTo() serializes to the reference alone', function (): void { + expect(Response::notFound()->ref('NotFound')->toArray())->toBe([ + '$ref' => '#/components/responses/NotFound', + ]); +}); + +it('refTo() keeps the status code so responses() can key it', function (): void { + expect(Response::notFound()->ref('NotFound')->getStatusCode())->toBe('404'); +}); + +it('refTo() carries summary and description onto the reference', function (): void { + expect(Response::unauthorized()->ref('Unauthorized', 'Auth failed', 'Token missing or expired')->toArray()) + ->toBe([ + '$ref' => '#/components/responses/Unauthorized', + 'summary' => 'Auth failed', + 'description' => 'Token missing or expired', + ]); +}); + +it('refTo() replaces any fields set on the response itself', function (): void { + $response = Response::notFound() + ->description('Locally described') + ->json(Schema::object()) + ->ref('NotFound'); + + expect($response->toArray())->toBe([ + '$ref' => '#/components/responses/NotFound', ]); }); @@ -97,7 +121,7 @@ it('adds links one at a time with link()', function (): void { $response = Response::ok() ->link('self', Link::create()->operationId('users.show')) - ->link('next', Link::ref('NextUser')); + ->link('next', Reference::link('NextUser')); expect($response->toArray()['links'])->toHaveKeys(['self', 'next']); }); diff --git a/tests/Unit/Objects/SecuritySchemeTest.php b/tests/Unit/Objects/SecuritySchemeTest.php index 4b99a25..833c602 100644 --- a/tests/Unit/Objects/SecuritySchemeTest.php +++ b/tests/Unit/Objects/SecuritySchemeTest.php @@ -71,9 +71,3 @@ 'type' => 'mutualTLS', ]); }); - -it('supports ref() shortcut', function (): void { - expect(SecurityScheme::ref('OAuth2')->toArray())->toBe([ - '$ref' => '#/components/securitySchemes/OAuth2', - ]); -});