Skip to content

Commit b2a4f7c

Browse files
committed
Making a few more improvements
1 parent c09d3cf commit b2a4f7c

2 files changed

Lines changed: 96 additions & 12 deletions

File tree

Annotations/AnnotationGenerator.php

Lines changed: 87 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ protected function buildAnnotationForMethod(array $rules, string $pluginName, \R
138138
);
139139

140140
$params = $this->determineParameters($rules, $pluginName, $methodName, $reflectionMethod);
141-
$responses = $this->determineResponses($rules, $pluginName, $methodName);
141+
$responses = $this->determineResponses($rules, $pluginName, $methodName, $reflectionMethod);
142142

143143
$isPost = !empty($rules['plugins'][$pluginName]['methodsRequiringPost'])
144144
&& in_array($methodName, $rules['plugins'][$pluginName]['methodsRequiringPost']);
@@ -168,6 +168,34 @@ protected function getParamInfoFromDocBlock(string $docBlock): array
168168
return $params;
169169
}
170170

171+
protected function getResponseInfoFromDocBlock(string $docBlock): array
172+
{
173+
$lexer = new Lexer();
174+
$tokens = $lexer->tokenize($docBlock);
175+
$expressionParser = new ConstExprParser();
176+
$parser = new PhpDocParser(new TypeParser($expressionParser), $expressionParser);
177+
$node = $parser->parse(new TokenIterator($tokens));
178+
179+
$responseInfo = ['type' => null];
180+
$returnTags = $node->getReturnTagValues();
181+
if (empty($returnTags)) {
182+
return $responseInfo;
183+
}
184+
185+
$returnTag = $returnTags[0];
186+
$tagValue = strval($returnTag->type);
187+
$responseInfo['type'] = $this->getOpenApiTypeFromPhpType($tagValue);
188+
if ($responseInfo['type'] === 'string' && !empty($tagValue) && strtolower($tagValue) !== 'string') {
189+
$responseInfo['type'] = '';
190+
$responseInfo['description'] = 'Response of unknown type';
191+
}
192+
if (!empty($returnTag->description)) {
193+
$responseInfo['description'] = $returnTag->description;
194+
}
195+
196+
return $responseInfo;
197+
}
198+
171199
protected function buildVirtualPath(string $virtualPathTemplate, string $plugin, string $method): string
172200
{
173201
return str_replace(['{plugin}', '{method}'], [$plugin, $method], $virtualPathTemplate);
@@ -320,16 +348,10 @@ protected function getExampleIfAvailable(string $url): array
320348
curl_close($ch);
321349

322350
// If the example didn't load or is too big, simply include the URL instead of the string value
323-
if ($body === false || $status !== 200 || strlen($body) > 1000 || strpos($body, 'Error: ') === 0) {
351+
if ($body === false || $status !== 200 || strlen($body) > 2000 || strpos($body, 'Error: ') === 0) {
324352
return ['externalValue' => $url];
325353
}
326354

327-
// Clean up XML formatting a bit
328-
$body = trim($body);
329-
if (stripos($url, 'format=xml') !== false) {
330-
$body = str_replace(['<?xml version="1.0" encoding="utf-8" ?>', "\n", "\t", '"'], ['', '', '', '\"'], $body);
331-
}
332-
333355
// The annotation expects an objects and not arrays
334356
if (stripos($url, 'format=json') !== false && stripos($body, '[') === 0) {
335357
$body = str_replace(['[', ']'], ['{', '}'], $body);
@@ -338,21 +360,62 @@ protected function getExampleIfAvailable(string $url): array
338360
return ['value' => $body];
339361
}
340362

341-
protected function determineResponses(array $rules, string $plugin, string $method): array
363+
protected function determineResponses(array $rules, string $plugin, string $method, \ReflectionMethod $reflectionMethod): array
342364
{
343365
$responses = [];
344366

345-
// TODO - Try to determine the success response using the return type and/or doc-block return type
367+
// Try to determine the success response using the return type and/or doc-block return type
368+
$returnType = $reflectionMethod->getReturnType();
369+
$responseInfo = $this->getResponseInfoFromDocBlock($reflectionMethod->getDocComment());
370+
$commentType = $responseInfo['type'];
371+
if (!empty($returnType) && $returnType->isBuiltin()) {
372+
$responseInfo['type'] = $this->getOpenApiTypeFromPhpType($returnType->getName());
373+
}
346374

347375
$successRef = null;
348376
$successArray = ['code' => 200];
349377
if (isset($rules['plugins'][$plugin]['successResponseByMethod'][$method])) {
350378
$successRef = $rules['plugins'][$plugin]['successResponseByMethod'][$method];
351379
}
380+
// TODO - See if there's a way to auto-handle custom objects, especially common stuff like DataTable\DataTableInterface
352381
if ($successRef) {
353382
$successArray['ref'] = $successRef;
354383
}
355384

385+
// If the return type is void, use the generic response type
386+
if (empty($successArray['ref']) && !empty($returnType) && $returnType->getName() === 'void') {
387+
$successArray['ref'] = '#/components/responses/GenericSuccessNoBody';
388+
}
389+
390+
// If it's a generic type and there's no custom description, use one of the global generic responses
391+
if (empty($successArray['ref']) && !empty($responseInfo['type']) && empty($responseInfo['description'])) {
392+
$ref = '';
393+
switch ($responseInfo['type']) {
394+
case 'array':
395+
$ref = '#/components/responses/GenericArray';
396+
break;
397+
case 'integer':
398+
$ref = '#/components/responses/GenericInteger';
399+
break;
400+
case 'boolean':
401+
$ref = '#/components/responses/GenericBoolean';
402+
break;
403+
case 'string':
404+
$ref = '#/components/responses/GenericString';
405+
break;
406+
}
407+
408+
if (!empty($ref)) {
409+
$successArray['ref'] = $ref;
410+
}
411+
}
412+
413+
if (!empty($responseInfo['description'])) {
414+
$successArray['desc'] = $responseInfo['description'];
415+
}
416+
417+
$responseSchema = !empty($responseInfo['type']) ? $this->buildSchemaObjectArray($responseInfo['type']) : [];
418+
356419
$mediaTypes = [];
357420
// This simply reuses the example URLs used by the current documentation, but some endpoints don't work because authentication is required
358421
// TODO - Come up with a way to demo examples for endpoints which require authentication. E.g. hit a live endpoint server-side and replace any potentially sensitive data...
@@ -371,13 +434,21 @@ protected function determineResponses(array $rules, string $plugin, string $meth
371434
$value = substr($value, 1, -1);
372435
}
373436
$exampleProperties[] = $valueKey . '=' . $value;
374-
$mediaTypes[] = [
437+
$mediaType = [
375438
'mediaType="' . $contentType . '"',
376439
'@OA\Examples' => $exampleProperties,
377440
];
441+
// If a type was found, add it as a schema to the media type
442+
if (!empty($responseSchema)) {
443+
$mediaType = array_merge($mediaType, $responseSchema);
444+
}
445+
$mediaTypes[] = $mediaType;
378446
}
379447
if (!empty($mediaTypes)) {
380448
$successArray['mediaTypes'] = $mediaTypes;
449+
} else {
450+
// Make sure the schema is included in there are no examples
451+
$successArray['schema'] = $responseSchema;
381452
}
382453

383454
$responses[] = $successArray;
@@ -507,7 +578,8 @@ protected function compileOperationLines(string $path, string $opId, string $plu
507578
$operationValuesMap[] = ['@OA\Parameter' => $paramMap];
508579
}
509580
foreach ($responses as $response) {
510-
if (isset($response['ref'])) {
581+
// Don't use the reference if there are media type examples
582+
if (isset($response['ref']) && empty($response['mediaTypes'])) {
511583
$code = $response['code'];
512584
$codeFormatted = is_numeric($code) ? (string)$code : '"' . $code . '"';
513585
$operationValuesMap[] = '@OA\Response(response=' . $codeFormatted . ', ref="' . $response['ref'] . '")';
@@ -516,6 +588,9 @@ protected function compileOperationLines(string $path, string $opId, string $plu
516588
'response=200',
517589
'description="' . ($response['desc'] ?? 'OK') . '"',
518590
];
591+
if (!empty($response['schema'])) {
592+
$responsePropertyArray = array_merge($responsePropertyArray, $response['schema']);
593+
}
519594
if (isset($response['mediaTypes']) && is_array($response['mediaTypes'])) {
520595
foreach ($response['mediaTypes'] as $mediaType) {
521596
$responsePropertyArray[] = ['@OA\MediaType' => $mediaType];

Annotations/GlobalApiComponents.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,15 @@
167167
* )
168168
*
169169
* @OA\Response(
170+
* response="GenericString",
171+
* description="Generic 200 response with only a string body",
172+
* @OA\JsonContent(type="string"),
173+
* @OA\XmlContent(type="string"),
174+
* @OA\MediaType(mediaType="text/plain", @OA\Schema(type="string"), example="Result: success"),
175+
* @OA\MediaType(mediaType="text/html", @OA\Schema(type="string"), example="success")
176+
* )
177+
*
178+
* @OA\Response(
170179
* response="GenericBoolean",
171180
* description="Generic 200 response with only true or false as the body",
172181
* @OA\JsonContent(type="boolean"),

0 commit comments

Comments
 (0)