Spectral v6 ships no implicit ruleset — the CI job exited 'no ruleset found'. Adds .spectral.yaml (extends spectral:oas, documentation-only warnings relaxed for design-time stubs), adds operation tags to the _example contract so it lints clean (0 results), and aligns the api-contract-stub note. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
98 lines
4.1 KiB
Markdown
98 lines
4.1 KiB
Markdown
# API Contract Stub
|
|
|
|
This project is **REST + OpenAPI**. The backend serves the live spec via springdoc at
|
|
`http://localhost:8080/v3/api-docs` (dev profile only), and the frontend generates its
|
|
TypeScript client from it with `npm run generate:api` (`openapi-typescript` →
|
|
`frontend/src/lib/generated/api.ts`). There is no GraphQL in this stack.
|
|
|
|
> **The live spec is generated from the Java controllers — it is the source of truth.** A
|
|
> hand-written stub is a *design artifact*: it pins the intended shape during spec review.
|
|
> Issue-only: paste the stub inline into the issue's `## API / Contract Stub` section. Keep it
|
|
> OpenAPI **3.1**, and keep `@Schema(requiredMode = REQUIRED)` on the Java side as the real
|
|
> driver of `required`.
|
|
|
|
## How to use this stub
|
|
|
|
1. Fill in the skeleton below with the paths/methods/schemas your feature adds, and paste it
|
|
into the issue's `## API / Contract Stub` section.
|
|
2. Every mutating path documents the `403`/`401` responses and the `cookieAuth` security
|
|
requirement (matching the real `@RequirePermission` gate).
|
|
3. If you prefer a standalone, lintable file (e.g. for a large contract), commit it on the
|
|
**feature branch** as `<feature>.openapi.yaml` — the `sdd-gate.yml` CI job lints any
|
|
committed OpenAPI contract with Spectral (`npx @stoplight/spectral-cli lint`). It never
|
|
needs to predate the issue.
|
|
4. After the endpoint ships, run `npm run generate:api` and diff the generated types against
|
|
this contract; reconcile any drift (the generated spec wins — update the contract).
|
|
|
|
## OpenAPI 3.1 skeleton
|
|
|
|
```yaml
|
|
openapi: 3.1.0
|
|
info:
|
|
title: Familienarchiv API — <feature name>
|
|
version: 0.0.1-SNAPSHOT
|
|
description: Design-time contract for <feature>. Source of truth is the generated /v3/api-docs.
|
|
servers:
|
|
- url: http://localhost:8080
|
|
description: Local backend (dev profile)
|
|
- url: https://archiv.raddatz.cloud
|
|
description: Production (behind Caddy)
|
|
components:
|
|
securitySchemes:
|
|
cookieAuth: # Spring Session JDBC — opaque session id in the SESSION cookie
|
|
type: apiKey
|
|
in: cookie
|
|
name: SESSION
|
|
schemas:
|
|
ErrorResponse: # shape produced by GlobalExceptionHandler
|
|
type: object
|
|
required: [code, message]
|
|
properties:
|
|
code:
|
|
type: string
|
|
description: Machine-readable ErrorCode (see ErrorCode.java / errors.ts).
|
|
example: FORBIDDEN
|
|
message:
|
|
type: string
|
|
# <YourResponseView>: # always a view, never a lazy-collection entity (ADR-036)
|
|
# type: object
|
|
# required: [id]
|
|
# properties:
|
|
# id: { type: string, format: uuid }
|
|
security:
|
|
- cookieAuth: [] # default: every path requires a session unless overridden to []
|
|
paths:
|
|
/api/<resource>:
|
|
post:
|
|
summary: <create …>
|
|
operationId: <createResource>
|
|
security:
|
|
- cookieAuth: [] # plus @RequirePermission(Permission.X) on the controller
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/<CreateDTO>' }
|
|
responses:
|
|
'201':
|
|
description: Created
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/<YourResponseView>' }
|
|
'400': { description: Validation failed, content: { application/json: { schema: { $ref: '#/components/schemas/ErrorResponse' } } } }
|
|
'401': { description: Unauthenticated, content: { application/json: { schema: { $ref: '#/components/schemas/ErrorResponse' } } } }
|
|
'403': { description: Missing permission, content: { application/json: { schema: { $ref: '#/components/schemas/ErrorResponse' } } } }
|
|
```
|
|
|
|
## Validating the contract in CI
|
|
|
|
The `sdd-gate.yml` `contract-validate` job lints any committed OpenAPI file changed in the PR:
|
|
|
|
```bash
|
|
npx @stoplight/spectral-cli lint <your-contract>.yaml
|
|
```
|
|
|
|
The ruleset is `.spectral.yaml` at the repo root (extends `spectral:oas`; documentation-only
|
|
warnings relaxed for design-time stubs). Spectral auto-discovers it. It catches malformed
|
|
specs, undefined `$ref`s, and duplicate `operationId`s; tune `.spectral.yaml` to adjust.
|