Skip to content

Commit e28ba46

Browse files
fix(scripts): Improve code sample selection logic (#6018)
1 parent 4ffa686 commit e28ba46

File tree

3 files changed

+64
-3
lines changed

3 files changed

+64
-3
lines changed

scripts/config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ export function getSnippetFile(gen: Generator): string {
4949
return `docs/snippets/${gen.language}/${gen.snippets.outputFolder}/${createClientName(gen.client, gen.language)}${gen.snippets.extension}`;
5050
}
5151

52+
export function getCTSRequestDir(clientName: string): string {
53+
return `tests/CTS/requests/${clientName}`;
54+
}
55+
5256
export function getDockerImage(language?: Language): string | undefined {
5357
if (CI || !language) {
5458
return undefined;

scripts/specs/format.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { GENERATORS, run, toAbsolutePath } from '../common.ts';
1010
import { createSpinner } from '../spinners.ts';
1111
import type { Spec } from '../types.ts';
1212

13-
import { getCodeSampleLabel, transformGeneratedSnippetsToCodeSamples } from './snippets.ts';
13+
import { CODE_SAMPLE_KEY, getCodeSampleLabel, transformGeneratedSnippetsToCodeSamples } from './snippets.ts';
1414

1515
export async function lintCommon(useCache: boolean): Promise<void> {
1616
const spinner = createSpinner('linting common spec');
@@ -70,11 +70,15 @@ export async function bundleSpecsForDoc(bundledPath: string, clientName: string)
7070
specMethod['x-codeSamples'] = [];
7171
}
7272

73+
// if a CTS test is marked with isCodeSample: true, it takes priority; otherwise fall back to the first snippet
7374
if (codeSamples[gen.language][specMethod.operationId]) {
7475
specMethod['x-codeSamples'].push({
7576
lang: gen.language,
7677
label: getCodeSampleLabel(gen.language),
77-
source: Object.values(codeSamples[gen.language][specMethod.operationId])[0],
78+
source:
79+
(Object.hasOwn(codeSamples[gen.language][specMethod.operationId], CODE_SAMPLE_KEY)
80+
? codeSamples[gen.language][specMethod.operationId][CODE_SAMPLE_KEY]
81+
: undefined) || Object.values(codeSamples[gen.language][specMethod.operationId])[0],
7882
});
7983
}
8084
}

scripts/specs/snippets.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import fsp from 'fs/promises';
2+
import path from 'path';
23

34
import { GENERATORS, capitalize, exists, toAbsolutePath } from '../common.ts';
45
import type { Language } from '../types.ts';
56

6-
import { getSnippetFile } from '../config.ts';
7+
import { getCTSRequestDir, getSnippetFile } from '../config.ts';
78
import type { CodeSamples, OpenAPICodeSample, SampleForOperation } from './types.ts';
89

10+
// Reserved key used to store the preferred code sample (marked with isCodeSample: true in CTS JSON).
11+
export const CODE_SAMPLE_KEY = 'PREFERRED_SNIPPET';
12+
913
export function getCodeSampleLabel(language: Language): OpenAPICodeSample['label'] {
1014
switch (language) {
1115
case 'csharp':
@@ -52,6 +56,53 @@ export function generateSnippetsJSON(codeSamples: CodeSamples): CodeSamples {
5256
return codeSamples;
5357
}
5458

59+
// Reads CTS request JSON files and, for tests marked with `isCodeSample: true`,
60+
// tags the matching snippet with the reserved CODE_SAMPLE_KEY.
61+
// Throws if more than one test per operationId is marked as a code sample.
62+
export async function tagCodeSamples(clientName: string, codeSamples: CodeSamples): Promise<void> {
63+
const ctsDir = toAbsolutePath(getCTSRequestDir(clientName));
64+
65+
if (!(await exists(ctsDir))) {
66+
return;
67+
}
68+
69+
const files = await fsp.readdir(ctsDir);
70+
71+
for (const file of files) {
72+
// Use basename to strip any accidental path components returned by readdir.
73+
const safeFile = path.basename(file);
74+
if (!safeFile.endsWith('.json')) {
75+
continue;
76+
}
77+
78+
const operationId = path.basename(safeFile, '.json');
79+
const tests: Array<{ testName: string; isCodeSample?: boolean }> = JSON.parse(
80+
await fsp.readFile(path.join(ctsDir, safeFile), 'utf8'),
81+
);
82+
83+
const codeSampleTests = tests.filter((t) => t.isCodeSample === true);
84+
85+
if (codeSampleTests.length > 1) {
86+
throw new Error(
87+
`Found ${codeSampleTests.length} tests with isCodeSample: true for operationId "${operationId}" in ${clientName}. Only one is allowed.`,
88+
);
89+
}
90+
91+
if (codeSampleTests.length === 0) {
92+
continue;
93+
}
94+
95+
const testName = codeSampleTests[0].testName;
96+
97+
for (const lang of Object.keys(codeSamples) as Language[]) {
98+
const samplesForOp = codeSamples[lang][operationId];
99+
if (samplesForOp !== undefined && Object.hasOwn(samplesForOp, testName)) {
100+
samplesForOp[CODE_SAMPLE_KEY] = samplesForOp[testName];
101+
}
102+
}
103+
}
104+
}
105+
55106
// Reads the generated `docs/snippets/` file for every languages of the given `clientName` and builds an hashmap of snippets per operationId per language.
56107
export async function transformGeneratedSnippetsToCodeSamples(clientName: string): Promise<CodeSamples> {
57108
const codeSamples = Object.values(GENERATORS).reduce<CodeSamples>(
@@ -126,5 +177,7 @@ export async function transformGeneratedSnippetsToCodeSamples(clientName: string
126177
}
127178
}
128179

180+
await tagCodeSamples(clientName, codeSamples);
181+
129182
return codeSamples;
130183
}

0 commit comments

Comments
 (0)