Skip to content

Commit 2bde8ea

Browse files
Copilotbaywet
andauthored
Fix OpenAPI import to emit @cookie for cookie parameters (#10995)
OpenAPI import currently drops parameter location metadata for `in: cookie`, producing undecorated parameters in generated TypeSpec. This updates conversion so cookie parameters are emitted as HTTP cookie parameters across OpenAPI 3.0/3.1/3.2 nullable forms. - **Converter fix** - Added `cookie` to supported parameter locations in the OpenAPI3 converter decorator mapping. - Cookie parameters now generate `@cookie` the same way `query`, `header`, and `path` already do. - **Coverage updates** - Extended parameter-location tests to include `cookie` in top-level parameter conversion. - Added operation-level cookie tests covering: - OpenAPI 3.0: `nullable: true` - OpenAPI 3.1/3.2: `type: ["string", "null"]` - required vs optional parameter emission - **Changelog** - Added a `fix` chronus entry for `@typespec/openapi3`. ```tsp // before @get op Widgets_get(session_id?: string | null): OkResponse; // after @get op Widgets_get(@cookie session_id?: string | null): OkResponse; ``` --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
1 parent 68dd7b4 commit 2bde8ea

3 files changed

Lines changed: 100 additions & 25 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
changeKind: fix
3+
packages:
4+
- "@typespec/openapi3"
5+
---
6+
7+
Fix OpenAPI import to emit `@cookie` decorators for cookie parameters, including nullable and type-null schema variants.

packages/openapi3/src/cli/actions/convert/utils/decorators.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { stringLiteral } from "../generators/common.js";
1313
import { TSValue, TypeSpecDecorator, TypeSpecDirective } from "../interfaces.js";
1414
import type { Context } from "./context.js";
1515

16-
const validLocations = ["header", "query", "path"];
16+
const validLocations = ["header", "query", "path", "cookie"];
1717
const extensionDecoratorName = "extension";
1818

1919
export function getExtensions(element: Extensions): TypeSpecDecorator[] {

packages/openapi3/test/tsp-openapi3/parameters.test.ts

Lines changed: 92 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,35 +11,38 @@ import {
1111
} from "./utils/tsp-for-openapi3.js";
1212

1313
describe("converts top-level parameters", () => {
14-
it.each(["query", "header", "path"] as const)(`Supports location: %s`, async (location) => {
15-
const serviceNamespace = await tspForOpenAPI3({
16-
parameters: {
17-
Foo: {
18-
name: "foo",
19-
in: location,
20-
required: true,
21-
schema: {
22-
type: "string",
14+
it.each(["query", "header", "path", "cookie"] as const)(
15+
`Supports location: %s`,
16+
async (location) => {
17+
const serviceNamespace = await tspForOpenAPI3({
18+
parameters: {
19+
Foo: {
20+
name: "foo",
21+
in: location,
22+
required: true,
23+
schema: {
24+
type: "string",
25+
},
2326
},
2427
},
25-
},
26-
});
28+
});
2729

28-
const parametersNamespace = serviceNamespace.namespaces.get("Parameters");
29-
assert(parametersNamespace, "Parameters namespace not found");
30+
const parametersNamespace = serviceNamespace.namespaces.get("Parameters");
31+
assert(parametersNamespace, "Parameters namespace not found");
3032

31-
const models = parametersNamespace.models;
33+
const models = parametersNamespace.models;
3234

33-
/* model Foo { @<location> foo: string, } */
34-
const Foo = models.get("Foo");
35-
assert(Foo, "Foo model not found");
36-
expect(Foo.properties.size).toBe(1);
37-
expect(Foo.properties.get("foo")).toMatchObject({
38-
optional: false,
39-
type: { kind: "Scalar", name: "string" },
40-
decorators: [{ definition: { name: `@${location}` } }],
41-
});
42-
});
35+
/* model Foo { @<location> foo: string, } */
36+
const Foo = models.get("Foo");
37+
assert(Foo, "Foo model not found");
38+
expect(Foo.properties.size).toBe(1);
39+
expect(Foo.properties.get("foo")).toMatchObject({
40+
optional: false,
41+
type: { kind: "Scalar", name: "string" },
42+
decorators: [{ definition: { name: `@${location}` } }],
43+
});
44+
},
45+
);
4346

4447
it(`Supports string schema constraints`, async () => {
4548
const serviceNamespace = await tspForOpenAPI3({
@@ -408,6 +411,71 @@ describe("header", () => {
408411
});
409412
});
410413

414+
describe("cookie", () => {
415+
it("renders @cookie for OpenAPI 3.0 nullable cookie parameters", async () => {
416+
const tsp = await renderTypeSpecForOpenAPI3({
417+
paths: {
418+
"/widgets": {
419+
get: {
420+
operationId: "Widgets_get",
421+
parameters: [
422+
{
423+
name: "session_id",
424+
in: "cookie",
425+
required: false,
426+
explode: false,
427+
schema: { type: "string", nullable: true },
428+
},
429+
],
430+
responses: {
431+
"200": { description: "OK" },
432+
},
433+
},
434+
},
435+
},
436+
});
437+
438+
expect(tsp).toContain('import "@typespec/http";');
439+
expect(tsp).toContain("using Http;");
440+
expect(tsp).toContain("@cookie session_id?: string | null");
441+
});
442+
443+
it.each([
444+
{ openapi: "3.1.0", required: false, expected: "@cookie session_id?: string | null" },
445+
{ openapi: "3.2.0", required: true, expected: "@cookie session_id: string | null" },
446+
] as const)(
447+
"renders @cookie for OpenAPI $openapi type-null cookie parameters",
448+
async ({ openapi, required, expected }) => {
449+
const tsp = await renderTypeSpecForOpenAPI3({
450+
openapi: openapi as any,
451+
paths: {
452+
"/widgets": {
453+
get: {
454+
operationId: "Widgets_get",
455+
parameters: [
456+
{
457+
name: "session_id",
458+
in: "cookie",
459+
required,
460+
explode: false,
461+
schema: { type: ["string", "null"] as any },
462+
},
463+
],
464+
responses: {
465+
"200": { description: "OK" },
466+
},
467+
},
468+
},
469+
},
470+
});
471+
472+
expect(tsp).toContain('import "@typespec/http";');
473+
expect(tsp).toContain("using Http;");
474+
expect(tsp).toContain(expected);
475+
},
476+
);
477+
});
478+
411479
describe("query", () => {
412480
it(`sets explode: true for default OpenAPI 3 parameter`, async () => {
413481
const serviceNamespace = await tspForOpenAPI3({

0 commit comments

Comments
 (0)