Skip to content

Commit

Permalink
refactor: Put non-atomic parameters into body
Browse files Browse the repository at this point in the history
Signed-off-by: provokateurin <[email protected]>
  • Loading branch information
provokateurin committed Mar 15, 2024
1 parent cd537bc commit 0717ced
Show file tree
Hide file tree
Showing 4 changed files with 433 additions and 290 deletions.
90 changes: 68 additions & 22 deletions generate-spec
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ $command
->option('--no-tags', 'Use no tags')
->option('--continue-on-error', 'Continue on error')
->option('--openapi-version', 'OpenAPI version to use', null, '3.0.3')
->option('--parameters-to-body', 'Put all parameters into the JSON body')
->option('--verbose', 'Verbose logging')
->parse($_SERVER["argv"]);

Expand All @@ -55,6 +56,7 @@ $useTags = $command->tags ?? true;
Logger::$exitOnError = !($command->continueOnError ?? false);
Logger::$verbose = $command->verbose ?? false;
$openapiVersion = $command->openapiVersion ?? '3.0.3';
$parametersToBody = $command->parametersToBody ?? false;

if ($dir == "") {
$dir = ".";
Expand Down Expand Up @@ -151,7 +153,7 @@ if (file_exists($definitionsPath)) {
}
}
foreach (array_keys($definitions) as $name) {
$schemas[Helpers::cleanSchemaName($name)] = OpenApiType::resolve("Response definitions", $definitions, $definitions[$name])->toArray($openapiVersion);
$schemas[Helpers::cleanSchemaName($name)] = OpenApiType::resolve("Response definitions", $definitions, $definitions[$name])->toArray();
}
} else {
Logger::debug("Response definitions", "No response definitions were loaded");
Expand Down Expand Up @@ -212,7 +214,7 @@ foreach ($capabilitiesFiles as $path) {
continue;
}

$schema = $type->toArray($openapiVersion);
$schema = $type->toArray();

if ($implementsPublicCapability) {
$publicCapabilities = $publicCapabilities == null ? $schema : Helpers::mergeSchemas([$publicCapabilities, $schema]);
Expand Down Expand Up @@ -574,7 +576,7 @@ foreach ($routes as $scope => $scopeRoutes) {
continue;
}

$schema = $parameter->type->toArray($openapiVersion, true);
$schema = $parameter->type->toArray(true);
$description = $parameter?->docType != null && $parameter->docType->description != "" ? Helpers::cleanDocComment($parameter->docType->description) : null;
} else {
$schema = [
Expand Down Expand Up @@ -667,19 +669,19 @@ foreach ($routes as $scope => $scopeRoutes) {
$contentTypeResponses = array_values(array_filter($statusCodeResponses, fn (ControllerMethodResponse $response) => $response->contentType == $contentType));

$hasEmpty = count(array_filter($contentTypeResponses, fn (ControllerMethodResponse $response) => $response->type == null)) > 0;
$uniqueResponses = array_values(array_intersect_key($contentTypeResponses, array_unique(array_map(fn (ControllerMethodResponse $response) => $response->type->toArray($openapiVersion), array_filter($contentTypeResponses, fn (ControllerMethodResponse $response) => $response->type != null)), SORT_REGULAR)));
$uniqueResponses = array_values(array_intersect_key($contentTypeResponses, array_unique(array_map(fn (ControllerMethodResponse $response) => $response->type->toArray(), array_filter($contentTypeResponses, fn (ControllerMethodResponse $response) => $response->type != null)), SORT_REGULAR)));
if (count($uniqueResponses) == 1) {
if ($hasEmpty) {
$mergedContentTypeResponses[$contentType] = [];
} else {
$schema = Helpers::cleanEmptyResponseArray($contentTypeResponses[0]->type->toArray($openapiVersion));
$schema = Helpers::cleanEmptyResponseArray($contentTypeResponses[0]->type->toArray());
$mergedContentTypeResponses[$contentType] = ["schema" => Helpers::wrapOCSResponse($route, $contentTypeResponses[0], $schema)];
}
} else {
$mergedContentTypeResponses[$contentType] = [
"schema" => [
[$hasEmpty ? "anyOf" : "oneOf" => array_map(function (ControllerMethodResponse $response) use ($route, $openapiVersion) {
$schema = Helpers::cleanEmptyResponseArray($response->type->toArray($openapiVersion));
[$hasEmpty ? "anyOf" : "oneOf" => array_map(function (ControllerMethodResponse $response) use ($route) {
$schema = Helpers::cleanEmptyResponseArray($response->type->toArray());
return Helpers::wrapOCSResponse($route, $response, $schema);
}, $uniqueResponses)],
],
Expand All @@ -695,7 +697,7 @@ foreach ($routes as $scope => $scopeRoutes) {
array_keys($headers),
array_map(
fn (OpenApiType $type) => [
"schema" => $type->toArray($openapiVersion),
"schema" => $type->toArray(),
],
array_values($headers),
),
Expand Down Expand Up @@ -745,24 +747,65 @@ foreach ($routes as $scope => $scopeRoutes) {
$operation["security"] = $security;
}
if (count($queryParameters) > 0 || count($pathParameters) > 0 || $route->isOCS) {
$parameters = [
...array_map(static function (ControllerMethodParameter $parameter) use ($openapiVersion) {
$out = [
"name" => $parameter->name . ($parameter->type->type === "array" ? "[]" : ""),
$requiredBodyParameters = [];
$bodyParameters = [];
$parameters = [];

foreach ($queryParameters as $queryParameter) {
// Non-atomic types have to be put into the body as the serialization is too complex for query parameters
// Booleans also have to be in the body because for query parameters 0/1 would have to be used which is not ergonomic.
$isBodyParameter = $parametersToBody ||
$queryParameter->type->type === "boolean" ||
$queryParameter->type->type === "object" ||
$queryParameter->type->type === "array" ||
$queryParameter->type->ref !== null ||
$queryParameter->type->anyOf !== null ||
$queryParameter->type->allOf !== null;
$required = !$queryParameter->type->nullable && !$queryParameter->type->hasDefaultValue;

if ($isBodyParameter) {
$bodyParameters[$queryParameter->name] = $queryParameter->type->toArray();
if ($required) {
$requiredBodyParameters[] = $queryParameter->name;
}
} else {
$parameter = [
"name" => $queryParameter->name,
"in" => "query",
];
if ($parameter->docType !== null && $parameter->docType->description !== "") {
$out["description"] = Helpers::cleanDocComment($parameter->docType->description);
if ($queryParameter->docType !== null && $queryParameter->docType->description !== "") {
$parameter["description"] = Helpers::cleanDocComment($queryParameter->docType->description);
}
if (!$parameter->type->nullable && !$parameter->type->hasDefaultValue) {
$out["required"] = true;
if ($required) {
$parameter["required"] = true;
}
$out["schema"] = $parameter->type->toArray($openapiVersion, true);
$parameter["schema"] = $queryParameter->type->toArray(true);
$parameters[] = $parameter;
}
}

return $out;
}, $queryParameters),
...$pathParameters,
];
if (count($bodyParameters) > 0) {
$required = count($requiredBodyParameters) > 0;

$schema = [
"type" => "object",
];
if ($required) {
$schema["required"] = $requiredBodyParameters;
}
$schema["properties"] = $bodyParameters;

$operation["requestBody"] = [
"required" => $required,
"content" => [
"application/json" => [
"schema" => $schema,
],
],
];
}

$parameters = array_merge($parameters, $pathParameters);
if ($route->isOCS) {
$parameters[] = [
"name" => "OCS-APIRequest",
Expand All @@ -775,7 +818,10 @@ foreach ($routes as $scope => $scopeRoutes) {
],
];
}
$operation["parameters"] = $parameters;

if (count($parameters) > 0) {
$operation["parameters"] = $parameters;
}
}
$operation["responses"] = $mergedResponses;

Expand Down
57 changes: 13 additions & 44 deletions src/OpenApiType.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,62 +51,31 @@ public function __construct(
) {
}

public function toArray(string $openapiVersion, bool $isParameter = false): array|stdClass {
$asContentString = $isParameter && (
$this->type == "object" ||
$this->ref !== null ||
$this->anyOf !== null ||
$this->allOf !== null);
if ($asContentString) {
$values = [
"type" => "string",
];
if ($this->nullable) {
$values["nullable"] = true;
}
if (version_compare($openapiVersion, "3.1.0", ">=")) {
$values["contentMediaType"] = "application/json";
$values["contentSchema"] = $this->toArray($openapiVersion);
}

return $values;
}

$type = $this->type;
$defaultValue = $this->defaultValue;
$enum = $this->enum;
if ($isParameter && $type == "boolean") {
$type = "integer";
$enum = [0, 1];
if ($this->hasDefaultValue) {
$defaultValue = $defaultValue === true ? 1 : 0;
}
}

public function toArray(bool $isParameter = false): array|stdClass {
$values = [];
if ($this->ref !== null) {
$values["\$ref"] = $this->ref;
}
if ($type !== null) {
$values["type"] = $type;
if ($this->type !== null) {
$values["type"] = $this->type;
}
if ($this->format !== null) {
$values["format"] = $this->format;
}
if ($this->nullable) {
$values["nullable"] = true;
}
if ($this->hasDefaultValue && $defaultValue !== null) {
$values["default"] = $defaultValue;
if ($this->hasDefaultValue && $this->defaultValue !== null) {
$values["default"] = $this->defaultValue;
}
if ($enum !== null) {
$values["enum"] = $enum;
if ($this->enum !== null) {
$values["enum"] = $this->enum;
}
if ($this->description !== null && $this->description !== "" && !$isParameter) {
$values["description"] = $this->description;
}
if ($this->items !== null) {
$values["items"] = $this->items->toArray($openapiVersion);
$values["items"] = $this->items->toArray();
}
if ($this->minLength !== null) {
$values["minLength"] = $this->minLength;
Expand All @@ -125,24 +94,24 @@ public function toArray(string $openapiVersion, bool $isParameter = false): arra
}
if ($this->properties !== null && count($this->properties) > 0) {
$values["properties"] = array_combine(array_keys($this->properties),
array_map(static fn (OpenApiType $property) => $property->toArray($openapiVersion), array_values($this->properties)),
array_map(static fn (OpenApiType $property) => $property->toArray(), array_values($this->properties)),
);
}
if ($this->additionalProperties !== null) {
if ($this->additionalProperties instanceof OpenApiType) {
$values["additionalProperties"] = $this->additionalProperties->toArray($openapiVersion);
$values["additionalProperties"] = $this->additionalProperties->toArray();
} else {
$values["additionalProperties"] = $this->additionalProperties;
}
}
if ($this->oneOf !== null) {
$values["oneOf"] = array_map(fn (OpenApiType $type) => $type->toArray($openapiVersion), $this->oneOf);
$values["oneOf"] = array_map(fn (OpenApiType $type) => $type->toArray(), $this->oneOf);
}
if ($this->anyOf !== null) {
$values["anyOf"] = array_map(fn (OpenApiType $type) => $type->toArray($openapiVersion), $this->anyOf);
$values["anyOf"] = array_map(fn (OpenApiType $type) => $type->toArray(), $this->anyOf);
}
if ($this->allOf !== null) {
$values["allOf"] = array_map(fn (OpenApiType $type) => $type->toArray($openapiVersion), $this->allOf);
$values["allOf"] = array_map(fn (OpenApiType $type) => $type->toArray(), $this->allOf);
}

return count($values) > 0 ? $values : new stdClass();
Expand Down
Loading

0 comments on commit 0717ced

Please sign in to comment.