492 lines
8.8 KiB
Markdown
492 lines
8.8 KiB
Markdown
# OpenAPI 3.1 Patterns and Best Practices
|
|
|
|
Comprehensive guide to OpenAPI 3.1 specification patterns for Grey Haven stack (FastAPI + TanStack Start).
|
|
|
|
## OpenAPI 3.1 Overview
|
|
|
|
OpenAPI 3.1 is fully compatible with JSON Schema Draft 2020-12.
|
|
|
|
**Key Differences from 3.0**: Full JSON Schema compatibility, `examples` replaces `example`, `webhooks` support, better discriminator
|
|
|
|
## Basic Structure
|
|
|
|
```yaml
|
|
openapi: 3.1.0
|
|
info:
|
|
title: Grey Haven API
|
|
version: 1.0.0
|
|
|
|
servers:
|
|
- url: https://api.greyhaven.com
|
|
|
|
paths:
|
|
/users:
|
|
get:
|
|
operationId: listUsers
|
|
tags: [users]
|
|
responses:
|
|
'200':
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/User'
|
|
```
|
|
|
|
## Authentication
|
|
|
|
### JWT Bearer
|
|
|
|
```yaml
|
|
components:
|
|
securitySchemes:
|
|
BearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
bearerFormat: JWT
|
|
|
|
security:
|
|
- BearerAuth: []
|
|
```
|
|
|
|
### OAuth2
|
|
|
|
```yaml
|
|
components:
|
|
securitySchemes:
|
|
OAuth2:
|
|
type: oauth2
|
|
flows:
|
|
authorizationCode:
|
|
authorizationUrl: https://auth.greyhaven.com/oauth/authorize
|
|
tokenUrl: https://auth.greyhaven.com/oauth/token
|
|
scopes:
|
|
read:users: Read user data
|
|
```
|
|
|
|
### API Key
|
|
|
|
```yaml
|
|
components:
|
|
securitySchemes:
|
|
ApiKey:
|
|
type: apiKey
|
|
in: header
|
|
name: X-API-Key
|
|
```
|
|
|
|
## Schema Patterns
|
|
|
|
### Pydantic v2 to OpenAPI
|
|
|
|
```python
|
|
from pydantic import BaseModel, Field
|
|
from typing import Literal
|
|
|
|
class User(BaseModel):
|
|
id: str = Field(..., pattern="^usr_[a-z0-9]{16}$")
|
|
email: str = Field(..., examples=["user@example.com"])
|
|
role: Literal["admin", "member", "guest"] = "member"
|
|
```
|
|
|
|
Generates:
|
|
|
|
```yaml
|
|
User:
|
|
type: object
|
|
required: [id, email]
|
|
properties:
|
|
id:
|
|
type: string
|
|
pattern: ^usr_[a-z0-9]{16}$
|
|
email:
|
|
type: string
|
|
examples: ["user@example.com"]
|
|
role:
|
|
type: string
|
|
enum: [admin, member, guest]
|
|
default: member
|
|
```
|
|
|
|
### Nullable and Optional
|
|
|
|
```yaml
|
|
# Optional (can be omitted)
|
|
username:
|
|
type: string
|
|
|
|
# Nullable (can be null)
|
|
middle_name:
|
|
type: [string, 'null']
|
|
|
|
# Both
|
|
nickname:
|
|
type: [string, 'null']
|
|
```
|
|
|
|
### Discriminated Unions
|
|
|
|
```yaml
|
|
PaymentMethod:
|
|
type: object
|
|
required: [type]
|
|
discriminator:
|
|
propertyName: type
|
|
mapping:
|
|
card: '#/components/schemas/CardPayment'
|
|
bank: '#/components/schemas/BankPayment'
|
|
|
|
CardPayment:
|
|
allOf:
|
|
- $ref: '#/components/schemas/PaymentMethod'
|
|
- type: object
|
|
properties:
|
|
card_number:
|
|
type: string
|
|
pattern: ^\d{16}$
|
|
```
|
|
|
|
## Response Patterns
|
|
|
|
### Error Response
|
|
|
|
```yaml
|
|
ErrorResponse:
|
|
type: object
|
|
required: [error, message]
|
|
properties:
|
|
error:
|
|
type: string
|
|
examples: ["VALIDATION_ERROR"]
|
|
message:
|
|
type: string
|
|
details:
|
|
type: object
|
|
additionalProperties: true
|
|
|
|
# Use in responses
|
|
responses:
|
|
'400':
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
```
|
|
|
|
### Paginated Response
|
|
|
|
```yaml
|
|
PaginatedUsers:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/User'
|
|
pagination:
|
|
type: object
|
|
properties:
|
|
page:
|
|
type: integer
|
|
minimum: 1
|
|
per_page:
|
|
type: integer
|
|
minimum: 1
|
|
maximum: 100
|
|
total:
|
|
type: integer
|
|
total_pages:
|
|
type: integer
|
|
```
|
|
|
|
### Multiple Status Codes
|
|
|
|
```yaml
|
|
responses:
|
|
'201':
|
|
description: Created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/User'
|
|
'202':
|
|
description: Accepted (async)
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
job_id:
|
|
type: string
|
|
```
|
|
|
|
## Request Body
|
|
|
|
### Required vs Optional
|
|
|
|
```yaml
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [email, password] # Required
|
|
properties:
|
|
email:
|
|
type: string
|
|
format: email
|
|
password:
|
|
type: string
|
|
minLength: 8
|
|
name:
|
|
type: string # Optional
|
|
```
|
|
|
|
### File Upload
|
|
|
|
```yaml
|
|
requestBody:
|
|
content:
|
|
multipart/form-data:
|
|
schema:
|
|
properties:
|
|
file:
|
|
type: string
|
|
format: binary
|
|
```
|
|
|
|
## Parameters
|
|
|
|
### Path
|
|
|
|
```yaml
|
|
parameters:
|
|
- name: user_id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
pattern: ^usr_[a-z0-9]{16}$
|
|
```
|
|
|
|
### Query
|
|
|
|
```yaml
|
|
parameters:
|
|
- name: status
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: [pending, processing, shipped]
|
|
- name: created_after
|
|
in: query
|
|
schema:
|
|
type: string
|
|
format: date-time
|
|
- name: sort
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: [created_at:asc, created_at:desc]
|
|
default: created_at:desc
|
|
```
|
|
|
|
### Headers
|
|
|
|
```yaml
|
|
components:
|
|
parameters:
|
|
TenantId:
|
|
name: X-Tenant-ID
|
|
in: header
|
|
required: true
|
|
schema:
|
|
type: string
|
|
|
|
paths:
|
|
/orders:
|
|
post:
|
|
parameters:
|
|
- $ref: '#/components/parameters/TenantId'
|
|
```
|
|
|
|
## Response Headers
|
|
|
|
```yaml
|
|
responses:
|
|
'200':
|
|
headers:
|
|
X-RateLimit-Limit:
|
|
schema:
|
|
type: integer
|
|
X-RateLimit-Remaining:
|
|
schema:
|
|
type: integer
|
|
X-Request-ID:
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
```
|
|
|
|
## Multi-Language Examples
|
|
|
|
```yaml
|
|
x-codeSamples:
|
|
- lang: TypeScript
|
|
source: |
|
|
const response = await fetch("https://api.greyhaven.com/users", {
|
|
method: "POST",
|
|
headers: { "Authorization": `Bearer ${token}` },
|
|
body: JSON.stringify({ email, password })
|
|
});
|
|
|
|
- lang: Python
|
|
source: |
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.post(
|
|
"https://api.greyhaven.com/users",
|
|
headers={"Authorization": f"Bearer {token}"},
|
|
json={"email": email, "password": password}
|
|
)
|
|
|
|
- lang: Shell
|
|
source: |
|
|
curl -X POST https://api.greyhaven.com/users \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-d '{"email": "user@example.com"}'
|
|
```
|
|
|
|
## Webhooks (OpenAPI 3.1)
|
|
|
|
```yaml
|
|
webhooks:
|
|
orderCreated:
|
|
post:
|
|
requestBody:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [event, data]
|
|
properties:
|
|
event:
|
|
type: string
|
|
const: order.created
|
|
data:
|
|
$ref: '#/components/schemas/Order'
|
|
responses:
|
|
'200':
|
|
description: Received
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Use $ref**: Define schemas once, reference everywhere
|
|
2. **Examples**: Realistic examples for all schemas
|
|
3. **Error Schemas**: Consistent error format
|
|
4. **Validation**: Use pattern, minLength, minimum
|
|
5. **Descriptions**: Document every field
|
|
6. **operationId**: Unique for SDK generation
|
|
7. **Tags**: Group related endpoints
|
|
8. **Deprecation**: Mark with `deprecated: true`
|
|
9. **Security**: Define at global or operation level
|
|
10. **Versioning**: Include in URL (/api/v1/)
|
|
|
|
## Common Patterns
|
|
|
|
### Multi-Tenant
|
|
|
|
```yaml
|
|
components:
|
|
parameters:
|
|
TenantId:
|
|
name: X-Tenant-ID
|
|
in: header
|
|
required: true
|
|
schema:
|
|
type: string
|
|
```
|
|
|
|
### Idempotency
|
|
|
|
```yaml
|
|
components:
|
|
parameters:
|
|
IdempotencyKey:
|
|
name: Idempotency-Key
|
|
in: header
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
```
|
|
|
|
### Rate Limiting
|
|
|
|
```yaml
|
|
responses:
|
|
'429':
|
|
description: Rate limit exceeded
|
|
headers:
|
|
X-RateLimit-Reset:
|
|
schema:
|
|
type: integer
|
|
```
|
|
|
|
## FastAPI Integration
|
|
|
|
```python
|
|
from fastapi import FastAPI
|
|
|
|
app = FastAPI(
|
|
title="Grey Haven API",
|
|
version="1.0.0",
|
|
openapi_version="3.1.0"
|
|
)
|
|
|
|
# Customize OpenAPI
|
|
def custom_openapi():
|
|
if app.openapi_schema:
|
|
return app.openapi_schema
|
|
|
|
openapi_schema = get_openapi(
|
|
title=app.title,
|
|
version=app.version,
|
|
routes=app.routes
|
|
)
|
|
|
|
# Add security schemes
|
|
openapi_schema["components"]["securitySchemes"] = {
|
|
"BearerAuth": {
|
|
"type": "http",
|
|
"scheme": "bearer",
|
|
"bearerFormat": "JWT"
|
|
}
|
|
}
|
|
|
|
app.openapi_schema = openapi_schema
|
|
return app.openapi_schema
|
|
|
|
app.openapi = custom_openapi
|
|
```
|
|
|
|
## Validation
|
|
|
|
Use @redocly/cli for validation:
|
|
|
|
```bash
|
|
npx @redocly/cli lint openapi.yaml
|
|
```
|
|
|
|
Common issues:
|
|
- Missing operationId
|
|
- Missing response descriptions
|
|
- Inconsistent naming
|
|
- Missing examples
|
|
- Invalid $ref paths
|
|
|
|
---
|
|
|
|
Related: [mermaid-diagrams.md](mermaid-diagrams.md) | [documentation-standards.md](documentation-standards.md) | [Return to INDEX](INDEX.md)
|