Skip to content

Commit

Permalink
Improve OpenAPI spec empty checks (#712)
Browse files Browse the repository at this point in the history
* add empty check for properties

* Update src/Writing/OpenAPISpecWriter.php

---------

Co-authored-by: Shalvah <shalvah@users.noreply.github.com>
  • Loading branch information
rpmccarter and shalvah authored Aug 19, 2023
1 parent 4317c23 commit 1f94ac5
Showing 1 changed file with 24 additions and 23 deletions.
47 changes: 24 additions & 23 deletions src/Writing/OpenAPISpecWriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,9 @@ class OpenAPISpecWriter

private DocumentationConfig $config;

/**
* Object to represent empty values, since empty arrays get serialised as objects.
* Can't use a constant because of initialisation expression.
*
*/
public \stdClass $EMPTY;

public function __construct(DocumentationConfig $config = null)
{
$this->config = $config ?: new DocumentationConfig(config('scribe', []));
$this->EMPTY = new \stdClass();
}

/**
Expand Down Expand Up @@ -241,6 +233,10 @@ protected function generateEndpointRequestBodySpec(OutputEndpointData $endpoint)
$schema['properties'][$name] = $fieldData;
}

// We remove 'properties' if the request body is an array, so we need to check if it's still there
if (array_key_exists('properties', $schema)) {
$schema['properties'] = $this->objectIfEmpty($schema['properties']);
}
$body['required'] = $hasRequiredParameter;

if ($hasFileParameter) {
Expand All @@ -257,7 +253,7 @@ protected function generateEndpointRequestBodySpec(OutputEndpointData $endpoint)
}

// return object rather than empty array, so can get properly serialised as object
return count($body) > 0 ? $body : $this->EMPTY;
return $this->objectIfEmpty($body);
}

protected function generateEndpointResponsesSpec(OutputEndpointData $endpoint)
Expand All @@ -282,7 +278,7 @@ protected function generateEndpointResponsesSpec(OutputEndpointData $endpoint)
}

// return object rather than empty array, so can get properly serialised as object
return count($responses) > 0 ? $responses : $this->EMPTY;
return $this->objectIfEmpty($responses);
}

protected function getResponseDescription(Response $response): string
Expand Down Expand Up @@ -386,16 +382,12 @@ protected function generateResponseContentSpec(?string $responseContent, OutputE
return [$key => $this->generateSchemaForValue($value, $endpoint, $key)];
})->toArray();

if (!count($properties)) {
$properties = $this->EMPTY;
}

return [
'application/json' => [
'schema' => [
'type' => 'object',
'example' => $decoded,
'properties' => $properties,
'properties' => $this->objectIfEmpty($properties),
],
],
];
Expand Down Expand Up @@ -513,9 +505,9 @@ public function generateFieldData($field): array
'type' => 'object',
'description' => $field->description ?: '',
'example' => $field->example,
'properties' => collect($field->__fields)->mapWithKeys(function ($subfield, $subfieldName) {
'properties' => $this->objectIfEmpty(collect($field->__fields)->mapWithKeys(function ($subfield, $subfieldName) {
return [$subfieldName => $this->generateFieldData($subfield)];
})->all(),
})->all()),
];
} else {
return [
Expand All @@ -534,6 +526,15 @@ protected function operationId(OutputEndpointData $endpoint): string
return Str::lower($endpoint->httpMethods[0]) . join('', array_map(fn ($part) => ucfirst($part), $parts));
}

/**
* Given an array, return an object if the array is empty. To be used with fields that are
* required by OpenAPI spec to be objects, since empty arrays get serialised as [].
*/
protected function objectIfEmpty(array $field): array | \stdClass
{
return count($field) > 0 ? $field : new \stdClass();
}

/**
* Given a value, generate the schema for it. The schema consists of: {type:, example:, properties: (if value is an object)},
* and possibly a description for each property.
Expand All @@ -543,17 +544,17 @@ public function generateSchemaForValue(mixed $value, OutputEndpointData $endpoin
{
if ($value instanceof \stdClass) {
$value = (array) $value;
$schema = [
'type' => 'object',
'properties' => [],
];
$properties = [];
// Recurse into the object
foreach($value as $subField => $subValue){
$subFieldPath = sprintf('%s.%s', $path, $subField);
$schema['properties'][$subField] = $this->generateSchemaForValue($subValue, $endpoint, $subFieldPath);
$properties[$subField] = $this->generateSchemaForValue($subValue, $endpoint, $subFieldPath);
}

return $schema;
return [
'type' => 'object',
'properties' => $this->objectIfEmpty($properties),
];
}

$schema = [
Expand Down

0 comments on commit 1f94ac5

Please sign in to comment.