Skip to content
Draft
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
10 changes: 10 additions & 0 deletions packages/openapi/src/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ export function getOperationId(program: Program, entity: Operation): string | un
return program.stateMap(operationIdsKey).get(entity);
}

/**
* Set a specific operation ID programmatically. Equivalent of using `@operationId` decorator.
* @param program TypeSpec Program
* @param entity Operation to set ID for
* @param opId Operation ID
*/
export function setOperationId(program: Program, entity: Operation, opId: string): void {
program.stateMap(operationIdsKey).set(entity, opId);
}

const openApiExtensionKey = createStateSymbol("openApiExtension");

/** {@inheritdoc ExtensionDecorator} */
Expand Down
1 change: 1 addition & 0 deletions packages/openapi/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export {
resolveInfo,
setExtension,
setInfo,
setOperationId,
} from "./decorators.js";
export {
checkDuplicateTypeName,
Expand Down
70 changes: 70 additions & 0 deletions packages/openapi/test/decorators.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { expectDiagnostics, t } from "@typespec/compiler/testing";
import type { Operation } from "@typespec/compiler";
import { deepStrictEqual } from "assert";
import { describe, it } from "vitest";
import {
getExtensions,
getExternalDocs,
getInfo,
getOperationId,
getTagsMetadata,
resolveInfo,
setInfo,
setOperationId,
} from "../src/decorators.js";
import { Tester } from "./test-host.js";

Expand Down Expand Up @@ -36,6 +39,73 @@ describe("openapi: decorators", () => {
code: "invalid-argument",
});
});

it("set operation id via decorator", async () => {
const { program, foo } = (await Tester.compile(t.code`
@operationId("myCustomId")
@test op foo(): string;
`)) as unknown as { program: any; foo: Operation };

deepStrictEqual(getOperationId(program, foo), "myCustomId");
});

it("getOperationId returns undefined when no operation id is set", async () => {
const { program, foo } = (await Tester.compile(t.code`
@test op foo(): string;
`)) as unknown as { program: any; foo: Operation };

deepStrictEqual(getOperationId(program, foo), undefined);
});

it("setOperationId function sets operation id programmatically", async () => {
const { program, foo } = (await Tester.compile(t.code`
@test op foo(): string;
`)) as unknown as { program: any; foo: Operation };

// Initially no operation id
deepStrictEqual(getOperationId(program, foo), undefined);

// Set operation id using setOperationId
setOperationId(program, foo, "programmaticId");

// Verify it was set
deepStrictEqual(getOperationId(program, foo), "programmaticId");
});

it("setOperationId function can override decorator-set operation id", async () => {
const { program, foo } = (await Tester.compile(t.code`
@operationId("decoratorId")
@test op foo(): string;
`)) as unknown as { program: any; foo: Operation };

// Initially has decorator id
deepStrictEqual(getOperationId(program, foo), "decoratorId");

// Override with setOperationId
setOperationId(program, foo, "overrideId");

// Verify it was overridden
deepStrictEqual(getOperationId(program, foo), "overrideId");
});

it("decorator can override setOperationId function", async () => {
const { program, foo } = (await Tester.compile(t.code`
@test op foo(): string;
`)) as unknown as { program: any; foo: Operation };

// Set programmatically first
setOperationId(program, foo, "programmaticId");
deepStrictEqual(getOperationId(program, foo), "programmaticId");

// Recompile with decorator - this simulates the decorator being applied later
const { program: program2, foo: foo2 } = (await Tester.compile(t.code`
@operationId("decoratorId")
@test op foo(): string;
`)) as unknown as { program: any; foo: Operation };

// Verify decorator value takes precedence
deepStrictEqual(getOperationId(program2, foo2), "decoratorId");
});
});

describe("@extension", () => {
Expand Down