Skip to content

Commit 1262264

Browse files
authored
Merge pull request #83 from cnizzardini/feature/issue-57-schema-spec
Feature/issue 57 schema spec
2 parents bf9315f + 68a7f35 commit 1262264

File tree

5 files changed

+124
-24
lines changed

5 files changed

+124
-24
lines changed

README.md

+42-2
Original file line numberDiff line numberDiff line change
@@ -329,12 +329,12 @@ class UsersController extends AppController {
329329
```
330330

331331
#### `@SwagEntity`
332-
Class level annotation for exposing entities to Swagger UI. By default all entities with routes will display as Swagger
332+
Class level annotation for exposing entities to Swagger UI. By default, all entities with routes will display as Swagger
333333
schema. You can hide a schema or display a schema that does not have an associated route.
334334

335335
```php
336336
/**
337-
* @Swag\SwagEntity(isVisible=false)
337+
* @Swag\SwagEntity(isVisible=false, title="optional title", description="optional description")
338338
*/
339339
class Employee extends Entity {
340340
```
@@ -445,6 +445,46 @@ bin/cake bake controller {Name} --theme SwaggerBake
445445
- SwaggerBake has been developed primarily for application/json and application/x-www-form-urlencoded, but does have
446446
some support for application/xml and *should* work with application/vnd.api+json.
447447

448+
SwaggerBake does not document schema associations. If your application includes associations on things like
449+
GET requests, you can easily add them into your swagger documentation through the OpenAPI `allOf` property. Since
450+
SwaggerBake works in conjunction with OpenAPI YAML you can easily add a new schema with this association. Below is an
451+
example of extending an existing City schema to include a Country association.
452+
453+
```yaml
454+
# in your swagger.yml
455+
components:
456+
schemas:
457+
CityExtended:
458+
description: 'City with extended information including Country'
459+
type: object
460+
allOf:
461+
- $ref: '#/components/schemas/City'
462+
- type: object
463+
properties:
464+
country:
465+
$ref: '#/components/schemas/Country'
466+
467+
```
468+
469+
Then in your controller action you'd specify the Schema:
470+
471+
```php
472+
/**
473+
* View method
474+
* @Swag\SwagResponseSchema(refEntity="#/components/schemas/CityExtended")
475+
*/
476+
public function view($id)
477+
{
478+
$this->request->allowMethod('get');
479+
$city = $this->Cities->get($id, ['contain' => ['Countries']]);
480+
$this->set(compact('cities'));
481+
$this->viewBuilder()->setOption('serialize', 'cities');
482+
}
483+
```
484+
485+
The demo application includes this and many other examples of usage. Read more about `oneOf`, `anyOf`, `allOf`, and
486+
`not` in the [OpenAPI 3 documentation](https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/).
487+
448488
## Supported Versions
449489

450490
This is built for CakePHP 4.x only. A cake-3.8 option is available, but not supported.

src/Lib/Annotation/SwagEntity.php

+13-4
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,32 @@
22

33
namespace SwaggerBake\Lib\Annotation;
44

5-
use InvalidArgumentException;
6-
75
/**
86
* @Annotation
97
* @Target({"CLASS"})
108
* @Attributes({
119
* @Attribute("isVisible", type="bool"),
10+
* @Attribute("title", type="string"),
11+
* @Attribute("description", type="string"),
1212
* })
1313
*/
1414
class SwagEntity
1515
{
1616
/** @var bool **/
1717
public $isVisible;
1818

19+
/** @var string|null */
20+
public $title;
21+
22+
/** @var string|null */
23+
public $description;
24+
1925
public function __construct(array $values)
2026
{
21-
$values = array_merge(['isVisible' => true], $values);
22-
$this->isVisible = (bool) $values['isVisible'];
27+
foreach ($values as $attribute => $value) {
28+
if (property_exists($this, $attribute)) {
29+
$this->{$attribute} = $value;
30+
}
31+
}
2332
}
2433
}

src/Lib/OpenApi/Schema.php

+51-8
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@ class Schema implements JsonSerializable
1414
/** @var string */
1515
private $name = '';
1616

17-
/** @var string */
18-
private $description = '';
17+
/** @var string|null */
18+
private $title;
19+
20+
/** @var string|null */
21+
private $description;
1922

2023
/** @var string */
2124
private $type = '';
@@ -38,6 +41,9 @@ class Schema implements JsonSerializable
3841
/** @var array */
3942
private $allOf = [];
4043

44+
/** @var array */
45+
private $not = [];
46+
4147
/** @var array */
4248
private $enum = [];
4349

@@ -51,6 +57,7 @@ public function toArray() : array
5157
{
5258
$vars = get_object_vars($this);
5359
unset($vars['name']);
60+
5461
if (empty($vars['required'])) {
5562
unset($vars['required']);
5663
} else {
@@ -59,8 +66,8 @@ public function toArray() : array
5966
}
6067

6168
// remove empty properties to avoid swagger.json clutter
62-
foreach (['properties', 'items', 'oneOf', 'anyOf', 'allOf', 'enum', 'format', 'type'] as $v) {
63-
if (empty($vars[$v])) {
69+
foreach (['title','description','properties','items','oneOf','anyOf','allOf','not','enum','format','type'] as $v) {
70+
if (empty($vars[$v]) || is_null($vars[$v])) {
6471
unset($vars[$v]);
6572
}
6673
}
@@ -94,6 +101,24 @@ public function setName(string $name): Schema
94101
return $this;
95102
}
96103

104+
/**
105+
* @return string|null
106+
*/
107+
public function getTitle(): ?string
108+
{
109+
return $this->title;
110+
}
111+
112+
/**
113+
* @param string|null $title
114+
* @return Schema
115+
*/
116+
public function setTitle(?string $title): Schema
117+
{
118+
$this->title = $title;
119+
return $this;
120+
}
121+
97122
/**
98123
* @return string
99124
*/
@@ -180,18 +205,18 @@ public function pushProperty(SchemaProperty $property): Schema
180205
}
181206

182207
/**
183-
* @return string
208+
* @return string|null
184209
*/
185-
public function getDescription(): string
210+
public function getDescription(): ?string
186211
{
187212
return $this->description;
188213
}
189214

190215
/**
191-
* @param string $description
216+
* @param string|null $description
192217
* @return Schema
193218
*/
194-
public function setDescription(string $description): Schema
219+
public function setDescription(?string $description): Schema
195220
{
196221
$this->description = $description;
197222
return $this;
@@ -269,6 +294,24 @@ public function setAllOf(array $allOf): Schema
269294
return $this;
270295
}
271296

297+
/**
298+
* @return array
299+
*/
300+
public function getNot(): array
301+
{
302+
return $this->not;
303+
}
304+
305+
/**
306+
* @param array $not
307+
* @return Schema
308+
*/
309+
public function setNot(array $not): Schema
310+
{
311+
$this->not = $not;
312+
return $this;
313+
}
314+
272315
/**
273316
* @return array
274317
*/

src/Lib/Schema/SchemaFactory.php

+17-8
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ public function __construct(Configuration $config)
4040
*/
4141
public function create(EntityDecorator $entity) : ?Schema
4242
{
43-
if (!$this->isSwaggable($entity)) {
43+
$swagEntity = $this->getSwagEntityAnnotation($entity);
44+
45+
if ($swagEntity !== null && $swagEntity->isVisible === false) {
4446
return null;
4547
}
4648

@@ -50,14 +52,19 @@ public function create(EntityDecorator $entity) : ?Schema
5052

5153
$properties = $this->getProperties($entity);
5254

53-
$schema = new Schema();
54-
$schema
55+
$schema = (new Schema())
5556
->setName($entity->getName())
56-
->setDescription($docBlock ? $docBlock->getSummary() : '')
57+
->setTitle($swagEntity !== null ? $swagEntity->description : null)
5758
->setType('object')
5859
->setProperties($properties)
5960
;
6061

62+
if ($swagEntity !== null && isset($swagEntity->description)) {
63+
$schema->setDescription($swagEntity->description);
64+
} else {
65+
$schema->setDescription($docBlock ? $docBlock->getSummary() : null);
66+
}
67+
6168
$requiredProperties = array_filter($properties, function ($property) {
6269
return $property->isRequired();
6370
});
@@ -166,20 +173,22 @@ private function getSwagPropertyAnnotations(EntityDecorator $entity) : array
166173
}
167174

168175
/**
176+
* Returns instance of SwagEntity annotation, otherwise null
177+
*
169178
* @param EntityDecorator $entity
170-
* @return bool
179+
* @return SwagEntity|null
171180
*/
172-
private function isSwaggable(EntityDecorator $entity) : bool
181+
private function getSwagEntityAnnotation(EntityDecorator $entity) : ?SwagEntity
173182
{
174183
$annotations = AnnotationUtility::getClassAnnotationsFromInstance($entity->getEntity());
175184

176185
foreach ($annotations as $annotation) {
177186
if ($annotation instanceof SwagEntity) {
178-
return $annotation->isVisible;
187+
return $annotation;
179188
}
180189
}
181190

182-
return true;
191+
return null;
183192
}
184193

185194
/**

tests/TestCase/Lib/Operation/OperationResponseTest.php

+1-2
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,9 @@ public function testAddOperationWithNoResponseDefined()
140140
$this->assertNotEmpty($response);
141141

142142
$content = $response->getContentByMimeType('application/json');
143+
143144
$this->assertNotEmpty($content);
144145
$this->assertNotEmpty($content->getSchema());
145-
$this->assertCount(1, $content->getSchema()->toArray());
146146
}
147147

148148
public function testDeleteActionResponseWithHttp204()
@@ -191,6 +191,5 @@ public function testNoResponseDefined()
191191
$content = $response->getContentByMimeType('application/json');
192192
$this->assertNotEmpty($content);
193193
$this->assertNotEmpty($content->getSchema());
194-
$this->assertCount(1, $content->getSchema()->toArray());
195194
}
196195
}

0 commit comments

Comments
 (0)