Merge pull request #3473 from appwrite/feat-specs-nested-models
feat: nested models in spec
This commit is contained in:
commit
4ffca32653
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
|
@ -123,105 +123,94 @@ $cli
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach (['swagger2', 'open-api3'] as $format) {
|
foreach ($platforms as $platform) {
|
||||||
foreach ($platforms as $platform) {
|
$routes = [];
|
||||||
$routes = [];
|
$models = [];
|
||||||
$models = [];
|
$services = [];
|
||||||
$services = [];
|
|
||||||
|
|
||||||
foreach ($appRoutes as $key => $method) {
|
foreach ($appRoutes as $key => $method) {
|
||||||
foreach ($method as $route) {
|
foreach ($method as $route) {
|
||||||
/** @var \Utopia\Route $route */
|
/** @var \Utopia\Route $route */
|
||||||
$routeSecurity = $route->getLabel('sdk.auth', []);
|
$routeSecurity = $route->getLabel('sdk.auth', []);
|
||||||
$sdkPlatofrms = [];
|
$sdkPlaforms = [];
|
||||||
|
|
||||||
foreach ($routeSecurity as $value) {
|
foreach ($routeSecurity as $value) {
|
||||||
switch ($value) {
|
switch ($value) {
|
||||||
case APP_AUTH_TYPE_SESSION:
|
case APP_AUTH_TYPE_SESSION:
|
||||||
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
|
$sdkPlaforms[] = APP_PLATFORM_CLIENT;
|
||||||
break;
|
break;
|
||||||
case APP_AUTH_TYPE_KEY:
|
case APP_AUTH_TYPE_KEY:
|
||||||
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
|
$sdkPlaforms[] = APP_PLATFORM_SERVER;
|
||||||
break;
|
break;
|
||||||
case APP_AUTH_TYPE_JWT:
|
case APP_AUTH_TYPE_JWT:
|
||||||
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
|
$sdkPlaforms[] = APP_PLATFORM_SERVER;
|
||||||
break;
|
break;
|
||||||
case APP_AUTH_TYPE_ADMIN:
|
case APP_AUTH_TYPE_ADMIN:
|
||||||
$sdkPlatofrms[] = APP_PLATFORM_CONSOLE;
|
$sdkPlaforms[] = APP_PLATFORM_CONSOLE;
|
||||||
break;
|
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 (empty($routeSecurity)) {
|
||||||
if (
|
$sdkPlaforms[] = APP_PLATFORM_CLIENT;
|
||||||
!isset($service['docs']) // Skip service if not part of the public API
|
}
|
||||||
|| !isset($service['sdk'])
|
|
||||||
|| !$service['docs']
|
if (!$route->getLabel('docs', true)) {
|
||||||
|| !$service['sdk']
|
|
||||||
) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$services[] = [
|
if ($route->getLabel('sdk.mock', false) && !$mocks) {
|
||||||
'name' => $service['key'] ?? '',
|
continue;
|
||||||
'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 (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) {
|
$services[] = [
|
||||||
case 'swagger2':
|
'name' => $service['key'] ?? '',
|
||||||
$formatInstance = new Swagger2(new App('UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0);
|
'description' => $service['subtitle'] ?? '',
|
||||||
break;
|
'x-globalAttributes' => $service['globalAttributes'] ?? [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
case 'open-api3':
|
$models = $response->getModels();
|
||||||
$formatInstance = new OpenAPI3(new App('UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
foreach ($models as $key => $value) {
|
||||||
throw new Exception('Format not found: ' . $format);
|
if ($platform !== APP_PLATFORM_CONSOLE && !$value->isPublic()) {
|
||||||
break;
|
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);
|
$specs = new Specification($formatInstance);
|
||||||
$endpoint = App::getEnv('_APP_HOME', '[HOSTNAME]');
|
$endpoint = App::getEnv('_APP_HOME', '[HOSTNAME]');
|
||||||
|
|
|
@ -8,40 +8,22 @@ use Appwrite\Utopia\Response\Model;
|
||||||
|
|
||||||
abstract class Format
|
abstract class Format
|
||||||
{
|
{
|
||||||
/**
|
protected App $app;
|
||||||
* @var App
|
|
||||||
*/
|
|
||||||
protected $app;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $services;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Route[]
|
* @var Route[]
|
||||||
*/
|
*/
|
||||||
protected $routes;
|
protected array $routes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Model[]
|
* @var Model[]
|
||||||
*/
|
*/
|
||||||
protected $models;
|
protected array $models;
|
||||||
|
|
||||||
/**
|
protected array $services;
|
||||||
* @var array
|
protected array $keys;
|
||||||
*/
|
protected int $authCount;
|
||||||
protected $keys;
|
protected array $params = [
|
||||||
|
|
||||||
/**
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
protected $authCount;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $params = [
|
|
||||||
'name' => '',
|
'name' => '',
|
||||||
'description' => '',
|
'description' => '',
|
||||||
'endpoint' => 'https://localhost',
|
'endpoint' => 'https://localhost',
|
||||||
|
@ -56,14 +38,6 @@ abstract class Format
|
||||||
'license.url' => '',
|
'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)
|
public function __construct(App $app, array $services, array $routes, array $models, array $keys, int $authCount)
|
||||||
{
|
{
|
||||||
$this->app = $app;
|
$this->app = $app;
|
||||||
|
@ -121,10 +95,6 @@ abstract class Format
|
||||||
*/
|
*/
|
||||||
public function getParam(string $key, string $default = ''): string
|
public function getParam(string $key, string $default = ''): string
|
||||||
{
|
{
|
||||||
if (!isset($this->params[$key])) {
|
return $this->params[$key] ?? $default;
|
||||||
return $default;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->params[$key];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,35 +4,40 @@ namespace Appwrite\Specification\Format;
|
||||||
|
|
||||||
use Appwrite\Specification\Format;
|
use Appwrite\Specification\Format;
|
||||||
use Appwrite\Template\Template;
|
use Appwrite\Template\Template;
|
||||||
|
use Appwrite\Utopia\Response\Model;
|
||||||
use Utopia\Validator;
|
use Utopia\Validator;
|
||||||
|
|
||||||
class OpenAPI3 extends Format
|
class OpenAPI3 extends Format
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Get Name.
|
|
||||||
*
|
|
||||||
* Get format name
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getName(): string
|
public function getName(): string
|
||||||
{
|
{
|
||||||
return 'Open API 3';
|
return 'Open API 3';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected function getNestedModels(Model $model, array &$usedModels): void
|
||||||
* Parse
|
{
|
||||||
*
|
foreach ($model->getRules() as $rule) {
|
||||||
* Parses Appwrite App to given format
|
if (
|
||||||
*
|
in_array($model->getType(), $usedModels)
|
||||||
* @return array
|
&& !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
|
public function parse(): array
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Specifications (v3.0.0):
|
* Specifications (v3.0.0):
|
||||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md
|
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md
|
||||||
*/
|
*/
|
||||||
$output = [
|
$output = [
|
||||||
'openapi' => '3.0.0',
|
'openapi' => '3.0.0',
|
||||||
'info' => [
|
'info' => [
|
||||||
|
@ -89,7 +94,7 @@ class OpenAPI3 extends Format
|
||||||
|
|
||||||
$usedModels = [];
|
$usedModels = [];
|
||||||
|
|
||||||
foreach ($this->routes as $route) { /** @var \Utopia\Route $route */
|
foreach ($this->routes as $route) {
|
||||||
$url = \str_replace('/v1', '', $route->getPath());
|
$url = \str_replace('/v1', '', $route->getPath());
|
||||||
$scope = $route->getLabel('scope', '');
|
$scope = $route->getLabel('scope', '');
|
||||||
$hide = $route->getLabel('sdk.hide', false);
|
$hide = $route->getLabel('sdk.hide', false);
|
||||||
|
@ -104,34 +109,32 @@ class OpenAPI3 extends Format
|
||||||
$produces = $route->getLabel('sdk.response.type', 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', []);
|
$routeSecurity = $route->getLabel('sdk.auth', []);
|
||||||
$sdkPlatofrms = [];
|
$sdkPlatforms = [];
|
||||||
|
|
||||||
foreach ($routeSecurity as $value) {
|
foreach ($routeSecurity as $value) {
|
||||||
switch ($value) {
|
switch ($value) {
|
||||||
case APP_AUTH_TYPE_SESSION:
|
case APP_AUTH_TYPE_SESSION:
|
||||||
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
|
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
|
||||||
break;
|
break;
|
||||||
case APP_AUTH_TYPE_KEY:
|
case APP_AUTH_TYPE_KEY:
|
||||||
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
|
$sdkPlatforms[] = APP_PLATFORM_SERVER;
|
||||||
break;
|
break;
|
||||||
case APP_AUTH_TYPE_JWT:
|
case APP_AUTH_TYPE_JWT:
|
||||||
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
|
$sdkPlatforms[] = APP_PLATFORM_SERVER;
|
||||||
break;
|
break;
|
||||||
case APP_AUTH_TYPE_ADMIN:
|
case APP_AUTH_TYPE_ADMIN:
|
||||||
$sdkPlatofrms[] = APP_PLATFORM_CONSOLE;
|
$sdkPlatforms[] = APP_PLATFORM_CONSOLE;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($routeSecurity)) {
|
if (empty($routeSecurity)) {
|
||||||
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
|
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
$temp = [
|
$temp = [
|
||||||
'summary' => $route->getDesc(),
|
'summary' => $route->getDesc(),
|
||||||
'operationId' => $route->getLabel('sdk.namespace', 'default') . ucfirst($id),
|
'operationId' => $route->getLabel('sdk.namespace', 'default') . ucfirst($id),
|
||||||
// 'consumes' => [],
|
|
||||||
// 'produces' => [$produces],
|
|
||||||
'tags' => [$route->getLabel('sdk.namespace', 'default')],
|
'tags' => [$route->getLabel('sdk.namespace', 'default')],
|
||||||
'description' => ($desc) ? \file_get_contents($desc) : '',
|
'description' => ($desc) ? \file_get_contents($desc) : '',
|
||||||
'responses' => [],
|
'responses' => [],
|
||||||
|
@ -146,20 +149,14 @@ class OpenAPI3 extends Format
|
||||||
'rate-time' => $route->getLabel('abuse-time', 3600),
|
'rate-time' => $route->getLabel('abuse-time', 3600),
|
||||||
'rate-key' => $route->getLabel('abuse-key', 'url:{url},ip:{ip}'),
|
'rate-key' => $route->getLabel('abuse-key', 'url:{url},ip:{ip}'),
|
||||||
'scope' => $route->getLabel('scope', ''),
|
'scope' => $route->getLabel('scope', ''),
|
||||||
'platforms' => $sdkPlatofrms,
|
'platforms' => $sdkPlatforms,
|
||||||
'packaging' => $route->getLabel('sdk.packaging', false),
|
'packaging' => $route->getLabel('sdk.packaging', false),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($this->models as $key => $value) {
|
foreach ($this->models as $value) {
|
||||||
if (\is_array($model)) {
|
if (\is_array($model)) {
|
||||||
$model = \array_map(function ($m) use ($value) {
|
$model = \array_map(fn ($m) => $m === $value->getType() ? $value : $m, $model);
|
||||||
if ($m === $value->getType()) {
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $m;
|
|
||||||
}, $model);
|
|
||||||
} else {
|
} else {
|
||||||
if ($value->getType() === $model) {
|
if ($value->getType() === $model) {
|
||||||
$model = $value;
|
$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')] = [
|
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [
|
||||||
'description' => (in_array($produces, [
|
'description' => in_array($produces, [
|
||||||
'image/*',
|
'image/*',
|
||||||
'image/jpeg',
|
'image/jpeg',
|
||||||
'image/gif',
|
'image/gif',
|
||||||
|
@ -179,16 +176,11 @@ class OpenAPI3 extends Format
|
||||||
'image/svg-x',
|
'image/svg-x',
|
||||||
'image/x-icon',
|
'image/x-icon',
|
||||||
'image/bmp',
|
'image/bmp',
|
||||||
])) ? 'Image' : 'File',
|
]) ? 'Image' : 'File',
|
||||||
// 'schema' => [
|
|
||||||
// 'type' => 'file'
|
|
||||||
// ],
|
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
if (\is_array($model)) {
|
if (\is_array($model)) {
|
||||||
$modelDescription = \join(', or ', \array_map(function ($m) {
|
$modelDescription = \join(', or ', \array_map(fn ($m) => $m->getName(), $model));
|
||||||
return $m->getName();
|
|
||||||
}, $model));
|
|
||||||
|
|
||||||
// model has multiple possible responses, we will use oneOf
|
// model has multiple possible responses, we will use oneOf
|
||||||
foreach ($model as $m) {
|
foreach ($model as $m) {
|
||||||
|
@ -200,9 +192,7 @@ class OpenAPI3 extends Format
|
||||||
'content' => [
|
'content' => [
|
||||||
$produces => [
|
$produces => [
|
||||||
'schema' => [
|
'schema' => [
|
||||||
'oneOf' => \array_map(function ($m) {
|
'oneOf' => \array_map(fn ($m) => ['$ref' => '#/components/schemas/' . $m->getType()], $model)
|
||||||
return ['$ref' => '#/components/schemas/' . $m->getType()];
|
|
||||||
}, $model)
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
@ -255,7 +245,10 @@ class OpenAPI3 extends Format
|
||||||
$bodyRequired = [];
|
$bodyRequired = [];
|
||||||
|
|
||||||
foreach ($route->getParams() as $name => $param) { // Set params
|
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 = [
|
$node = [
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
|
@ -329,7 +322,8 @@ class OpenAPI3 extends Format
|
||||||
$node['schema']['format'] = 'password';
|
$node['schema']['format'] = 'password';
|
||||||
$node['schema']['x-example'] = 'password';
|
$node['schema']['x-example'] = 'password';
|
||||||
break;
|
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']['type'] = $validator->getType() === Validator::TYPE_FLOAT ? 'number' : $validator->getType();
|
||||||
$node['schema']['format'] = $validator->getType() == Validator::TYPE_INTEGER ? 'int32' : 'float';
|
$node['schema']['format'] = $validator->getType() == Validator::TYPE_INTEGER ? 'int32' : 'float';
|
||||||
$node['schema']['x-example'] = $validator->getMin();
|
$node['schema']['x-example'] = $validator->getMin();
|
||||||
|
@ -351,7 +345,8 @@ class OpenAPI3 extends Format
|
||||||
$node['schema']['format'] = 'url';
|
$node['schema']['format'] = 'url';
|
||||||
$node['schema']['x-example'] = 'https://example.com';
|
$node['schema']['x-example'] = 'https://example.com';
|
||||||
break;
|
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']['type'] = $validator->getType();
|
||||||
$node['schema']['x-example'] = $validator->getList()[0];
|
$node['schema']['x-example'] = $validator->getList()[0];
|
||||||
|
|
||||||
|
@ -413,20 +408,11 @@ class OpenAPI3 extends Format
|
||||||
$temp['requestBody'] = $body;
|
$temp['requestBody'] = $body;
|
||||||
}
|
}
|
||||||
|
|
||||||
//$temp['consumes'] = $consumes;
|
|
||||||
|
|
||||||
$output['paths'][$url][\strtolower($route->getMethod())] = $temp;
|
$output['paths'][$url][\strtolower($route->getMethod())] = $temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->models as $model) {
|
foreach ($this->models as $model) {
|
||||||
foreach ($model->getRules() as $rule) {
|
$this->getNestedModels($model, $usedModels);
|
||||||
if (
|
|
||||||
in_array($model->getType(), $usedModels)
|
|
||||||
&& !in_array($rule['type'], ['string', 'integer', 'boolean', 'json', 'float'])
|
|
||||||
) {
|
|
||||||
$usedModels[] = $rule['type'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->models as $model) {
|
foreach ($this->models as $model) {
|
||||||
|
|
|
@ -4,34 +4,39 @@ namespace Appwrite\Specification\Format;
|
||||||
|
|
||||||
use Appwrite\Specification\Format;
|
use Appwrite\Specification\Format;
|
||||||
use Appwrite\Template\Template;
|
use Appwrite\Template\Template;
|
||||||
|
use Appwrite\Utopia\Response\Model;
|
||||||
use Utopia\Validator;
|
use Utopia\Validator;
|
||||||
|
|
||||||
class Swagger2 extends Format
|
class Swagger2 extends Format
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Get Name.
|
|
||||||
*
|
|
||||||
* Get format name
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getName(): string
|
public function getName(): string
|
||||||
{
|
{
|
||||||
return 'Swagger 2';
|
return 'Swagger 2';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected function getNestedModels(Model $model, array &$usedModels): void
|
||||||
* Parse
|
{
|
||||||
*
|
foreach ($model->getRules() as $rule) {
|
||||||
* Parses Appwrite App to given format
|
if (
|
||||||
*
|
in_array($model->getType(), $usedModels)
|
||||||
* @return array
|
&& !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
|
public function parse(): array
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Specifications (v3.0.0):
|
* Specifications (v2.0):
|
||||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md
|
* https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md
|
||||||
*/
|
*/
|
||||||
$output = [
|
$output = [
|
||||||
'swagger' => '2.0',
|
'swagger' => '2.0',
|
||||||
|
@ -87,7 +92,8 @@ class Swagger2 extends Format
|
||||||
|
|
||||||
$usedModels = [];
|
$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());
|
$url = \str_replace('/v1', '', $route->getPath());
|
||||||
$scope = $route->getLabel('scope', '');
|
$scope = $route->getLabel('scope', '');
|
||||||
$hide = $route->getLabel('sdk.hide', false);
|
$hide = $route->getLabel('sdk.hide', false);
|
||||||
|
@ -102,27 +108,27 @@ class Swagger2 extends Format
|
||||||
$produces = $route->getLabel('sdk.response.type', 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', []);
|
$routeSecurity = $route->getLabel('sdk.auth', []);
|
||||||
$sdkPlatofrms = [];
|
$sdkPlatforms = [];
|
||||||
|
|
||||||
foreach ($routeSecurity as $value) {
|
foreach ($routeSecurity as $value) {
|
||||||
switch ($value) {
|
switch ($value) {
|
||||||
case APP_AUTH_TYPE_SESSION:
|
case APP_AUTH_TYPE_SESSION:
|
||||||
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
|
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
|
||||||
break;
|
break;
|
||||||
case APP_AUTH_TYPE_KEY:
|
case APP_AUTH_TYPE_KEY:
|
||||||
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
|
$sdkPlatforms[] = APP_PLATFORM_SERVER;
|
||||||
break;
|
break;
|
||||||
case APP_AUTH_TYPE_JWT:
|
case APP_AUTH_TYPE_JWT:
|
||||||
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
|
$sdkPlatforms[] = APP_PLATFORM_SERVER;
|
||||||
break;
|
break;
|
||||||
case APP_AUTH_TYPE_ADMIN:
|
case APP_AUTH_TYPE_ADMIN:
|
||||||
$sdkPlatofrms[] = APP_PLATFORM_CONSOLE;
|
$sdkPlatforms[] = APP_PLATFORM_CONSOLE;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($routeSecurity)) {
|
if (empty($routeSecurity)) {
|
||||||
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
|
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
$temp = [
|
$temp = [
|
||||||
|
@ -144,7 +150,7 @@ class Swagger2 extends Format
|
||||||
'rate-time' => $route->getLabel('abuse-time', 3600),
|
'rate-time' => $route->getLabel('abuse-time', 3600),
|
||||||
'rate-key' => $route->getLabel('abuse-key', 'url:{url},ip:{ip}'),
|
'rate-key' => $route->getLabel('abuse-key', 'url:{url},ip:{ip}'),
|
||||||
'scope' => $route->getLabel('scope', ''),
|
'scope' => $route->getLabel('scope', ''),
|
||||||
'platforms' => $sdkPlatofrms,
|
'platforms' => $sdkPlatforms,
|
||||||
'packaging' => $route->getLabel('sdk.packaging', false),
|
'packaging' => $route->getLabel('sdk.packaging', false),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
@ -153,14 +159,9 @@ class Swagger2 extends Format
|
||||||
$temp['produces'][] = $produces;
|
$temp['produces'][] = $produces;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->models as $key => $value) {
|
foreach ($this->models as $value) {
|
||||||
if (\is_array($model)) {
|
if (\is_array($model)) {
|
||||||
$model = \array_map(function ($m) use ($value) {
|
$model = \array_map(fn ($m) => $m === $value->getType() ? $value : $m, $model);
|
||||||
if ($m === $value->getType()) {
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
return $m;
|
|
||||||
}, $model);
|
|
||||||
} else {
|
} else {
|
||||||
if ($value->getType() === $model) {
|
if ($value->getType() === $model) {
|
||||||
$model = $value;
|
$model = $value;
|
||||||
|
@ -171,7 +172,7 @@ class Swagger2 extends Format
|
||||||
|
|
||||||
if (!(\is_array($model)) && $model->isNone()) {
|
if (!(\is_array($model)) && $model->isNone()) {
|
||||||
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [
|
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [
|
||||||
'description' => (in_array($produces, [
|
'description' => in_array($produces, [
|
||||||
'image/*',
|
'image/*',
|
||||||
'image/jpeg',
|
'image/jpeg',
|
||||||
'image/gif',
|
'image/gif',
|
||||||
|
@ -180,16 +181,14 @@ class Swagger2 extends Format
|
||||||
'image/svg-x',
|
'image/svg-x',
|
||||||
'image/x-icon',
|
'image/x-icon',
|
||||||
'image/bmp',
|
'image/bmp',
|
||||||
])) ? 'Image' : 'File',
|
]) ? 'Image' : 'File',
|
||||||
'schema' => [
|
'schema' => [
|
||||||
'type' => 'file'
|
'type' => 'file'
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
if (\is_array($model)) {
|
if (\is_array($model)) {
|
||||||
$modelDescription = \join(', or ', \array_map(function ($m) {
|
$modelDescription = \join(', or ', \array_map(fn ($m) => $m->getName(), $model));
|
||||||
return $m->getName();
|
|
||||||
}, $model));
|
|
||||||
// model has multiple possible responses, we will use oneOf
|
// model has multiple possible responses, we will use oneOf
|
||||||
foreach ($model as $m) {
|
foreach ($model as $m) {
|
||||||
$usedModels[] = $m->getType();
|
$usedModels[] = $m->getType();
|
||||||
|
@ -244,7 +243,8 @@ class Swagger2 extends Format
|
||||||
$bodyRequired = [];
|
$bodyRequired = [];
|
||||||
|
|
||||||
foreach ($route->getParams() as $name => $param) { // Set params
|
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 = [
|
$node = [
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
|
@ -294,7 +294,6 @@ class Swagger2 extends Format
|
||||||
$node['type'] = 'object';
|
$node['type'] = 'object';
|
||||||
$node['default'] = (empty($param['default'])) ? new \stdClass() : $param['default'];
|
$node['default'] = (empty($param['default'])) ? new \stdClass() : $param['default'];
|
||||||
$node['x-example'] = '{}';
|
$node['x-example'] = '{}';
|
||||||
//$node['format'] = 'json';
|
|
||||||
break;
|
break;
|
||||||
case 'Utopia\Storage\Validator\File':
|
case 'Utopia\Storage\Validator\File':
|
||||||
$consumes = ['multipart/form-data'];
|
$consumes = ['multipart/form-data'];
|
||||||
|
@ -320,7 +319,8 @@ class Swagger2 extends Format
|
||||||
$node['format'] = 'password';
|
$node['format'] = 'password';
|
||||||
$node['x-example'] = 'password';
|
$node['x-example'] = 'password';
|
||||||
break;
|
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['type'] = $validator->getType() === Validator::TYPE_FLOAT ? 'number' : $validator->getType();
|
||||||
$node['format'] = $validator->getType() == Validator::TYPE_INTEGER ? 'int32' : 'float';
|
$node['format'] = $validator->getType() == Validator::TYPE_INTEGER ? 'int32' : 'float';
|
||||||
$node['x-example'] = $validator->getMin();
|
$node['x-example'] = $validator->getMin();
|
||||||
|
@ -342,7 +342,8 @@ class Swagger2 extends Format
|
||||||
$node['format'] = 'url';
|
$node['format'] = 'url';
|
||||||
$node['x-example'] = 'https://example.com';
|
$node['x-example'] = 'https://example.com';
|
||||||
break;
|
break;
|
||||||
case 'Utopia\Validator\WhiteList': /** @var \Utopia\Validator\WhiteList $validator */
|
case 'Utopia\Validator\WhiteList':
|
||||||
|
/** @var \Utopia\Validator\WhiteList $validator */
|
||||||
$node['type'] = $validator->getType();
|
$node['type'] = $validator->getType();
|
||||||
$node['x-example'] = $validator->getList()[0];
|
$node['x-example'] = $validator->getList()[0];
|
||||||
|
|
||||||
|
@ -410,14 +411,7 @@ class Swagger2 extends Format
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->models as $model) {
|
foreach ($this->models as $model) {
|
||||||
foreach ($model->getRules() as $rule) {
|
$this->getNestedModels($model, $usedModels);
|
||||||
if (
|
|
||||||
in_array($model->getType(), $usedModels)
|
|
||||||
&& !in_array($rule['type'], ['string', 'integer', 'boolean', 'json', 'float'])
|
|
||||||
) {
|
|
||||||
$usedModels[] = $rule['type'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->models as $model) {
|
foreach ($this->models as $model) {
|
||||||
|
@ -485,15 +479,11 @@ class Swagger2 extends Format
|
||||||
if (\is_array($rule['type'])) {
|
if (\is_array($rule['type'])) {
|
||||||
if ($rule['array']) {
|
if ($rule['array']) {
|
||||||
$items = [
|
$items = [
|
||||||
'x-anyOf' => \array_map(function ($type) {
|
'x-anyOf' => \array_map(fn ($type) => ['$ref' => '#/definitions/' . $type], $rule['type'])
|
||||||
return ['$ref' => '#/definitions/' . $type];
|
|
||||||
}, $rule['type'])
|
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
$items = [
|
$items = [
|
||||||
'x-oneOf' => \array_map(function ($type) {
|
'x-oneOf' => \array_map(fn ($type) => ['$ref' => '#/definitions/' . $type], $rule['type'])
|
||||||
return ['$ref' => '#/definitions/' . $type];
|
|
||||||
}, $rule['type'])
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -4,14 +4,8 @@ namespace Appwrite\Specification;
|
||||||
|
|
||||||
class Specification
|
class Specification
|
||||||
{
|
{
|
||||||
/**
|
protected Format $format;
|
||||||
* @var Format
|
|
||||||
*/
|
|
||||||
protected $format;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Format $format
|
|
||||||
*/
|
|
||||||
public function __construct(Format $format)
|
public function __construct(Format $format)
|
||||||
{
|
{
|
||||||
$this->format = $format;
|
$this->format = $format;
|
||||||
|
|
Loading…
Reference in a new issue