api-platform / api-platform/core

OpenAPI: Support for JSON-LD Only APIs

未關閉
#7,803 1 則留言 2 個 reaction 已指派 0 人 在 GitHub 檢視
OpenAPI RFC
主要語言
PHP
星號
2.6k
分支
980
平均合併
2 天 5 小時
30 天內合併 PR
48

描述

## Context

When building a JSON-LD only API (no plain JSON format), the OpenAPI specification still generates duplicate schemas and content types. Users have to create custom OpenApiFactory decorators to filter these out.

## Current Behavior

With this configuration:

```yaml
api_platform:
formats:
jsonld: ['application/ld+json']
error_formats:
jsonld: ['application/ld+json']
jsonproblem: ['application/problem+json']
```

The OpenAPI export still produces:

1. **Duplicate schemas**: `EmailMessage` AND `EmailMessage.jsonld`
2. **Multiple content types in responses**: `application/ld+json`, `application/problem+json`, `application/json`

## Workaround We Implemented

We created an `OpenApiFactory` decorator that:

### 1. Filters non-JSON-LD content types from responses

```php
private const NON_JSONLD_MIME_TYPES = [
'text/html',
'application/problem+json',
'application/json',
];

private function filterNonJsonContentTypes(PathItem $pathItem): PathItem
{
foreach (PathItem::$methods as $method) {
$operation = $pathItem->{"get" . ucfirst(strtolower($method))}();
if (null === $operation) continue;

foreach ($operation->getResponses() as $status => $response) {
$content = $response->getContent();
if ($content) {
$filteredContent = new \ArrayObject();
foreach ($content as $mimeType => $mediaType) {
if (!in_array($mimeType, self::NON_JSONLD_MIME_TYPES, true)) {
$filteredContent[$mimeType] = $mediaType;
}
}
// ... update response
}
}
}
return $pathItem;
}
```

### 2. Removes duplicate schemas (keeps only .jsonld versions)

```php
private function filterNonJsonSchemas(OpenApi $openApi): OpenApi
{
$schemas = $openApi->getComponents()->getSchemas();

// Collect base names that have .jsonld versions
$jsonldSchemas = [];
foreach ($schemas as $name => $schema) {
if (str_ends_with($name, '.jsonld')) {
$jsonldSchemas[substr($name, 0, -7)] = true;
}
}

$filteredSchemas = new \ArrayObject();
foreach ($schemas as $name => $schema) {
$baseName = preg_replace('/\.jsonld$/', '', $name);

// Skip non-.jsonld version when .jsonld exists
if (!str_ends_with($name, '.jsonld') && isset($jsonldSchemas[$baseName])) {
continue;
}

$filteredSchemas[$name] = $schema;
}

return $openApi->withComponents($components->withSchemas($filteredSchemas));
}
```

## Proposed Framework Enhancement

### Option A: `openapi.prefer_format` Configuration

Add a configuration option to specify the preferred format for OpenAPI documentation:

```yaml
api_platform:
openapi:
prefer_format: jsonld # Only show jsonld schemas and content types
```

**Implementation:**

In `OpenApiFactory`, when `prefer_format` is set:

1. Only generate schemas for that format (skip others even if configured)
2. Only include that format's content type in responses
3. Remove duplicate schemas that exist in multiple format variants

### Option B: `openapi.formats` Configuration

Allow explicit control over which formats appear in OpenAPI (separate from runtime formats):

```yaml
api_platform:
formats:
jsonld: ['application/ld+json']
json: ['application/json'] # Available at runtime
openapi:
formats:
jsonld: ['application/ld+json'] # Only this in docs
```

**Implementation:**

In `OpenApiFactory::getMimeTypes()`:

```php
private function getMimeTypes(HttpOperation $operation): array
{
// Use openapi.formats if configured, otherwise fall back to operation formats
$responseFormats = $this->openapiFormats
?? $operation->getOutputFormats()
?? [];

// ... rest of method
}
```

### Option C: Automatic Deduplication

When multiple formats produce equivalent schemas (same structure, different naming), automatically deduplicate:

```php
// In OpenApiFactory::collectPaths() or a new dedicated method
private function deduplicateSchemas(\ArrayObject $schemas): \ArrayObject
{
$dominated = [];

// jsonld dominates json (more specific)
foreach ($schemas as $name => $schema) {
if (str_ends_with($name, '.jsonld')) {
$baseName = substr($name, 0, -7);
if (isset($schemas[$baseName])) {
$dominated[$baseName] = true;
}
}
}

// Remove dominated schemas
foreach ($dominated as $name => $_) {
unset($schemas[$name]);
}

return $schemas;
}
```

## Recommendation

**Option A (`prefer_format`) is the cleanest** because:

1. Simple, single configuration option
2. Clear intent: "I want my OpenAPI docs in this format"
3. Doesn't affect runtime behavior
4. Easy to understand and document

**Example implementation location:**

- Configuration: `src/Symfony/Bundle/DependencyInjection/Configuration.php`
- Logic: `src/OpenApi/Factory/OpenApiFactory.php`

## Additional Consideration: Accept/Content-Type Headers

Our decorator also adds `Accept` and `Content-Type` header parameters to all operations, showing available formats as an enum. This is useful documentation that API Platform could generate automatically.

```yaml
parameters:
- name: Accept
in: header
schema:
type: string
enum: ['application/ld+json']
default: 'application/ld+json'
```

This could be another configuration option:

```yaml
api_platform:
openapi:
document_content_negotiation_headers: true
```

貢獻指南

開啟貢獻指南

評估

這個 Issue 還沒有評估資料。

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。