Skip to content

Commit b67cbd1

Browse files
Merge pull request #488 from ReCodEx/client-generator-adaptation
Client generator adaptation
2 parents 1e145d5 + a0e58ac commit b67cbd1

File tree

9 files changed

+186
-12
lines changed

9 files changed

+186
-12
lines changed

app/V1Module/presenters/LoginPresenter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ public function checkRefresh()
199199

200200
/**
201201
* Refresh the access token of current user
202-
* @GET
202+
* @POST
203203
* @LoggedIn
204204
* @throws ForbiddenRequestException
205205
*/

app/helpers/MetaFormats/RequestParamData.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,15 +112,27 @@ public function toAnnotationParameterData()
112112

113113
// determine swagger type
114114
$nestedArraySwaggerType = null;
115+
$arrayDepth = null;
115116
$swaggerType = $this->validators[0]::SWAGGER_TYPE;
116-
// extract array element type
117+
// extract array depth and element type
117118
if ($this->validators[0] instanceof VArray) {
119+
$arrayDepth = $this->validators[0]->getArrayDepth();
118120
$nestedArraySwaggerType = $this->validators[0]->getElementSwaggerType();
119121
}
120122

121123
// get example value from the first validator
122124
$exampleValue = $this->validators[0]->getExampleValue();
123125

126+
// get constraints from validators
127+
$constraints = null;
128+
foreach ($this->validators as $validator) {
129+
$constraints = $validator->getConstraints();
130+
// it is assumed that at most one validator defines constraints
131+
if ($constraints !== null) {
132+
break;
133+
}
134+
}
135+
124136
// add nested parameter data if this is an object
125137
$format = $this->getFormatName();
126138
$nestedObjectParameterData = null;
@@ -140,7 +152,9 @@ public function toAnnotationParameterData()
140152
$this->nullable,
141153
$exampleValue,
142154
$nestedArraySwaggerType,
155+
$arrayDepth,
143156
$nestedObjectParameterData,
157+
$constraints,
144158
);
145159
}
146160
}

app/helpers/MetaFormats/Validators/BaseValidator.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace App\Helpers\MetaFormats\Validators;
44

5+
use App\Helpers\Swagger\ParameterConstraints;
6+
57
/**
68
* Base class for all validators.
79
*/
@@ -43,6 +45,16 @@ public function getExampleValue(): string | null
4345
return null;
4446
}
4547

48+
/**
49+
* @return ParameterConstraints Returns all parameter constrains that will be written into the generated
50+
* swagger document. Returns null if there are no constraints.
51+
*/
52+
public function getConstraints(): ?ParameterConstraints
53+
{
54+
// there are no default constraints
55+
return null;
56+
}
57+
4658
/**
4759
* Validates a value with the configured validation strictness.
4860
* @param mixed $value The value to be validated.

app/helpers/MetaFormats/Validators/VArray.php

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace App\Helpers\MetaFormats\Validators;
44

5+
use App\Helpers\Swagger\ParameterConstraints;
6+
57
/**
68
* Validates arrays and their nested elements.
79
*/
@@ -33,17 +35,45 @@ public function getExampleValue(): string | null
3335
}
3436

3537
/**
36-
* @return string|null Returns the element swagger type. Can be null if the element validator is not set.
38+
* @return string|null Returns the bottommost element swagger type. Can be null if the element validator is not set.
3739
*/
3840
public function getElementSwaggerType(): mixed
3941
{
42+
// return null if the element type is unspecified
4043
if ($this->nestedValidator === null) {
4144
return null;
4245
}
4346

47+
// traverse the VArray chain to get the final element type
48+
if ($this->nestedValidator instanceof VArray) {
49+
return $this->nestedValidator->getElementSwaggerType();
50+
}
51+
4452
return $this->nestedValidator::SWAGGER_TYPE;
4553
}
4654

55+
/**
56+
* @return int Returns the defined depth of the array.
57+
* 1 for arrays containing the final elements, 2 for arrays of arrays etc.
58+
*/
59+
public function getArrayDepth(): int
60+
{
61+
if ($this->nestedValidator instanceof VArray) {
62+
return $this->nestedValidator->getArrayDepth() + 1;
63+
}
64+
65+
return 1;
66+
}
67+
68+
/**
69+
* @return ParameterConstraints Returns all parameter constrains of the bottommost element type that will be
70+
* written into the generated swagger document. Returns null if there are no constraints.
71+
*/
72+
public function getConstraints(): ?ParameterConstraints
73+
{
74+
return $this->nestedValidator?->getConstraints();
75+
}
76+
4777
/**
4878
* Sets the strict flag for this validator and the element validator if present.
4979
* Expected to be changed by Attributes containing validators to change their behavior based on the Attribute type.

app/helpers/MetaFormats/Validators/VString.php

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

33
namespace App\Helpers\MetaFormats\Validators;
44

5+
use App\Helpers\Swagger\ParameterConstraints;
6+
57
/**
68
* Validates strings.
79
*/
@@ -32,6 +34,14 @@ public function getExampleValue(): string
3234
return "text";
3335
}
3436

37+
public function getConstraints(): ParameterConstraints
38+
{
39+
// do not pass redundant constraints
40+
$minLength = ($this->minLength > 0 ? $this->minLength : null);
41+
$maxLength = ($this->maxLength !== -1 ? $this->maxLength : null);
42+
return new ParameterConstraints($this->regex, $minLength, $maxLength);
43+
}
44+
3545
public function validate(mixed $value): bool
3646
{
3747
// do not allow other types

app/helpers/Swagger/AnnotationData.php

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,17 @@ class AnnotationData
1313

1414
public string $className;
1515
public string $methodName;
16+
/**
17+
* @var AnnotationParameterData[]
18+
*/
1619
public array $pathParams;
20+
/**
21+
* @var AnnotationParameterData[]
22+
*/
1723
public array $queryParams;
24+
/**
25+
* @var AnnotationParameterData[]
26+
*/
1827
public array $bodyParams;
1928
public ?string $endpointDescription;
2029

@@ -68,9 +77,22 @@ private function getBodyAnnotation(): string | null
6877
// only json is supported due to the media type
6978
$head = '@OA\RequestBody(@OA\MediaType(mediaType="application/json",@OA\Schema';
7079
$body = new ParenthesesBuilder();
80+
// list of all required properties
81+
$required = [];
7182

7283
foreach ($this->bodyParams as $bodyParam) {
7384
$body->addValue($bodyParam->toPropertyAnnotation());
85+
if ($bodyParam->required) {
86+
// add quotes around the names (required by the swagger generator)
87+
$required[] = '"' . $bodyParam->name . '"';
88+
}
89+
}
90+
91+
// add a list of required properties
92+
if (count($required) > 0) {
93+
// stringify the list (it has to be in '{"name1","name1",...}' format)
94+
$requiredString = "{" . implode(",", $required) . "}";
95+
$body->addValue("required=" . $requiredString);
7496
}
7597

7698
return $head . $body->toString() . "))";
@@ -85,8 +107,8 @@ private function constructOperationId()
85107
{
86108
// remove the namespace prefix of the class and make the first letter lowercase
87109
$className = lcfirst(Utils::shortenClass($this->className));
88-
// remove the 'action' prefix
89-
$endpoint = substr($this->methodName, strlen("action"));
110+
// make the 'a' in the action prefix uppercase to match the camel-case notation
111+
$endpoint = ucfirst($this->methodName);
90112
return $className . $endpoint;
91113
}
92114

app/helpers/Swagger/AnnotationHelper.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,11 @@ private static function extractStandardAnnotationParams(array $annotations, stri
163163

164164
// the array element type cannot be determined from standard @param annotations
165165
$nestedArraySwaggerType = null;
166+
// the actual depth of the array cannot be determined as well
167+
$arrayDepth = null;
168+
if ($swaggerType == "array") {
169+
$arrayDepth = 1;
170+
}
166171

167172
$descriptor = new AnnotationParameterData(
168173
$swaggerType,
@@ -172,6 +177,7 @@ private static function extractStandardAnnotationParams(array $annotations, stri
172177
$isPathParam,
173178
$nullable,
174179
nestedArraySwaggerType: $nestedArraySwaggerType,
180+
arrayDepth: $arrayDepth,
175181
);
176182
$params[] = $descriptor;
177183
}

app/helpers/Swagger/AnnotationParameterData.php

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ class AnnotationParameterData
1818
public bool $nullable;
1919
public ?string $example;
2020
public ?string $nestedArraySwaggerType;
21+
public ?int $arrayDepth;
2122
public ?array $nestedObjectParameterData;
23+
public ?ParameterConstraints $constraints;
2224

2325
public function __construct(
2426
string $swaggerType,
@@ -29,7 +31,9 @@ public function __construct(
2931
bool $nullable,
3032
?string $example = null,
3133
?string $nestedArraySwaggerType = null,
34+
?int $arrayDepth = null,
3235
?array $nestedObjectParameterData = null,
36+
?ParameterConstraints $constraints = null,
3337
) {
3438
$this->swaggerType = $swaggerType;
3539
$this->name = $name;
@@ -39,7 +43,9 @@ public function __construct(
3943
$this->nullable = $nullable;
4044
$this->example = $example;
4145
$this->nestedArraySwaggerType = $nestedArraySwaggerType;
46+
$this->arrayDepth = $arrayDepth;
4247
$this->nestedObjectParameterData = $nestedObjectParameterData;
48+
$this->constraints = $constraints;
4349
}
4450

4551
private function addArrayItemsIfArray(ParenthesesBuilder $container)
@@ -49,18 +55,42 @@ private function addArrayItemsIfArray(ParenthesesBuilder $container)
4955
}
5056

5157
$itemsHead = "@OA\\Items";
52-
$items = new ParenthesesBuilder();
58+
$layers = [];
59+
for ($i = 0; $i < $this->arrayDepth; $i++) {
60+
$items = new ParenthesesBuilder();
61+
62+
// add array time for all nested arrays
63+
if ($i < $this->arrayDepth - 1) {
64+
$items->addKeyValue("type", "array");
65+
// handle bottommost elements
66+
} else {
67+
// add element type if present
68+
if ($this->nestedArraySwaggerType !== null) {
69+
$items->addKeyValue("type", $this->nestedArraySwaggerType);
70+
}
71+
72+
// add example value
73+
if ($this->example != null) {
74+
$items->addKeyValue("example", $this->example);
75+
}
76+
77+
// add constraints
78+
$this->constraints?->addConstraints($items);
79+
}
5380

54-
if ($this->nestedArraySwaggerType !== null) {
55-
$items->addKeyValue("type", $this->nestedArraySwaggerType);
81+
$layers[] = $items;
5682
}
5783

58-
// add example value
59-
if ($this->example != null) {
60-
$items->addKeyValue("example", $this->example);
84+
// serialize the layers from the bottom up
85+
$layers = array_reverse($layers);
86+
$serialized_layer = $itemsHead . $layers[0]->toString();
87+
for ($i = 1; $i < $this->arrayDepth; $i++) {
88+
$layer = $layers[$i];
89+
$layer->addValue($serialized_layer);
90+
$serialized_layer = $itemsHead . $layer->toString();
6191
}
6292

63-
$container->addValue($itemsHead . $items->toString());
93+
$container->addValue($serialized_layer);
6494
}
6595

6696
private function addObjectParamsIfObject(ParenthesesBuilder $container)
@@ -85,6 +115,7 @@ private function generateSchemaAnnotation(): string
85115
$body = new ParenthesesBuilder();
86116

87117
$body->addKeyValue("type", $this->swaggerType);
118+
$body->addKeyValue("nullable", $this->nullable);
88119
$this->addArrayItemsIfArray($body);
89120

90121
return $head . $body->toString();
@@ -128,6 +159,11 @@ public function toPropertyAnnotation(): string
128159
$body->addKeyValue("description", $this->description);
129160
}
130161

162+
// handle param constraints (array constrains have to be added to the element, not the array)
163+
if ($this->swaggerType !== "array") {
164+
$this->constraints?->addConstraints($body);
165+
}
166+
131167
// handle arrays
132168
$this->addArrayItemsIfArray($body);
133169

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
namespace App\Helpers\Swagger;
4+
5+
class ParameterConstraints
6+
{
7+
private array $constraints;
8+
9+
/**
10+
* Constructs a container for swagger constraints.
11+
* Constructor parameter names match swagger keywords, see
12+
* https://swagger.io/docs/specification/v3_0/data-models/keywords/.
13+
* @param ?string $pattern String regex pattern.
14+
* @param ?int $minLength String min length.
15+
* @param ?int $maxLength String max length.
16+
*/
17+
public function __construct(?string $pattern = null, ?int $minLength = null, ?int $maxLength = null)
18+
{
19+
// swagger patterns must not contain the bounding '/.../' slashes
20+
if ($pattern != null && strlen($pattern) >= 2) {
21+
$pattern = substr($pattern, 1, -1);
22+
}
23+
24+
$this->constraints["pattern"] = $pattern;
25+
$this->constraints["minLength"] = $minLength;
26+
$this->constraints["maxLength"] = $maxLength;
27+
}
28+
29+
/**
30+
* Adds constraints to a ParenthesesBuilder for swagger doc construction.
31+
* @param \App\Helpers\Swagger\ParenthesesBuilder $container The container for keywords and values.
32+
*/
33+
public function addConstraints(ParenthesesBuilder $container)
34+
{
35+
foreach ($this->constraints as $keyword => $value) {
36+
// skip null values
37+
if ($value === null) {
38+
continue;
39+
}
40+
41+
$container->addKeyValue($keyword, $value);
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)