mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
feat(specs): add discriminators for polymorphic responses
This commit is contained in:
+4
-2
@@ -117,7 +117,9 @@ use Appwrite\Utopia\Response\Model\Project;
|
||||
use Appwrite\Utopia\Response\Model\Provider;
|
||||
use Appwrite\Utopia\Response\Model\ProviderRepository;
|
||||
use Appwrite\Utopia\Response\Model\ProviderRepositoryFramework;
|
||||
use Appwrite\Utopia\Response\Model\ProviderRepositoryFrameworkList;
|
||||
use Appwrite\Utopia\Response\Model\ProviderRepositoryRuntime;
|
||||
use Appwrite\Utopia\Response\Model\ProviderRepositoryRuntimeList;
|
||||
use Appwrite\Utopia\Response\Model\ResourceToken;
|
||||
use Appwrite\Utopia\Response\Model\Row;
|
||||
use Appwrite\Utopia\Response\Model\Rule;
|
||||
@@ -190,8 +192,8 @@ Response::setModel(new BaseList('Site Templates List', Response::MODEL_TEMPLATE_
|
||||
Response::setModel(new BaseList('Functions List', Response::MODEL_FUNCTION_LIST, 'functions', Response::MODEL_FUNCTION));
|
||||
Response::setModel(new BaseList('Function Templates List', Response::MODEL_TEMPLATE_FUNCTION_LIST, 'templates', Response::MODEL_TEMPLATE_FUNCTION));
|
||||
Response::setModel(new BaseList('Installations List', Response::MODEL_INSTALLATION_LIST, 'installations', Response::MODEL_INSTALLATION));
|
||||
Response::setModel(new BaseList('Framework Provider Repositories List', Response::MODEL_PROVIDER_REPOSITORY_FRAMEWORK_LIST, 'frameworkProviderRepositories', Response::MODEL_PROVIDER_REPOSITORY_FRAMEWORK));
|
||||
Response::setModel(new BaseList('Runtime Provider Repositories List', Response::MODEL_PROVIDER_REPOSITORY_RUNTIME_LIST, 'runtimeProviderRepositories', Response::MODEL_PROVIDER_REPOSITORY_RUNTIME));
|
||||
Response::setModel(new ProviderRepositoryFrameworkList());
|
||||
Response::setModel(new ProviderRepositoryRuntimeList());
|
||||
Response::setModel(new BaseList('Branches List', Response::MODEL_BRANCH_LIST, 'branches', Response::MODEL_BRANCH));
|
||||
Response::setModel(new BaseList('Frameworks List', Response::MODEL_FRAMEWORK_LIST, 'frameworks', Response::MODEL_FRAMEWORK));
|
||||
Response::setModel(new BaseList('Runtimes List', Response::MODEL_RUNTIME_LIST, 'runtimes', Response::MODEL_RUNTIME));
|
||||
|
||||
+1
@@ -307,6 +307,7 @@ class Create extends Action
|
||||
];
|
||||
}
|
||||
|
||||
$output->setAttribute('type', $type);
|
||||
$output->setAttribute('variables', $variables);
|
||||
|
||||
$response->dynamic($output, $type === 'framework' ? Response::MODEL_DETECTION_FRAMEWORK : Response::MODEL_DETECTION_RUNTIME);
|
||||
|
||||
@@ -313,6 +313,7 @@ class XList extends Action
|
||||
}, $repos);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'type' => $type,
|
||||
$type === 'framework' ? 'frameworkProviderRepositories' : 'runtimeProviderRepositories' => $repos,
|
||||
'total' => $total,
|
||||
]), ($type === 'framework') ? Response::MODEL_PROVIDER_REPOSITORY_FRAMEWORK_LIST : Response::MODEL_PROVIDER_REPOSITORY_RUNTIME_LIST);
|
||||
|
||||
@@ -263,6 +263,117 @@ abstract class Format
|
||||
return $contents;
|
||||
}
|
||||
|
||||
protected function getRegisteredModel(string $type): Model
|
||||
{
|
||||
foreach ($this->models as $model) {
|
||||
if ($model->getType() === $type) {
|
||||
return $model;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \RuntimeException("Unresolved model '{$type}'. Ensure the model is registered.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Model> $models
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
protected function getUnionDiscriminator(array $models, string $refPrefix): ?array
|
||||
{
|
||||
if (\count($models) < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$candidateKeys = null;
|
||||
|
||||
foreach ($models as $model) {
|
||||
$keys = [];
|
||||
|
||||
foreach ($model->conditions as $key => $condition) {
|
||||
if ($this->isDiscriminatorConditionSupported($condition)) {
|
||||
$keys[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
$candidateKeys = $candidateKeys === null
|
||||
? $keys
|
||||
: \array_values(\array_intersect($candidateKeys, $keys));
|
||||
}
|
||||
|
||||
if (empty($candidateKeys)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($candidateKeys as $key) {
|
||||
$mapping = [];
|
||||
$matchedModels = [];
|
||||
|
||||
foreach ($models as $model) {
|
||||
$rules = $model->getRules();
|
||||
|
||||
if (!isset($rules[$key]) || ($rules[$key]['required'] ?? false) !== true) {
|
||||
continue 2;
|
||||
}
|
||||
|
||||
$condition = $model->conditions[$key];
|
||||
$values = \is_array($condition) ? $condition : [$condition];
|
||||
|
||||
if (isset($rules[$key]['enum']) && \is_array($rules[$key]['enum'])) {
|
||||
$values = \array_values(\array_filter(
|
||||
$values,
|
||||
fn (mixed $value) => \in_array($value, $rules[$key]['enum'], true)
|
||||
));
|
||||
}
|
||||
|
||||
if ($values === []) {
|
||||
continue 2;
|
||||
}
|
||||
|
||||
foreach ($values as $value) {
|
||||
$mappingKey = \is_bool($value) ? ($value ? 'true' : 'false') : (string) $value;
|
||||
|
||||
if (isset($mapping[$mappingKey]) && $mapping[$mappingKey] !== $refPrefix . $model->getType()) {
|
||||
continue 2;
|
||||
}
|
||||
|
||||
$mapping[$mappingKey] = $refPrefix . $model->getType();
|
||||
}
|
||||
|
||||
$matchedModels[$model->getType()] = true;
|
||||
}
|
||||
|
||||
if (\count($matchedModels) !== \count($models)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return [
|
||||
'propertyName' => $key,
|
||||
'mapping' => $mapping,
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function isDiscriminatorConditionSupported(mixed $condition): bool
|
||||
{
|
||||
if (\is_scalar($condition) || \is_bool($condition)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!\is_array($condition) || $condition === []) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($condition as $value) {
|
||||
if (!(\is_scalar($value) || \is_bool($value))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getRequestEnumName(string $service, string $method, string $param): ?string
|
||||
{
|
||||
/* `$service` is `$namespace` */
|
||||
|
||||
@@ -316,9 +316,10 @@ class OpenAPI3 extends Format
|
||||
'description' => $modelDescription,
|
||||
'content' => [
|
||||
$produces => [
|
||||
'schema' => [
|
||||
'oneOf' => \array_map(fn ($m) => ['$ref' => '#/components/schemas/' . $m->getType()], $model)
|
||||
],
|
||||
'schema' => \array_filter([
|
||||
'oneOf' => \array_map(fn ($m) => ['$ref' => '#/components/schemas/' . $m->getType()], $model),
|
||||
'discriminator' => $this->getUnionDiscriminator($model, '#/components/schemas/'),
|
||||
]),
|
||||
],
|
||||
],
|
||||
];
|
||||
@@ -901,17 +902,25 @@ class OpenAPI3 extends Format
|
||||
|
||||
if (\is_array($rule['type'])) {
|
||||
if ($rule['array']) {
|
||||
$items = [
|
||||
$items = \array_filter([
|
||||
'anyOf' => \array_map(function ($type) {
|
||||
return ['$ref' => '#/components/schemas/' . $type];
|
||||
}, $rule['type'])
|
||||
];
|
||||
}, $rule['type']),
|
||||
'discriminator' => $this->getUnionDiscriminator(
|
||||
\array_map(fn (string $type) => $this->getRegisteredModel($type), $rule['type']),
|
||||
'#/components/schemas/'
|
||||
),
|
||||
]);
|
||||
} else {
|
||||
$items = [
|
||||
$items = \array_filter([
|
||||
'oneOf' => \array_map(function ($type) {
|
||||
return ['$ref' => '#/components/schemas/' . $type];
|
||||
}, $rule['type'])
|
||||
];
|
||||
}, $rule['type']),
|
||||
'discriminator' => $this->getUnionDiscriminator(
|
||||
\array_map(fn (string $type) => $this->getRegisteredModel($type), $rule['type']),
|
||||
'#/components/schemas/'
|
||||
),
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$items = [
|
||||
|
||||
@@ -322,11 +322,12 @@ class Swagger2 extends Format
|
||||
}
|
||||
$temp['responses'][(string)$response->getCode() ?? '500'] = [
|
||||
'description' => $modelDescription,
|
||||
'schema' => [
|
||||
'schema' => \array_filter([
|
||||
'x-oneOf' => \array_map(function ($m) {
|
||||
return ['$ref' => '#/definitions/' . $m->getType()];
|
||||
}, $model)
|
||||
],
|
||||
}, $model),
|
||||
'x-discriminator' => $this->getUnionDiscriminator($model, '#/definitions/'),
|
||||
]),
|
||||
];
|
||||
} else {
|
||||
// Response definition using one type
|
||||
@@ -881,13 +882,21 @@ class Swagger2 extends Format
|
||||
|
||||
if (\is_array($rule['type'])) {
|
||||
if ($rule['array']) {
|
||||
$items = [
|
||||
'x-anyOf' => \array_map(fn ($type) => ['$ref' => '#/definitions/' . $type], $rule['type'])
|
||||
];
|
||||
$items = \array_filter([
|
||||
'x-anyOf' => \array_map(fn ($type) => ['$ref' => '#/definitions/' . $type], $rule['type']),
|
||||
'x-discriminator' => $this->getUnionDiscriminator(
|
||||
\array_map(fn (string $type) => $this->getRegisteredModel($type), $rule['type']),
|
||||
'#/definitions/'
|
||||
),
|
||||
]);
|
||||
} else {
|
||||
$items = [
|
||||
'x-oneOf' => \array_map(fn ($type) => ['$ref' => '#/definitions/' . $type], $rule['type'])
|
||||
];
|
||||
$items = \array_filter([
|
||||
'x-oneOf' => \array_map(fn ($type) => ['$ref' => '#/definitions/' . $type], $rule['type']),
|
||||
'x-discriminator' => $this->getUnionDiscriminator(
|
||||
\array_map(fn (string $type) => $this->getRegisteredModel($type), $rule['type']),
|
||||
'#/definitions/'
|
||||
),
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$items = [
|
||||
|
||||
@@ -7,9 +7,16 @@ use Appwrite\Utopia\Response\Model;
|
||||
|
||||
abstract class Detection extends Model
|
||||
{
|
||||
public function __construct()
|
||||
public function __construct(string $type)
|
||||
{
|
||||
$this
|
||||
->addRule('type', [
|
||||
'type' => self::TYPE_ENUM,
|
||||
'description' => 'Repository detection type.',
|
||||
'default' => $type,
|
||||
'example' => $type,
|
||||
'enum' => ['runtime', 'framework'],
|
||||
])
|
||||
->addRule('variables', [
|
||||
'type' => Response::MODEL_DETECTION_VARIABLE,
|
||||
'description' => 'Environment variables found in .env files',
|
||||
|
||||
@@ -8,7 +8,11 @@ class DetectionFramework extends Detection
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->conditions = [
|
||||
'type' => 'framework',
|
||||
];
|
||||
|
||||
parent::__construct('framework');
|
||||
|
||||
$this
|
||||
->addRule('framework', [
|
||||
|
||||
@@ -8,7 +8,11 @@ class DetectionRuntime extends Detection
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->conditions = [
|
||||
'type' => 'runtime',
|
||||
];
|
||||
|
||||
parent::__construct('runtime');
|
||||
|
||||
$this
|
||||
->addRule('runtime', [
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
|
||||
class ProviderRepositoryFrameworkList extends BaseList
|
||||
{
|
||||
public array $conditions = [
|
||||
'type' => 'framework',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(
|
||||
'Framework Provider Repositories List',
|
||||
Response::MODEL_PROVIDER_REPOSITORY_FRAMEWORK_LIST,
|
||||
'frameworkProviderRepositories',
|
||||
Response::MODEL_PROVIDER_REPOSITORY_FRAMEWORK
|
||||
);
|
||||
|
||||
$this->addRule('type', [
|
||||
'type' => self::TYPE_ENUM,
|
||||
'description' => 'Repository detection type.',
|
||||
'default' => 'framework',
|
||||
'example' => 'framework',
|
||||
'enum' => ['runtime', 'framework'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
|
||||
class ProviderRepositoryRuntimeList extends BaseList
|
||||
{
|
||||
public array $conditions = [
|
||||
'type' => 'runtime',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(
|
||||
'Runtime Provider Repositories List',
|
||||
Response::MODEL_PROVIDER_REPOSITORY_RUNTIME_LIST,
|
||||
'runtimeProviderRepositories',
|
||||
Response::MODEL_PROVIDER_REPOSITORY_RUNTIME
|
||||
);
|
||||
|
||||
$this->addRule('type', [
|
||||
'type' => self::TYPE_ENUM,
|
||||
'description' => 'Repository detection type.',
|
||||
'default' => 'runtime',
|
||||
'example' => 'runtime',
|
||||
'enum' => ['runtime', 'framework'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user