Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bug: Unable to use {service-name} interpolation for filenames with openapi3-emitter when only one service is defined #6182

Merged
merged 12 commits into from
Mar 3, 2025
7 changes: 7 additions & 0 deletions .chronus/changes/wanl-service-name-2025-1-27-19-40-32.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: breaking
packages:
- "@typespec/openapi3"
---

Using `{service-name}` in `tspconfig.yaml` will always interpolate the current service name. `{service-name-if-multiple}` can be used to get the previous behavior
5 changes: 3 additions & 2 deletions packages/openapi3/README.md
Original file line number Diff line number Diff line change
@@ -48,10 +48,11 @@ If the content should be serialized as YAML or JSON. Default 'yaml', it not spec
Name of the output file.
Output file will interpolate the following values:

- service-name: Name of the service if multiple
- service-name: Name of the service
- service-name-if-multiple: Name of the service if multiple
- version: Version of the service if multiple

Default: `{service-name}.{version}.openapi.yaml` or `.json` if `file-type` is `"json"`
Default: `{service-name-if-multiple}.{version}.openapi.yaml` or `.json` if `file-type` is `"json"`

Example Single service no versioning

10 changes: 6 additions & 4 deletions packages/openapi3/src/lib.ts
Original file line number Diff line number Diff line change
@@ -13,10 +13,11 @@ export interface OpenAPI3EmitterOptions {
/**
* Name of the output file.
* Output file will interpolate the following values:
* - service-name: Name of the service if multiple
* - service-name: Name of the service
* - service-name-if-multiple: Name of the service if multiple
* - version: Version of the service if multiple
*
* @default `{service-name}.{version}.openapi.yaml` or `.json` if {@link OpenAPI3EmitterOptions["file-type"]} is `"json"`
* @default `{service-name-if-multiple}.{version}.openapi.yaml` or `.json` if {@link OpenAPI3EmitterOptions["file-type"]} is `"json"`
*
* @example Single service no versioning
* - `openapi.yaml`
@@ -99,10 +100,11 @@ const EmitterOptionsSchema: JSONSchemaType<OpenAPI3EmitterOptions> = {
description: [
"Name of the output file.",
" Output file will interpolate the following values:",
" - service-name: Name of the service if multiple",
" - service-name: Name of the service",
" - service-name-if-multiple: Name of the service if multiple",
" - version: Version of the service if multiple",
"",
' Default: `{service-name}.{version}.openapi.yaml` or `.json` if `file-type` is `"json"`',
' Default: `{service-name-if-multiple}.{version}.openapi.yaml` or `.json` if `file-type` is `"json"`',
"",
" Example Single service no versioning",
" - `openapi.yaml`",
5 changes: 3 additions & 2 deletions packages/openapi3/src/openapi.ts
Original file line number Diff line number Diff line change
@@ -200,7 +200,7 @@ export function resolveOptions(
resolvedOptions["file-type"] ?? findFileTypeFromFilename(resolvedOptions["output-file"]);

const outputFile =
resolvedOptions["output-file"] ?? `openapi.{service-name}.{version}.${fileType}`;
resolvedOptions["output-file"] ?? `openapi.{service-name-if-multiple}.{version}.${fileType}`;

const openapiVersions = resolvedOptions["openapi-versions"] ?? ["3.0.0"];

@@ -533,7 +533,8 @@ function createOAPIEmitter(
function resolveOutputFile(service: Service, multipleService: boolean, version?: string): string {
return interpolatePath(options.outputFile, {
"openapi-version": specVersion,
"service-name": multipleService ? getNamespaceFullName(service.type) : undefined,
"service-name-if-multiple": multipleService ? getNamespaceFullName(service.type) : undefined,
"service-name": getNamespaceFullName(service.type),
version,
});
}
56 changes: 56 additions & 0 deletions packages/openapi3/test/output-file.test.ts
Original file line number Diff line number Diff line change
@@ -134,4 +134,60 @@ describe("openapi3: output file", () => {
});
});
});

describe("Predefined variable name behavior", () => {
interface ServiceNameCase {
description: string;
code: string;
outputFilePattern: string;
expectedOutputFiles: string[];
}
it.each([
// {service-name} cases
{
description: "{service-name} for one service",
code: "@service namespace AAA { model M {a: string} }",
outputFilePattern: "{service-name}.yaml",
expectedOutputFiles: ["AAA.yaml"],
},
{
description: "{service-name} for multiple services",
code:
"@service namespace AAA { model M {a: string} }" +
"@service namespace BBB { model N {b: string} }",
outputFilePattern: "{service-name}.yaml",
expectedOutputFiles: ["AAA.yaml", "BBB.yaml"],
},
// {service-name-if-multiple} cases
{
description: "{service-name-if-multiple} for one service",
code: "@service namespace AAA { model M {a: string} }",
outputFilePattern: "{service-name-if-multiple}.yaml",
expectedOutputFiles: ["yaml"],
},
{
description: "{service-name-if-multiple} for multiple services",
code:
"@service namespace AAA { model M {a: string} }" +
"@service namespace BBB { model N {b: string} }",
outputFilePattern: "{service-name-if-multiple}.yaml",
expectedOutputFiles: ["AAA.yaml", "BBB.yaml"],
},
// fixed name cases
{
description: "fixed name for one service",
code: "@service namespace AAA { model M {a: string} }",
outputFilePattern: "fixed-name.yaml",
expectedOutputFiles: ["fixed-name.yaml"],
},
])("$description", async (c: ServiceNameCase) => {
await compileOpenAPI(
{
"output-file": c.outputFilePattern,
},
c.code,
);
for (const outputFile of c.expectedOutputFiles) expectHasOutput(outputFile);
});
});
});
Original file line number Diff line number Diff line change
@@ -42,10 +42,11 @@ If the content should be serialized as YAML or JSON. Default 'yaml', it not spec
Name of the output file.
Output file will interpolate the following values:

- service-name: Name of the service if multiple
- service-name: Name of the service
- service-name-if-multiple: Name of the service if multiple
- version: Version of the service if multiple

Default: `{service-name}.{version}.openapi.yaml` or `.json` if `file-type` is `"json"`
Default: `{service-name-if-multiple}.{version}.openapi.yaml` or `.json` if `file-type` is `"json"`

Example Single service no versioning

Original file line number Diff line number Diff line change
@@ -99,17 +99,17 @@ Here's what would be produced:
| ------------------ | ------------- | ------------------------- |
| `"PetStore"` | `"v1"` | `PetStore/output.v1.json` |
| `"PetStore"` | `undefined` | `PetStore/output.json` |
| `undefined` | `"v1"` | `output.v1.json` |
| `undefined` | `undefined` | `output.json` |

#### Built-in variables

| Variable name | Scope | Description |
| -------------- | --------------- | ------------------------------------------------------------------------------------ |
| `cwd` | \* | Points to the current working directory |
| `project-root` | \* | Points to the the tspconfig.yaml file containing folder. |
| `output-dir` | emitter options | Common `output-dir` See [output-dir](#output-dir---configure-the-default-output-dir) |
| `emitter-name` | emitter options | Name of the emitter |
| Variable name | Scope | Description |
| -------------------------- | ------------------------ | ------------------------------------------------------------------------------------ |
| `cwd` | \* | Points to the current working directory |
| `project-root` | \* | Points to the the tspconfig.yaml file containing folder. |
| `output-dir` | emitter options | Common `output-dir` See [output-dir](#output-dir---configure-the-default-output-dir) |
| `emitter-name` | emitter options | Name of the emitter |
| `service-name` | service name | Name of the service |
| `service-name-if-multiple` | service name if multiple | Name of the service if multiple, undefined if one |

#### Project Parameters

Loading
Oops, something went wrong.