Files
Flash5/flash-extensions/flash-ext-openapi/README.md
T
Zakaria El OrcheandClaude Opus 5 2fbe65fcc5 feat(ext-openapi): document what the code already says
Every class-based route is documented now, annotated or not: a route without
@ApiOperation used to be dropped with a warning, which made the document lie by
omission.

Read off the handler: the request body from the type it declares (or from the
new @RequestBody, for one that reads the body itself), the success schema from
the most specific handle it implements, and the media type from @Consumes.

Failures are described too. Any 4xx or 5xx without an explicit schema documents
the error object Flash actually answers with, written once under
components.schemas.Error — before this, a declared 4xx inherited the success
schema, which was simply wrong. And an answer two or more operations give
identically is hoisted into components.responses and referenced, so the 401 of
every guarded route appears once rather than on every path.

@Content gained an example, and the schema registry moved into its own class.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 13:31:28 +00:00

141 lines
5.2 KiB
Markdown

# flash-ext-openapi
OpenAPI 3.0.3 generation and Swagger UI, built from what the handlers already say about
themselves.
## What it provides
| Route | Description |
|---|---|
| `GET /openapi.json` | OpenAPI spec JSON |
| `GET /openapi.yaml` | OpenAPI spec YAML |
| `GET /openapi/swagger` | Swagger UI |
```java
FlashApp.create(8080)
.install(new JacksonExtension())
.install(new OpenApiExtension("/openapi", "My API", "1.0.0"))
.scan("com.acme.handlers")
.startAndBlock();
```
## What you get without writing anything
Every class-based route is documented, annotated or not. Read off the code:
- **path parameters**, from `/{id}` in the route
- **the request body**, from the handler's own body type (see below)
- **the response schema**, from what `handle` returns — an object, a `List<T>`, a `Map<String, T>`
- **error responses**, in the one shape Flash answers failures with: `{"error": "...", "status": 404}`
- **security and rate limiting**, from the extensions that enforce them
Annotations add what the code cannot say: prose, extra statuses, examples. They never repeat it.
## Request bodies
A handler that extends `BodyHandler``JsonHandler` and `XmlHandler`, and anything else that
reads a format — declares its body type in its signature, and that is the whole documentation:
```java
@POST("/users")
public final class CreateUser extends JsonHandler<NewUser> {
@Override protected Object handle(Request req, Response res, NewUser body) {
return users.create(body);
}
}
```
```yaml
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/NewUser' }
```
The media type comes from `@Consumes` on the base class, so an XML handler documents itself as
XML without a word from the route.
For a handler that reads the body by hand, or to describe it as something else, declare it:
```java
@PUT("/users")
@RequestBody(value = User.class, array = true, description = "Users to store")
public final class ReplaceUsers extends RequestHandler { ... }
```
## Operations
```java
@GET("/users/{id}")
@ApiOperation(summary = "Get user", description = "Returns one user", tags = {"users"})
@Parameter(name = "expand", in = ParameterIn.QUERY, type = SchemaType.STRING, examples = {"roles", "permissions"})
public final class GetUser extends RequestHandler { ... }
```
`@ApiOperation` is optional: without it the route is still in the document, with no summary.
## Responses
The success response is inferred. Declare one only to say more:
```java
@APIResponse(responseCode = "200", description = "User found",
content = @Content(schema = UserDto.class, example = "{\"id\":\"usr-1\"}"))
@APIResponse(responseCode = "409", description = "That email is taken")
@APIResponse(responseCode = "204", content = @Content(contentType = ContentType.NONE))
```
- `content.schema` omitted on a 2xx: the handler's return type.
- Any 4xx or 5xx without an explicit schema: Flash's error object, referenced from `components`.
- `content.array = true` wraps whichever schema was chosen.
- `contentType = NONE` documents a response with no body.
**A response several operations share is written once.** Identical answers — the 401 of every
guarded route, the 429 of every limited one — become `components.responses` entries referenced by
`$ref`, instead of being repeated on every path.
## DTO schemas
```java
@Schema(name = "User", title = "User DTO", description = "Public user")
public record UserDto(
@SchemaProperty(title = "ID", example = "USR-100") String id,
@SchemaProperty(hidden = true) String internalDebug) {}
```
Each type is described once under `components.schemas` and referenced everywhere it appears.
Field-level exclusion: `@Schema(hidden = true)`, `@SchemaProperty(hidden = true)`, `@JsonIgnore`,
`@JsonIgnoreProperties`, `transient`, `static`. `jakarta.validation` constraints (`@NotNull`,
`@NotBlank`, `@NotEmpty`, `@Size`, `@Min`, `@Max`, `@Email`, `@Pattern`) become the schema's own
bounds and required fields, so a rule is written once and documented for free.
## Contributor API
OpenAPI is extension-agnostic. Other extensions contribute through `OpenApiContributor`, held in
`OpenApiContributorRegistry`:
- `components` fragments (merged last-wins)
- operation `security` requirements (additive)
- operation `responses` and response `headers` (additive)
Manual `@APIResponse` description always wins over a contributor's for the same status.
### Security interop
With `flash-ext-security-core` installed, every registered mechanism's scheme lands under
`components.securitySchemes`, and every operation carrying a security annotation lists them as
`security` alternatives with automatic `401` and — for roles or scopes — `403` responses.
### Limiter interop
When `flash-ext-limiter` is installed, handlers with `@Limit` document `X-RateLimit-Limit`,
`X-RateLimit-Remaining`, `X-RateLimit-Reset`, and a `429` with `Retry-After`.
## Notes
- Operations come from the final boot-time routes, so documented paths match runtime paths,
namespaces, prefixes and rewrites included.
- Lambda routes are not documented: there is no class to read.
- Responses are sorted by status code; the document is rebuilt only when a route is added.