From 73c0b23680c310cdde8ab8010d9599cf07db1210 Mon Sep 17 00:00:00 2001 From: Matej Baco Date: Thu, 30 Sep 2021 11:59:01 +0200 Subject: [PATCH] Swagger (2,3) support for multiple types of response --- app/controllers/web/home.php | 9 +- .../Specification/Format/OpenAPI3.php | 86 +++++++++++++------ .../Specification/Format/Swagger2.php | 59 ++++++++++--- 3 files changed, 114 insertions(+), 40 deletions(-) diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php index 1d9e86d87..9f4d2412a 100644 --- a/app/controllers/web/home.php +++ b/app/controllers/web/home.php @@ -387,11 +387,10 @@ App::get('/specs/:format') } $routes[] = $route; - $model = $response->getModel($route->getLabel('sdk.response.model', 'none')); - - if($model) { - $models[$model->getType()] = $model; - } + $modelLabel = $route->getLabel('sdk.response.model', 'none'); + $model = \is_array($modelLabel) ? \array_map(function($m) use($response) { + return $response->getModel($m); + }, $modelLabel) : $response->getModel($modelLabel); } } diff --git a/src/Appwrite/Specification/Format/OpenAPI3.php b/src/Appwrite/Specification/Format/OpenAPI3.php index 1d462669c..b0a61114c 100644 --- a/src/Appwrite/Specification/Format/OpenAPI3.php +++ b/src/Appwrite/Specification/Format/OpenAPI3.php @@ -6,6 +6,7 @@ use Appwrite\Specification\Format; use Appwrite\Template\Template; use stdClass; use Utopia\Validator; +use function gettype; class OpenAPI3 extends Format { @@ -25,14 +26,14 @@ class OpenAPI3 extends Format * Get Used Models * * Recursively get all used models - * + * * @param object $model * @param array $models * * @return void */ protected function getUsedModels($model, array &$usedModels) - { + { if (is_string($model) && !in_array($model, ['string', 'integer', 'boolean', 'json', 'float', 'double'])) { $usedModels[] = $model; return; @@ -99,7 +100,7 @@ class OpenAPI3 extends Format if (isset($output['components']['securitySchemes']['Project'])) { $output['components']['securitySchemes']['Project']['x-appwrite'] = ['demo' => '5df5acd0d48c2']; } - + if (isset($output['components']['securitySchemes']['Key'])) { $output['components']['securitySchemes']['Key']['x-appwrite'] = ['demo' => '919c2d18fb5d4...a2ae413da83346ad2']; } @@ -107,7 +108,7 @@ class OpenAPI3 extends Format if (isset($output['securityDefinitions']['JWT'])) { $output['securityDefinitions']['JWT']['x-appwrite'] = ['demo' => 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ...']; } - + if (isset($output['components']['securitySchemes']['Locale'])) { $output['components']['securitySchemes']['Locale']['x-appwrite'] = ['demo' => 'en']; } @@ -131,7 +132,7 @@ class OpenAPI3 extends Format $id = $route->getLabel('sdk.method', \uniqid()); $desc = (!empty($route->getLabel('sdk.description', ''))) ? \realpath(__DIR__.'/../../../../'.$route->getLabel('sdk.description', '')) : null; $produces = $route->getLabel('sdk.response.type', null); - $model = $route->getLabel('sdk.response.model', 'none'); + $model = $route->getLabel('sdk.response.model', 'none'); $routeSecurity = $route->getLabel('sdk.auth', []); $sdkPlatofrms = []; @@ -155,7 +156,7 @@ class OpenAPI3 extends Format if(empty($routeSecurity)) { $sdkPlatofrms[] = APP_PLATFORM_CLIENT; } - + $temp = [ 'summary' => $route->getDesc(), 'operationId' => $route->getLabel('sdk.namespace', 'default').ucfirst($id), @@ -181,13 +182,24 @@ class OpenAPI3 extends Format ]; foreach ($this->models as $key => $value) { - if($value->getType() === $model) { - $model = $value; - break; + if(\is_array($model)) { + $model = \array_map(function($m) use($value) { + if($m === $value->getType()) { + return $value; + } + + return $m; + }, $model); + } else { + if($value->getType() === $model) { + $model = $value; + break; + } } + } - if($model->isNone()) { + if(!(\is_array($model)) && $model->isNone()) { $temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [ 'description' => (in_array($produces, [ 'image/*', @@ -204,17 +216,43 @@ class OpenAPI3 extends Format // ], ]; } else { - $usedModels[] = $model->getType(); - $temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [ - 'description' => $model->getName(), - 'content' => [ - $produces => [ - 'schema' => [ - '$ref' => '#/components/schemas/'.$model->getType(), + if(\is_array($model)) { + $modelDescription = \join(', or ', \array_map(function ($m) { + return $m->getName(); + }, $model)); + + // model has multiple possible responses, we will use oneOf + foreach ($model as $m) { + $usedModels[] = $m->getType(); + } + + $temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [ + 'description' => $modelDescription, + 'content' => [ + $produces => [ + 'schema' => [ + 'oneOf' => \array_map(function($m) { + return ['$ref' => '#/components/schemas/'.$m->getType()]; + }, $model) + ], ], ], - ], - ]; + ]; + } else { + // Response definition using one type + $usedModels[] = $model->getType(); + $temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [ + 'description' => $model->getName(), + 'content' => [ + $produces => [ + 'schema' => [ + '$ref' => '#/components/schemas/'.$model->getType(), + ], + ], + ], + ]; + } + } if($route->getLabel('sdk.response.code', 500) === 204) { @@ -224,7 +262,7 @@ class OpenAPI3 extends Format if ((!empty($scope))) { // && 'public' != $scope $securities = ['Project' => []]; - + foreach($route->getLabel('sdk.auth', []) as $security) { if(array_key_exists($security, $this->keys)) { $securities[$security] = []; @@ -402,7 +440,7 @@ class OpenAPI3 extends Format if($model->isAny()) { $output['components']['schemas'][$model->getType()]['additionalProperties'] = true; } - + if(!empty($required)) { $output['components']['schemas'][$model->getType()]['required'] = $required; } @@ -417,7 +455,7 @@ class OpenAPI3 extends Format case 'json': $type = 'string'; break; - + case 'integer': $type = 'integer'; $format = 'int32'; @@ -432,11 +470,11 @@ class OpenAPI3 extends Format $type = 'number'; $format = 'double'; break; - + case 'boolean': $type = 'boolean'; break; - + default: $type = 'object'; $rule['type'] = ($rule['type']) ? $rule['type'] : 'none'; diff --git a/src/Appwrite/Specification/Format/Swagger2.php b/src/Appwrite/Specification/Format/Swagger2.php index 66324567b..a8dcffb1e 100644 --- a/src/Appwrite/Specification/Format/Swagger2.php +++ b/src/Appwrite/Specification/Format/Swagger2.php @@ -183,13 +183,22 @@ class Swagger2 extends Format } foreach ($this->models as $key => $value) { - if($value->getType() === $model) { - $model = $value; - break; + if(\is_array($model)) { + $model = \array_map(function($m) use($value) { + if($m === $value->getType()) { + return $value; + } + return $m; + }, $model); + } else { + if($value->getType() === $model) { + $model = $value; + break; + } } } - if($model->isNone()) { + if(!(\is_array($model)) && $model->isNone()) { $temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [ 'description' => (in_array($produces, [ 'image/*', @@ -206,13 +215,41 @@ class Swagger2 extends Format ], ]; } else { - $usedModels[] = $model->getType(); - $temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [ - 'description' => $model->getName(), - 'schema' => [ - '$ref' => '#/definitions/'.$model->getType(), - ], - ]; + + if(\is_array($model)) { + $modelDescription = \join(', or ', \array_map(function ($m) { + return $m->getName(); + }, $model)); + // model has multiple possible responses, we will use oneOf + foreach ($model as $m) { + $usedModels[] = $m->getType(); + } + $temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [ + 'description' => $modelDescription, + 'content' => [ + $produces => [ + 'schema' => [ + 'oneOf' => \array_map(function($m) { + return ['$ref' => '#/definitions/'.$m->getType()]; + }, $model) + ], + ], + ], + ]; + } else { + // Response definition using one type + $usedModels[] = $model->getType(); + $temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [ + 'description' => $model->getName(), + 'content' => [ + $produces => [ + 'schema' => [ + '$ref' => '#/definitions/'.$model->getType(), + ], + ], + ], + ]; + } } if(in_array($route->getLabel('sdk.response.code', 500), [204, 301, 302, 308], true)) {