api-platform / api-platform/core

OpenAPI: Non-JSON formats should not generate JSON schemas

Open
#7,802 0 comments 1 reaction 0 assignees View on GitHub
Dominant language
PHP
Stars
2.6k
Forks
980
Avg merge
2d 5h
Merged PRs (30d)
48

Description

## Issue Description

When an API Platform operation defines non-JSON output formats (e.g., `text/html`, `text/xml`), the OpenAPI factory generates JSON schemas for these formats, which is semantically incorrect.

### Example

```php
#[ApiResource(
operations: [
new Get(
uriTemplate: '/unsubscribe/{token}',
formats: [
'jsonld' => ['application/ld+json'],
'json' => ['application/json'],
'html' => ['text/html'], // This causes the issue
],
),
],
)]
class UnsubscribedEmail {}
```

### Current Behavior

The OpenAPI export generates:

```yaml
paths:
/api/unsubscribe/{token}:
get:
responses:
'200':
content:
application/ld+json:
schema:
$ref: '#/components/schemas/UnsubscribedEmail.jsonld'
application/json:
schema:
$ref: '#/components/schemas/UnsubscribedEmail'
text/html:
schema:
$ref: '#/components/schemas/UnsubscribedEmail.html' # Makes no sense

components:
schemas:
UnsubscribedEmail.html: # JSON schema for HTML format - meaningless
type: object
properties:
status:
type: string
```

### Expected Behavior

Non-JSON formats should either:
1. Not have a `schema` reference in OpenAPI
2. Or be excluded from the OpenAPI spec entirely (content negotiation still works at runtime)

## Root Cause

In `src/OpenApi/Factory/OpenApiFactory.php`, the `getMimeTypes()` method returns ALL output formats, and the schema generation loop creates schemas for each:

```php
// Lines 267-273
foreach ($responseMimeTypes as $operationFormat) {
$operationOutputSchema = $this->jsonSchemaFactory->buildSchema(
$resourceClass,
$operationFormat, // <-- Includes 'html', 'xml', etc.
Schema::TYPE_OUTPUT,
$operation,
$schema,
null,
$forceSchemaCollection
);
$operationOutputSchemas[$operationFormat] = $operationOutputSchema;
}
```

## Proposed Fix

Use the existing `jsonschema_formats` configuration parameter to filter which formats get JSON schemas in OpenAPI.

### Step 1: Inject `jsonschema_formats` into OpenApiFactory

In `src/Symfony/Bundle/Resources/config/openapi.php`:

```php
$services->set('api_platform.openapi.factory', OpenApiFactory::class)
->args([
// ... existing args ...
'%api_platform.jsonschema_formats%', // Add this parameter
]);
```

### Step 2: Update OpenApiFactory constructor

```php
public function __construct(
// ... existing parameters ...
private readonly array $jsonSchemaFormats = [],
) {
// ...
}
```

### Step 3: Filter formats in schema generation loop

```php
// Around line 267
foreach ($responseMimeTypes as $mimeType => $operationFormat) {
// Skip formats that don't have JSON schema support
if (!isset($this->jsonSchemaFormats[$operationFormat])) {
continue;
}

$operationOutputSchema = $this->jsonSchemaFactory->buildSchema(
$resourceClass,
$operationFormat,
Schema::TYPE_OUTPUT,
$operation,
$schema,
null,
$forceSchemaCollection
);
$operationOutputSchemas[$operationFormat] = $operationOutputSchema;
}
```

### Step 4: Update buildContent to handle missing schemas

```php
private function buildContent(array $responseMimeTypes, array $operationSchemas): \ArrayObject
{
$content = new \ArrayObject();

foreach ($responseMimeTypes as $mimeType => $format) {
// Only add content entry if we have a schema for this format
if (isset($operationSchemas[$format])) {
$content[$mimeType] = new MediaType(
schema: new \ArrayObject($operationSchemas[$format]->getArrayCopy(false))
);
}
// Non-JSON formats without schemas are simply not included in OpenAPI content
}

return $content;
}
```

## Alternative Consideration

The `buildContent` method could instead add the content type without a schema reference:

```php
if (isset($operationSchemas[$format])) {
$content[$mimeType] = new MediaType(schema: new \ArrayObject($operationSchemas[$format]->getArrayCopy(false)));
} else {
// Include the content type but without a schema (valid in OpenAPI 3.1)
$content[$mimeType] = new MediaType();
}
```

This preserves the information that the endpoint supports HTML responses, just without a JSON schema.

## Testing

Add a test case to `tests/OpenApi/Factory/OpenApiFactoryTest.php`:

```php
public function testNonJsonFormatsDoNotGenerateSchemas(): void
{
// Create a resource with html format
// Assert that UnsubscribedEmail.html schema does not exist
// Assert that text/html content type either has no schema or is not present
}
```

## Configuration Reference

The `jsonschema_formats` is already computed in `ApiPlatformExtension.php`:

```php
$jsonSchemaFormats = $config['jsonschema_formats'];

if (!$jsonSchemaFormats) {
foreach (array_merge(array_keys($formats), array_keys($errorFormats)) as $f) {
// Only JSON-based formats get schemas by default
if (str_starts_with($f, 'json')) {
$jsonSchemaFormats[$f] = true;
}
}
}
```

This logic already correctly identifies JSON formats - it just needs to be used in OpenApiFactory.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.