Streaming payloads¶
Server-Sent Events, JSON Lines, and multipart streams — the payloads that have no end, and that OpenAPI had no way to describe until 3.2.
apiverity validate stream.yaml # is one item described at all?
apiverity breaking stream-v1.yaml stream-v2.yaml # did the event shape change?
The problem 3.2 solved¶
Before OpenAPI 3.2 a streaming endpoint had two options and both were wrong.
Declare schema, and you are describing the whole body — but a stream has no
whole body, so the schema describes something the client never holds. Or
declare nothing, and the payload is documented in prose, which means no tool
can check it, mock it or notice when it changes.
3.2 added itemSchema: the shape of one item, not of the body.
responses:
'200':
content:
text/event-stream:
itemSchema: # one event, not the stream
type: object
required: [id, kind]
properties:
id: { type: string }
kind: { type: string, enum: [created, updated, deleted] }
What this tool does with it¶
Every schema rule applies to an item. itemSchema is parsed into the same
SchemaNode a body schema is, so removing a field from an event produces
BRK-RESP-FIELD-REMOVED exactly as removing it from a body does — with the
finding labelled stream item so a reader knows which it was.
[ERROR] BRK-RESP-FIELD-REMOVED response 200 stream item (text/event-stream): field 'detail' was removed
[WARN] BRK-ENUM-NARROWED-RESPONSE response 200 stream item (text/event-stream).kind: enum changed (removed ['deleted'])
A second family of item-shaped rules would have been forty duplicates of rules that already exist, and they would have drifted.
The five rules that are about streaming itself¶
These are the changes no schema rule can see, because no schema changed.
| Rule | |
|---|---|
BRK-STREAM-SEQUENTIAL-CHANGED |
A payload moved between a single document and a sequence of items. |
BRK-STREAM-ITEM-SCHEMA-REMOVED |
A sequential media type stopped declaring itemSchema. |
BRK-STREAM-ITEM-SCHEMA-ADDED |
It started declaring one. |
BRK-STREAM-ENCODING-CHANGED |
A streamed item's contentType changed. |
BRK-STREAM-PREFIX-COUNT-CHANGED |
The number of leading parts in a multipart stream changed. |
The one worth having¶
BRK-STREAM-SEQUENTIAL-CHANGED. Moving a payload from application/json to
application/jsonl breaks every consumer even when the item shape is
byte-identical:
- a client reading
application/jsonwaits for the body to end, then parses once; - a client reading
application/jsonlparses a line at a time, and may never see an end at all.
Nothing about the schema changed, so every schema-diffing tool reports a clean run. This is the finding that exists because somebody will make that change thinking it is a content-negotiation detail.
The non-breaking route, which --suggest-fix prints: serve both, and let
content negotiation decide rather than a deploy.
prefixEncoding is positional¶
BRK-STREAM-PREFIX-COUNT-CHANGED is an ERROR for a reason that is easy to miss:
prefixEncoding describes the leading parts of a multipart stream by
position. Inserting or removing one renumbers every part after it, so a reader
that correctly identified part three now reads part four's bytes with part
three's decoder.
Append, or version the operation.
Two validation rules¶
apiverity validate reports both directions of the mistake:
SPEC-STREAM-ITEM-SCHEMA-MISSING— a sequential media type with noitemSchema. This is what every pre-3.2 contract looks like, and it is worth a warning rather than silence: the payload is invisible to every rule, mock and drift check, and the contract does not say so.SPEC-STREAM-ITEM-SCHEMA-UNUSED—itemSchemaon a media type that is not sequential. Nothing reads it.
Which media types count as sequential¶
text/event-stream, application/jsonl, application/x-ndjson,
application/json-seq, application/jsonlines, and anything under
multipart/.
Parameters are ignored: text/event-stream; charset=utf-8 is the same media
type as text/event-stream, and a contract writing one against a service
sending the other are not disagreeing about anything.
itemEncoding is read in both shapes it is written in¶
The documented shape is a map of property name to Encoding Object. Some documents write a single Encoding Object there instead, meaning "this applies to the whole item".
Both are read. The single-object form is stored under the empty key and reported as the item rather than as a named property — because a contract this tool silently ignored half of would be worse than one it read generously and reported on accurately.
What is not modelled¶
contentType, style, explode and the header names of an Encoding Object.
The header schemas inside an encoding are not carried into the model, so a
change to one produces no finding. That is a real gap and it is recorded here
rather than left for somebody to discover from a clean run — it is listed in
product gaps.