mirror of https://github.com/OpenVidu/openvidu.git
6.4 KiB
6.4 KiB
Design
Context
RouteRegistryholds oneRouteDefinitionper route, andopenApiDocument(registry, version)(src/api/openapi.ts) turns the listed routes into a zod-openapi 6 document.- How the generator fills error responses today:
- It adds
400to every operation,401to every non-public one, and403"PRO feature on CE" to every PRO route. No document lists those routes in an edition that answers that 403: locks are left out in CE, and PRO never answers it. - It adds the
ApiErrorschema only to the errors it adds itself. Errors a route declares (login401/429,ready503, analytics403, detail404) have no body.
- It adds
- Parameter descriptions come from
.meta()on the Zod schemas:- A
.meta()placed before.default()stays inside the parameter's schema and never reaches the parameter object. This is whyfromandtoshow no description. csvParam()parses a string, so its parameters render as plain strings.
- A
DashboardOptionscarries the edition and version. The public base path is bound toTOKENS.BasePathby a CE startup hook, before the docs router is built.
Goals / Non-Goals
Goals:
- A document that a client generator and a reader can use as is.
- Pieces that let PRO document its routes the same way, without CE knowing them.
- Tests that keep the invariants for every route, not only for today's.
Non-Goals:
- Changing requests, responses, status codes or authentication.
- Serving the document in production.
Decisions
Explicit operationId, checked by the registry
RouteDefinitiongains a requiredoperationId, camelCase, verb first:list<Resources>for paged lists;get<Resource>for single documents;get<Resource><Sub>for sub-resources and summaries;login,refreshSession,logout,getCurrentAdminfor auth;getHealth,getReadinessfor the probes.
register()andreplace()throw on anoperationIdanother route already uses. A lock has none and is never listed.
Rejected: deriving IDs from method and path. Names such as getApiV1RoomsSidSeries read badly, and they would change
when a path changes.
Declared tags
registry.tag(name, description)declares a tag. Each route module declares its tags before registering its routes.register()throws on an undeclared tag.- The document lists the declared tags, in declaration order, that at least one listed operation uses.
- Tag names are Title Case nouns:
Probes,Auth,Info,Overview,Rooms,Participants,Agents,Egress,Ingress,Analytics,Status. - PRO declares its own tags (such as
Tracks) the same way.
Error responses built by the generator
400(Invalid request) is added only when the route has params, a query or a body.401is added forbearerandoptional-bearerroutes.- The automatic
403for PRO routes is removed. - Every response with status ≥ 400 gets
application/jsonwithApiErrorwhen the route gives no schema. RouteResponsegainsedition?: Edition. A response with an edition is left out of other editions' documents. The CE analytics routes mark theirPRO_FEATURE403asce. The registry exposes its edition for this.
Parameters
- Shared pieces in
query.ts:- Shared parameters carry their descriptions after
.default(), so zod-openapi lifts them to the parameter:from,to,tz,limit,order,cursorandq. togets its own example (now).
- Shared parameters carry their descriptions after
csvParam(values): addsparam: { style: 'form', explode: false }andoverride: { type: 'array', items: { type: 'string', enum: values } }. The route adds the description. The other comma-separated parameters do the same:features, the egressstatuswith itsall, and the analyticsmetricsanddimensions.If-None-Match: the generator adds this header parameter to every operation whose responses include304.- Integration test: it asserts that every parameter of the booted document has a description. A new route without one fails CI instead of silently degrading the document.
Alternatives rejected for the list parameters:
- Changing their schemas to
z.array()withexplode: Express parsesa,bas one string, so validation would change. - A document-wide
overridehook keyed on the transform: too implicit.
Refresh cookie and Set-Cookie
RouteDefinitiongains an optionalrefreshCookie: 'required' | 'optional'. It only affects the document; authentication is unchanged, and the auth routes staypublic.- The generator adds the
refreshCookiesecurity scheme (apiKey,in: cookie, nameREFRESH_COOKIE). It setssecurity: [{ refreshCookie: [] }]for refresh, and[{}, { refreshCookie: [] }]for logout, which also answers without a cookie. - The auth responses that set or clear the cookie declare a
Set-Cookieheader with its attributes: login200, refresh200and401, logout204.
Metadata
openApiDocument(registry, { version, basePath, license? }):servers: [{ url: basePath without its trailing slash, or '/' }];info.description: one Markdown text of the conventions, edition-neutral;info.license: the given one, or{ name: 'Apache 2.0', identifier: 'Apache-2.0' }.
DashboardOptionsgains an optionalapiLicense, which an extending edition passes. CE passes nothing.
Schema with required keys never defined
byInputType: z.record(inputType, count) renders required without properties. It becomes
z.object({ rtmp: count, whip: count, url: count }), which validates the same values.
Unknown-route message
apiNotFound is mounted on /api, so req.path lacks the prefix. The message uses the path of req.originalUrl
(without the query string), which is the path the client sent: ${req.baseUrl}${req.path} would turn /api into
/api/.
Risks / Trade-offs
- [Breaking for PRO at compile time]
operationIdbecomes required, and undeclared tags throw. → PRO adds them in the same move ofce.ref, with its own document test. - [
overridedrifting from the parser] The array rendering duplicates the allowed values. → Both come from the samevaluesargument ofcsvParam(). The other list parameters pass their own enum constant to both the parser and the override.
Migration Plan
This changes the document only; deploy as usual. Clients that generated code from the old document get stable method names from now on.