@@ -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 ];
0 commit comments