OpenAPI 3.2 SSE schemas misdescribe unnamed events and literal payloads

Abierto
#12,119 2 comentarios 1 reacción 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
4/5
Tiempo estimado
3-5 días
Aptitud para principiantes
55/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
json, openapi

Línea de trabajo

The bug lives in the TypeSpec OpenAPI 3.2 emitter's SSE stream schema generation. Start by finding the code that produces itemSchema for SSEStream<T> unions, focusing on the paths left unchanged by PR 12021. Add regression tests in the compiler/emitter test harness for unnamed events, text/JSON sentinels, JSON terminal literals, and missing data. Validate output against OpenAPI 3.2 SSE rules and JSON Schema Draft 2020-12.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

bug emitter:openapi3 triaged:core

Problem

OpenAPI 3.2 SSE emission still misdescribes valid streams with unnamed events, literal sentinels, and JSON-encoded terminal literals. These gaps are separate from the named model-valued terminal-event bug in https://github.com/microsoft/typespec/issues/11747.

https://github.com/microsoft/typespec/pull/12021 fixes that named-terminal case and preserves event names for named literal variants. The problems below are in code paths that the PR leaves unchanged.

This affects common contracts such as OpenAI Chat Completions (unnamed JSON chunks followed by a plain-text [DONE] sentinel) and Gemini streamGenerateContent (unnamed JSON records until EOF).

Reproduction

import "@typespec/http";
import "@typespec/events";
import "@typespec/sse";

using TypeSpec.Http;
using TypeSpec.Events;
using TypeSpec.SSE;

model Chunk {
  text: string;
}

@events
union UnnamedEvents {
  Chunk,
}

@route("/unnamed")
@get op unnamed(): SSEStream<UnnamedEvents>;

@events
union ChatEvents {
  Chunk,
  @contentType("text/plain")
  @terminalEvent
  "[DONE]",
}

@route("/chat")
@get op chat(): SSEStream<ChatEvents>;

@events
union JsonTerminalEvents {
  @contentType("application/json")
  @terminalEvent
  "done",
}

@route("/json-terminal")
@get op jsonTerminal(): SSEStream<JsonTerminalEvents>;

@events
union NamedEvents {
  chunk: Chunk,
}

@route("/named")
@get op named(): SSEStream<NamedEvents>;
emit:
  - "@typespec/openapi3"
options:
  "@typespec/openapi3":
    openapi-versions: ["3.2.0"]
1. event is required even for unnamed events

All four emitted itemSchema objects use required: ["event"]. But this is a valid unnamed SSE event:

data: {"text":"hello"}

Its parsed field object, {"data":"{\"text\":\"hello\"}"}, fails validation with must have required property 'event'. An omitted or empty SSE event name defaults to message; the wire field need not exist.

2. Unnamed JSON and sentinel branches overlap

The /chat schema contains:

oneOf:
  - properties:
      data:
        contentMediaType: application/json
        contentSchema:
          $ref: '#/components/schemas/Chunk'
  - properties:
      data:
        contentMediaType: text/plain
        const: '[DONE]'

Even if a parser materializes the default event name, {"event":"message","data":"[DONE]"} fails oneOf: both branches match.

contentMediaType and contentSchema are annotations, not default validation assertions. They do not distinguish the first branch from the sentinel branch. Multiple unnamed model variants have the same problem: different contentSchema annotations alone cannot make their outer oneOf branches exclusive.

3. JSON terminal literals constrain the wrong string

For /json-terminal, the emitter produces:

data:
  contentMediaType: application/json
  const: done

But the explicitly JSON-encoded literal "done" has this wire representation:

data: "done"

The parsed SSE data string includes the JSON quotation marks. With an explicit/default-materialized event: message, that valid JSON data fails the emitted const, whereas unquoted data: done passes.

Terminality should not determine whether a literal is treated as raw text or a JSON-encoded value. The declared payload content type should determine the representation.

4. data is not required

For /named, {"event":"chunk"} passes the emitted schema despite having no data. SSE does not dispatch a message when there are no data lines. OpenAPI 3.2's generic SSE event schema requires data, not event.

Expected

  • Require data for dispatched SSE messages.
  • Require an event name on branches describing named events, not globally when unnamed/default-message variants are present.
  • Preserve the distinction between named events and unnamed/default-message events in mixed unions.
  • Avoid outer oneOf branches whose only differences are non-validating content annotations. For example, an assertion such as not: { const: "[DONE]" } can exclude a plain-text sentinel from the JSON branch; other correct representations are possible.
  • Describe JSON literals inside contentSchema, or constrain their correctly serialized wire string. Do not apply a raw-text const solely because the variant is terminal.
  • Keep event identity and payload representation unchanged when @terminalEvent is added. Representing the disconnect instruction itself is a separate extension/design question.

Useful regression cases include data-only JSON streams, data-only JSON plus a text sentinel, mixed named/default-message events, named text/plain terminal literals, unnamed JSON terminal literals, multiple unnamed model variants, and records missing data.

Verification and references

Reproduced with the existing compiler/emitter test harness and Ajv's Draft 2020-12 validator with default content-keyword behavior. The observations above do not depend on the named model-terminal branch changed in PR 12021.

Lenguaje dominante
Java
Estrellas
5.9k
Forks
421
Merge medio
2 d 3 h
PR fusionados (30 d)
176

Preparar el entorno

Abrir en Codespaces

Inicia el contenedor de desarrollo del proyecto en tu navegador, con tu propia cuenta de GitHub.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de microsoft/typespec

Todos los issues de microsoft/typespec

Issues similares

Más issues de Java

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.