Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/fix-streamsse-data-identifier.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"effect": patch
---

Fix `HttpApiSchema.StreamSse({ data })` so the decoded data schema keeps its identifier in the generated OpenAPI spec.

The SSE `data` field is encoded as a JSON string, and that string wrapper was claiming the data schema's `identifier`, pushing the real schema to a suffixed name (e.g. `MyEvent` became the `string` wrapper while the decoded object became `MyEvent1`). This inverted the names for OpenAPI codegen consumers. The wrapper now gets its own `${identifier}Stream` name, leaving the data schema's identifier intact.
10 changes: 9 additions & 1 deletion packages/effect/src/unstable/httpapi/HttpApiSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ export const StreamSse: {
const events = options.events ?? (options.data === undefined ? undefined : Schema.Struct({
id: Schema.UndefinedOr(Schema.String),
event: Schema.String,
data: Schema.fromJsonString(options.data)
data: sseDataJsonSchema(options.data)
}))
if (events === undefined) {
throw new Error("StreamSse requires either an events schema or a data schema")
Expand All @@ -422,6 +422,14 @@ export const StreamSse: {
})
}

const sseDataJsonSchema = (data: Schema.Constraint) => {
const json = Schema.fromJsonString(data)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potentially the real issue is with Schema.fromJsonString transforming the schema but keeping the identifier.

I'll wait for @gcanti to weight in here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potentially the real issue is with Schema.fromJsonString transforming the schema but keeping the identifier.

Agreed, we could add a synthetic identifier to the encoded side: #2512

const identifier = SchemaAST.resolveIdentifier(data.ast)
// The SSE transport field is a JSON string. Give that wrapper its own
// OpenAPI identifier so it does not claim the decoded data schema's name.
return identifier === undefined ? json : json.annotate({ identifier: `${identifier}Stream` })
}

/**
* Creates a streaming `Uint8Array` success response schema.
*
Expand Down
36 changes: 36 additions & 0 deletions packages/effect/test/unstable/httpapi/OpenApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,40 @@ describe("OpenApi", () => {
assert.property(streamExtension, "causeSchema")
assert.property(streamExtension, "errorSchema")
})

it("preserves the data schema identifier for SSE streams", () => {
const Event = Schema.Struct({
kind: Schema.String,
payload: Schema.String
}).annotate({ identifier: "MyEvent" })

const Api = HttpApi.make("Api").add(
HttpApiGroup.make("test").add(
HttpApiEndpoint.get("stream", "/stream", {
success: [HttpApiSchema.StreamSse({ data: Event })]
})
)
)

const spec = OpenApi.fromApi(Api)
const schemas = spec.components?.schemas

// The decoded data schema keeps its identifier instead of being suffixed.
assert.deepStrictEqual(schemas?.MyEvent, {
type: "object",
properties: {
kind: { type: "string" },
payload: { type: "string" }
},
required: ["kind", "payload"],
additionalProperties: false
})

// The JSON-string transport wrapper gets its own `${identifier}Stream` name.
assert.deepStrictEqual(schemas?.MyEventStream, {
type: "string",
contentSchema: { $ref: "#/components/schemas/MyEvent" },
contentMediaType: "application/json"
})
})
})