Fix OpenAPI3 component response conversion to emit reusable response models - #11735
Fix OpenAPI3 component response conversion to emit reusable response models#11735Vincent Biret (baywet) with Copilot wants to merge 14 commits into
Conversation
|
Azure Pipelines: Successfully started running 1 pipeline(s). 1 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
Copilot add the changelog entry for this fix |
commit: |
|
You can try these changes here
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
packages/openapi3/src/cli/actions/convert/transforms/transforms.ts:321
responsesis assigned but never used. WithnoUnusedLocals/linting enabled this will fail the build; even without it, this local can be removed without changing behavior.
const responses = context.openApi3Doc.components?.responses;
if (!responses) return;
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
packages/openapi3/src/cli/actions/convert/transforms/transforms.ts:321
transformComponentResponsesdeclaresresponsesbut never uses it. This adds dead code and makes it harder to see what the function actually depends on.
You can inline the existence check against context.openApi3Doc.components?.responses and remove the unused local.
const responses = context.openApi3Doc.components?.responses;
if (!responses) return;
packages/openapi3/src/cli/actions/convert/transforms/transforms.ts:389
getResponsePropertiestakesstatusCode: string, but then passes it toconvertStatusCodeToProperty, which expectsExclude<StatusCodes, "default">. This is a TypeScript type error (a plainstringisn’t assignable toStatusCodes) and can break the build.
Consider typing getResponseProperties to StatusCodes (via a type query) and casting the iterated statusCode when calling it, since Object.entries(...) loses the literal key type.
function getResponseProperties(
statusCode: string,
response: OpenAPI3Response,
context: Context,
): TypeSpecModelProperty[] {
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
packages/openapi3/src/cli/actions/convert/generators/generate-response-expressions.ts:48
- The early return for
#/components/responses/...refs bypasses the existing OpenAPI 3.2 SSE (text/event-stream+itemSchema) handling later ingenerateResponseExpressions, so SSE component responses would incorrectly return the generated response model instead ofSSEStream<...>.
if ("$ref" in props.response && props.response.$ref.startsWith("#/components/responses/")) {
const componentResponseName = context.getComponentResponseName(props.response.$ref, statusCode);
if (componentResponseName) {
return [componentResponseName];
}
packages/openapi3/test/tsp-openapi3/convert-openapi3-doc.test.ts:285
- This assertion hard-codes an exact newline + indentation sequence (
"@error\n model ..."), which is brittle across formatting changes. Prefer a whitespace-tolerant regex so the test only cares that@erroris applied to the model.
strictEqual(
tsp.includes("@error\n model RejectedDefault"),
true,
"Expected default response model to be marked as an error: " + tsp,
);
Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
Signed-off-by: Vincent Biret <vibiret@microsoft.com>
fac2d04 to
d503c1c
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (1)
packages/openapi3/src/cli/actions/convert/generators/generate-response-expressions.ts:49
- Component response reuse short-circuits purely on
$ref+ status code, which drops any operation-level overrides on the referencing response object (e.g.descriptionalongside$ref). This is a behavior change from the existing inline path (which usesprops.response.description ?? response.description) and can cause per-operation response docs/metadata to be silently ignored when$refis used with sibling fields.
if ("$ref" in props.response && props.response.$ref.startsWith("#/components/responses/")) {
const componentResponseName = context.getComponentResponseName(props.response.$ref, statusCode);
if (componentResponseName) {
return [componentResponseName];
}
}
| @header("x-test") xTest?: string; | ||
| @route("/") @get op getFoo(): Responses.TestResponse; | ||
|
|
||
| namespace Responses { |
There was a problem hiding this comment.
you think we should put this into a Responses namespace instead of at the root? Will this not scope every response to Responses.TestResponse in the openapi3 regenreated?
There was a problem hiding this comment.
the reason why I pushed everything to a separate sub-namespace was to avoid collisions.
If we have a response component named Response that $refs into a schema component named Response, we'll have a collision. Do you have a better suggestion on how to handle that scenario?
There was a problem hiding this comment.
did you encounter those conflicts? Just because here it makes the roundtrip keep adding Responses prefix. Could we maybe add a Response suffix if there is a conflcit?
There was a problem hiding this comment.
Any OData based service is going to expose those kinds of responses. For example Microsoft Graph the ODataCountResponse component response $ref to a ODataCountResponse component schema.
Adding a suffix doesn't technically solve the issue, it only reduces the likelyness of a collision. Also, when would you run the conflict detection?
The OpenAPI3 import path was flattening referenced component responses into ad hoc inline response objects, which duplicated response metadata across operations and prevented reusable response models. This made shared component responses harder to maintain and could produce awkward TypeSpec definitions when the same response was referenced repeatedly.
Summary
#/components/responses/...references instead of inlining the response body at each operation.Responsesand preserves the status code, headers, and body schema from the component response.What changed
application/jsonpayload when multiple content types exist.Example
This keeps component responses reusable and reduces duplication while preserving the semantics of the original OpenAPI description.