1
0
Fork 0
mirror of synced 2024-07-02 13:10:38 +12:00

Merge pull request #3473 from appwrite/feat-specs-nested-models

feat: nested models in spec
This commit is contained in:
Torsten Dittmann 2022-06-30 10:54:36 +02:00 committed by GitHub
commit 4ffca32653
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 185 additions and 256 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -123,105 +123,94 @@ $cli
],
];
foreach (['swagger2', 'open-api3'] as $format) {
foreach ($platforms as $platform) {
$routes = [];
$models = [];
$services = [];
foreach ($platforms as $platform) {
$routes = [];
$models = [];
$services = [];
foreach ($appRoutes as $key => $method) {
foreach ($method as $route) {
/** @var \Utopia\Route $route */
$routeSecurity = $route->getLabel('sdk.auth', []);
$sdkPlatofrms = [];
foreach ($appRoutes as $key => $method) {
foreach ($method as $route) {
/** @var \Utopia\Route $route */
$routeSecurity = $route->getLabel('sdk.auth', []);
$sdkPlaforms = [];
foreach ($routeSecurity as $value) {
switch ($value) {
case APP_AUTH_TYPE_SESSION:
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
break;
case APP_AUTH_TYPE_KEY:
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_JWT:
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_ADMIN:
$sdkPlatofrms[] = APP_PLATFORM_CONSOLE;
break;
}
foreach ($routeSecurity as $value) {
switch ($value) {
case APP_AUTH_TYPE_SESSION:
$sdkPlaforms[] = APP_PLATFORM_CLIENT;
break;
case APP_AUTH_TYPE_KEY:
$sdkPlaforms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_JWT:
$sdkPlaforms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_ADMIN:
$sdkPlaforms[] = APP_PLATFORM_CONSOLE;
break;
}
if (empty($routeSecurity)) {
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
}
if (!$route->getLabel('docs', true)) {
continue;
}
if ($route->getLabel('sdk.mock', false) && !$mocks) {
continue;
}
if (!$route->getLabel('sdk.mock', false) && $mocks) {
continue;
}
if (empty($route->getLabel('sdk.namespace', null))) {
continue;
}
if ($platform !== APP_PLATFORM_CONSOLE && !\in_array($platforms[$platform], $sdkPlatofrms)) {
continue;
}
$routes[] = $route;
$modelLabel = $route->getLabel('sdk.response.model', 'none');
\is_array($modelLabel) ? \array_map(function ($m) use ($response) {
return $response->getModel($m);
}, $modelLabel) : $response->getModel($modelLabel);
}
}
foreach (Config::getParam('services', []) as $service) {
if (
!isset($service['docs']) // Skip service if not part of the public API
|| !isset($service['sdk'])
|| !$service['docs']
|| !$service['sdk']
) {
if (empty($routeSecurity)) {
$sdkPlaforms[] = APP_PLATFORM_CLIENT;
}
if (!$route->getLabel('docs', true)) {
continue;
}
$services[] = [
'name' => $service['key'] ?? '',
'description' => $service['subtitle'] ?? '',
'x-globalAttributes' => $service['globalAttributes'] ?? [],
];
}
$models = $response->getModels();
foreach ($models as $key => $value) {
if ($platform !== APP_PLATFORM_CONSOLE && !$value->isPublic()) {
unset($models[$key]);
if ($route->getLabel('sdk.mock', false) && !$mocks) {
continue;
}
if (!$route->getLabel('sdk.mock', false) && $mocks) {
continue;
}
if (empty($route->getLabel('sdk.namespace', null))) {
continue;
}
if ($platform !== APP_PLATFORM_CONSOLE && !\in_array($platforms[$platform], $sdkPlaforms)) {
continue;
}
$routes[] = $route;
}
}
foreach (Config::getParam('services', []) as $service) {
if (
!isset($service['docs']) // Skip service if not part of the public API
|| !isset($service['sdk'])
|| !$service['docs']
|| !$service['sdk']
) {
continue;
}
switch ($format) {
case 'swagger2':
$formatInstance = new Swagger2(new App('UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0);
break;
$services[] = [
'name' => $service['key'] ?? '',
'description' => $service['subtitle'] ?? '',
'x-globalAttributes' => $service['globalAttributes'] ?? [],
];
}
case 'open-api3':
$formatInstance = new OpenAPI3(new App('UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0);
break;
$models = $response->getModels();
default:
throw new Exception('Format not found: ' . $format);
break;
foreach ($models as $key => $value) {
if ($platform !== APP_PLATFORM_CONSOLE && !$value->isPublic()) {
unset($models[$key]);
}
}
// var_dump($models);
$arguments = [new App('UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0];
foreach (['swagger2', 'open-api3'] as $format) {
$formatInstance = match ($format) {
'swagger2' => new Swagger2(...$arguments),
'open-api3' => new OpenAPI3(...$arguments),
default => throw new Exception('Format not found: ' . $format)
};
$specs = new Specification($formatInstance);
$endpoint = App::getEnv('_APP_HOME', '[HOSTNAME]');

View file

@ -8,40 +8,22 @@ use Appwrite\Utopia\Response\Model;
abstract class Format
{
/**
* @var App
*/
protected $app;
/**
* @var array
*/
protected $services;
protected App $app;
/**
* @var Route[]
*/
protected $routes;
protected array $routes;
/**
* @var Model[]
*/
protected $models;
protected array $models;
/**
* @var array
*/
protected $keys;
/**
* @var int
*/
protected $authCount;
/**
* @var array
*/
protected $params = [
protected array $services;
protected array $keys;
protected int $authCount;
protected array $params = [
'name' => '',
'description' => '',
'endpoint' => 'https://localhost',
@ -56,14 +38,6 @@ abstract class Format
'license.url' => '',
];
/**
* @param App $app
* @param array $services
* @param Route[] $routes
* @param Model[] $models
* @param array $keys
* @param int $authCount
*/
public function __construct(App $app, array $services, array $routes, array $models, array $keys, int $authCount)
{
$this->app = $app;
@ -121,10 +95,6 @@ abstract class Format
*/
public function getParam(string $key, string $default = ''): string
{
if (!isset($this->params[$key])) {
return $default;
}
return $this->params[$key];
return $this->params[$key] ?? $default;
}
}

View file

@ -4,35 +4,40 @@ namespace Appwrite\Specification\Format;
use Appwrite\Specification\Format;
use Appwrite\Template\Template;
use Appwrite\Utopia\Response\Model;
use Utopia\Validator;
class OpenAPI3 extends Format
{
/**
* Get Name.
*
* Get format name
*
* @return string
*/
public function getName(): string
{
return 'Open API 3';
}
/**
* Parse
*
* Parses Appwrite App to given format
*
* @return array
*/
protected function getNestedModels(Model $model, array &$usedModels): void
{
foreach ($model->getRules() as $rule) {
if (
in_array($model->getType(), $usedModels)
&& !in_array($rule['type'], ['string', 'integer', 'boolean', 'json', 'float', 'double'])
) {
$usedModels[] = $rule['type'];
foreach ($this->models as $m) {
if ($m->getType() === $rule['type']) {
$this->getNestedModels($m, $usedModels);
return;
}
}
}
}
}
public function parse(): array
{
/**
* Specifications (v3.0.0):
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md
*/
* Specifications (v3.0.0):
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md
*/
$output = [
'openapi' => '3.0.0',
'info' => [
@ -89,7 +94,7 @@ class OpenAPI3 extends Format
$usedModels = [];
foreach ($this->routes as $route) { /** @var \Utopia\Route $route */
foreach ($this->routes as $route) {
$url = \str_replace('/v1', '', $route->getPath());
$scope = $route->getLabel('scope', '');
$hide = $route->getLabel('sdk.hide', false);
@ -104,34 +109,32 @@ class OpenAPI3 extends Format
$produces = $route->getLabel('sdk.response.type', null);
$model = $route->getLabel('sdk.response.model', 'none');
$routeSecurity = $route->getLabel('sdk.auth', []);
$sdkPlatofrms = [];
$sdkPlatforms = [];
foreach ($routeSecurity as $value) {
switch ($value) {
case APP_AUTH_TYPE_SESSION:
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
break;
case APP_AUTH_TYPE_KEY:
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
$sdkPlatforms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_JWT:
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
$sdkPlatforms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_ADMIN:
$sdkPlatofrms[] = APP_PLATFORM_CONSOLE;
$sdkPlatforms[] = APP_PLATFORM_CONSOLE;
break;
}
}
if (empty($routeSecurity)) {
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
}
$temp = [
'summary' => $route->getDesc(),
'operationId' => $route->getLabel('sdk.namespace', 'default') . ucfirst($id),
// 'consumes' => [],
// 'produces' => [$produces],
'tags' => [$route->getLabel('sdk.namespace', 'default')],
'description' => ($desc) ? \file_get_contents($desc) : '',
'responses' => [],
@ -146,20 +149,14 @@ class OpenAPI3 extends Format
'rate-time' => $route->getLabel('abuse-time', 3600),
'rate-key' => $route->getLabel('abuse-key', 'url:{url},ip:{ip}'),
'scope' => $route->getLabel('scope', ''),
'platforms' => $sdkPlatofrms,
'platforms' => $sdkPlatforms,
'packaging' => $route->getLabel('sdk.packaging', false),
],
];
foreach ($this->models as $key => $value) {
foreach ($this->models as $value) {
if (\is_array($model)) {
$model = \array_map(function ($m) use ($value) {
if ($m === $value->getType()) {
return $value;
}
return $m;
}, $model);
$model = \array_map(fn ($m) => $m === $value->getType() ? $value : $m, $model);
} else {
if ($value->getType() === $model) {
$model = $value;
@ -168,9 +165,9 @@ class OpenAPI3 extends Format
}
}
if (!(\is_array($model)) && $model->isNone()) {
if (!(\is_array($model)) && $model->isNone()) {
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [
'description' => (in_array($produces, [
'description' => in_array($produces, [
'image/*',
'image/jpeg',
'image/gif',
@ -179,16 +176,11 @@ class OpenAPI3 extends Format
'image/svg-x',
'image/x-icon',
'image/bmp',
])) ? 'Image' : 'File',
// 'schema' => [
// 'type' => 'file'
// ],
]) ? 'Image' : 'File',
];
} else {
if (\is_array($model)) {
$modelDescription = \join(', or ', \array_map(function ($m) {
return $m->getName();
}, $model));
$modelDescription = \join(', or ', \array_map(fn ($m) => $m->getName(), $model));
// model has multiple possible responses, we will use oneOf
foreach ($model as $m) {
@ -200,9 +192,7 @@ class OpenAPI3 extends Format
'content' => [
$produces => [
'schema' => [
'oneOf' => \array_map(function ($m) {
return ['$ref' => '#/components/schemas/' . $m->getType()];
}, $model)
'oneOf' => \array_map(fn ($m) => ['$ref' => '#/components/schemas/' . $m->getType()], $model)
],
],
],
@ -255,7 +245,10 @@ class OpenAPI3 extends Format
$bodyRequired = [];
foreach ($route->getParams() as $name => $param) { // Set params
$validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $this->app->getResources($param['injections'])) : $param['validator']; /* @var $validator \Utopia\Validator */
/**
* @var \Utopia\Validator $validator
*/
$validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $this->app->getResources($param['injections'])) : $param['validator'];
$node = [
'name' => $name,
@ -329,7 +322,8 @@ class OpenAPI3 extends Format
$node['schema']['format'] = 'password';
$node['schema']['x-example'] = 'password';
break;
case 'Utopia\Validator\Range': /** @var \Utopia\Validator\Range $validator */
case 'Utopia\Validator\Range':
/** @var \Utopia\Validator\Range $validator */
$node['schema']['type'] = $validator->getType() === Validator::TYPE_FLOAT ? 'number' : $validator->getType();
$node['schema']['format'] = $validator->getType() == Validator::TYPE_INTEGER ? 'int32' : 'float';
$node['schema']['x-example'] = $validator->getMin();
@ -351,7 +345,8 @@ class OpenAPI3 extends Format
$node['schema']['format'] = 'url';
$node['schema']['x-example'] = 'https://example.com';
break;
case 'Utopia\Validator\WhiteList': /** @var \Utopia\Validator\WhiteList $validator */
case 'Utopia\Validator\WhiteList':
/** @var \Utopia\Validator\WhiteList $validator */
$node['schema']['type'] = $validator->getType();
$node['schema']['x-example'] = $validator->getList()[0];
@ -413,20 +408,11 @@ class OpenAPI3 extends Format
$temp['requestBody'] = $body;
}
//$temp['consumes'] = $consumes;
$output['paths'][$url][\strtolower($route->getMethod())] = $temp;
}
foreach ($this->models as $model) {
foreach ($model->getRules() as $rule) {
if (
in_array($model->getType(), $usedModels)
&& !in_array($rule['type'], ['string', 'integer', 'boolean', 'json', 'float'])
) {
$usedModels[] = $rule['type'];
}
}
$this->getNestedModels($model, $usedModels);
}
foreach ($this->models as $model) {

View file

@ -4,34 +4,39 @@ namespace Appwrite\Specification\Format;
use Appwrite\Specification\Format;
use Appwrite\Template\Template;
use Appwrite\Utopia\Response\Model;
use Utopia\Validator;
class Swagger2 extends Format
{
/**
* Get Name.
*
* Get format name
*
* @return string
*/
public function getName(): string
{
return 'Swagger 2';
}
/**
* Parse
*
* Parses Appwrite App to given format
*
* @return array
*/
protected function getNestedModels(Model $model, array &$usedModels): void
{
foreach ($model->getRules() as $rule) {
if (
in_array($model->getType(), $usedModels)
&& !in_array($rule['type'], ['string', 'integer', 'boolean', 'json', 'float', 'double'])
) {
$usedModels[] = $rule['type'];
foreach ($this->models as $m) {
if ($m->getType() === $rule['type']) {
$this->getNestedModels($m, $usedModels);
return;
}
}
}
}
}
public function parse(): array
{
/*
* Specifications (v3.0.0):
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md
* Specifications (v2.0):
* https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md
*/
$output = [
'swagger' => '2.0',
@ -87,7 +92,8 @@ class Swagger2 extends Format
$usedModels = [];
foreach ($this->routes as $route) { /** @var \Utopia\Route $route */
foreach ($this->routes as $route) {
/** @var \Utopia\Route $route */
$url = \str_replace('/v1', '', $route->getPath());
$scope = $route->getLabel('scope', '');
$hide = $route->getLabel('sdk.hide', false);
@ -102,27 +108,27 @@ class Swagger2 extends Format
$produces = $route->getLabel('sdk.response.type', null);
$model = $route->getLabel('sdk.response.model', 'none');
$routeSecurity = $route->getLabel('sdk.auth', []);
$sdkPlatofrms = [];
$sdkPlatforms = [];
foreach ($routeSecurity as $value) {
switch ($value) {
case APP_AUTH_TYPE_SESSION:
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
break;
case APP_AUTH_TYPE_KEY:
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
$sdkPlatforms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_JWT:
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
$sdkPlatforms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_ADMIN:
$sdkPlatofrms[] = APP_PLATFORM_CONSOLE;
$sdkPlatforms[] = APP_PLATFORM_CONSOLE;
break;
}
}
if (empty($routeSecurity)) {
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
}
$temp = [
@ -144,7 +150,7 @@ class Swagger2 extends Format
'rate-time' => $route->getLabel('abuse-time', 3600),
'rate-key' => $route->getLabel('abuse-key', 'url:{url},ip:{ip}'),
'scope' => $route->getLabel('scope', ''),
'platforms' => $sdkPlatofrms,
'platforms' => $sdkPlatforms,
'packaging' => $route->getLabel('sdk.packaging', false),
],
];
@ -153,14 +159,9 @@ class Swagger2 extends Format
$temp['produces'][] = $produces;
}
foreach ($this->models as $key => $value) {
foreach ($this->models as $value) {
if (\is_array($model)) {
$model = \array_map(function ($m) use ($value) {
if ($m === $value->getType()) {
return $value;
}
return $m;
}, $model);
$model = \array_map(fn ($m) => $m === $value->getType() ? $value : $m, $model);
} else {
if ($value->getType() === $model) {
$model = $value;
@ -171,7 +172,7 @@ class Swagger2 extends Format
if (!(\is_array($model)) && $model->isNone()) {
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [
'description' => (in_array($produces, [
'description' => in_array($produces, [
'image/*',
'image/jpeg',
'image/gif',
@ -180,16 +181,14 @@ class Swagger2 extends Format
'image/svg-x',
'image/x-icon',
'image/bmp',
])) ? 'Image' : 'File',
]) ? 'Image' : 'File',
'schema' => [
'type' => 'file'
],
];
} else {
if (\is_array($model)) {
$modelDescription = \join(', or ', \array_map(function ($m) {
return $m->getName();
}, $model));
$modelDescription = \join(', or ', \array_map(fn ($m) => $m->getName(), $model));
// model has multiple possible responses, we will use oneOf
foreach ($model as $m) {
$usedModels[] = $m->getType();
@ -244,7 +243,8 @@ class Swagger2 extends Format
$bodyRequired = [];
foreach ($route->getParams() as $name => $param) { // Set params
$validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $this->app->getResources($param['injections'])) : $param['validator']; /** @var \Utopia\Validator $validator */
/** @var \Utopia\Validator $validator */
$validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $this->app->getResources($param['injections'])) : $param['validator'];
$node = [
'name' => $name,
@ -294,7 +294,6 @@ class Swagger2 extends Format
$node['type'] = 'object';
$node['default'] = (empty($param['default'])) ? new \stdClass() : $param['default'];
$node['x-example'] = '{}';
//$node['format'] = 'json';
break;
case 'Utopia\Storage\Validator\File':
$consumes = ['multipart/form-data'];
@ -320,7 +319,8 @@ class Swagger2 extends Format
$node['format'] = 'password';
$node['x-example'] = 'password';
break;
case 'Utopia\Validator\Range': /** @var \Utopia\Validator\Range $validator */
case 'Utopia\Validator\Range':
/** @var \Utopia\Validator\Range $validator */
$node['type'] = $validator->getType() === Validator::TYPE_FLOAT ? 'number' : $validator->getType();
$node['format'] = $validator->getType() == Validator::TYPE_INTEGER ? 'int32' : 'float';
$node['x-example'] = $validator->getMin();
@ -342,7 +342,8 @@ class Swagger2 extends Format
$node['format'] = 'url';
$node['x-example'] = 'https://example.com';
break;
case 'Utopia\Validator\WhiteList': /** @var \Utopia\Validator\WhiteList $validator */
case 'Utopia\Validator\WhiteList':
/** @var \Utopia\Validator\WhiteList $validator */
$node['type'] = $validator->getType();
$node['x-example'] = $validator->getList()[0];
@ -410,14 +411,7 @@ class Swagger2 extends Format
}
foreach ($this->models as $model) {
foreach ($model->getRules() as $rule) {
if (
in_array($model->getType(), $usedModels)
&& !in_array($rule['type'], ['string', 'integer', 'boolean', 'json', 'float'])
) {
$usedModels[] = $rule['type'];
}
}
$this->getNestedModels($model, $usedModels);
}
foreach ($this->models as $model) {
@ -485,15 +479,11 @@ class Swagger2 extends Format
if (\is_array($rule['type'])) {
if ($rule['array']) {
$items = [
'x-anyOf' => \array_map(function ($type) {
return ['$ref' => '#/definitions/' . $type];
}, $rule['type'])
'x-anyOf' => \array_map(fn ($type) => ['$ref' => '#/definitions/' . $type], $rule['type'])
];
} else {
$items = [
'x-oneOf' => \array_map(function ($type) {
return ['$ref' => '#/definitions/' . $type];
}, $rule['type'])
'x-oneOf' => \array_map(fn ($type) => ['$ref' => '#/definitions/' . $type], $rule['type'])
];
}
} else {

View file

@ -4,14 +4,8 @@ namespace Appwrite\Specification;
class Specification
{
/**
* @var Format
*/
protected $format;
protected Format $format;
/**
* @param Format $format
*/
public function __construct(Format $format)
{
$this->format = $format;