Skip to content

Commit a15862c

Browse files
author
Linh Phan
committed
add nested enums discovered during operation processing to code model
1 parent 3ed063b commit a15862c

2 files changed

Lines changed: 110 additions & 9 deletions

File tree

packages/http-client-csharp/emitter/src/lib/client-model-builder.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,17 +62,26 @@ export function createModel(sdkContext: CSharpEmitterContext): [CodeModel, reado
6262
enums.push(type as InputEnumType);
6363
}
6464
}
65-
// Include models discovered only via operation processing (e.g., anonymous response models
66-
// for protocol-only paging operations where TCGC does not include the response model in
67-
// sdkPackage.models). See https://github.com/microsoft/typespec/issues/9391. Dedupe by
68-
// name to avoid duplicates when TCGC produces a different reference for the same model.
65+
// Include models and enums discovered only via operation processing (e.g., anonymous
66+
// response models for protocol-only paging operations where TCGC does not include the
67+
// response model in sdkPackage.models, or enums only reachable through nested property
68+
// types of such models). See https://github.com/microsoft/typespec/issues/9391. Dedupe
69+
// models by name to avoid duplicates when TCGC produces a different reference for the
70+
// same model.
71+
const existingEnumNames = new Set(enums.map((e) => e.name));
6972
for (const type of sdkContext.__typeCache.types.values()) {
7073
if (typesBeforeClients.has(type)) continue;
71-
if (type.kind !== "model") continue;
72-
const model = type as InputModelType;
73-
if (existingModelNames.has(model.name)) continue;
74-
models.push(model);
75-
existingModelNames.add(model.name);
74+
if (type.kind === "model") {
75+
const model = type as InputModelType;
76+
if (existingModelNames.has(model.name)) continue;
77+
models.push(model);
78+
existingModelNames.add(model.name);
79+
} else if (type.kind === "enum") {
80+
const enumType = type as InputEnumType;
81+
if (existingEnumNames.has(enumType.name)) continue;
82+
enums.push(enumType);
83+
existingEnumNames.add(enumType.name);
84+
}
7685
}
7786

7887
// TODO -- TCGC now does not have constants field in its sdkPackage, they might add it in the future.

packages/http-client-csharp/emitter/test/Unit/operation-paging.test.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,98 @@ describe("Next link operations", () => {
263263
`Expected response model 'LinkResponse' to be present in code model. Found: ${root.models.map((m) => m.name).join(", ")}`,
264264
);
265265
});
266+
267+
it("includes nested enum from protocol-only response model in code model", async () => {
268+
const program = await typeSpecCompile(
269+
`
270+
@convenientAPI(false)
271+
@list
272+
op link(): {
273+
@pageItems
274+
items: Foo[];
275+
276+
@nextLink
277+
next?: url;
278+
};
279+
enum Status { running, completed, failed };
280+
model Foo {
281+
name: string;
282+
status: Status;
283+
};
284+
`,
285+
runner,
286+
{ IsTCGCNeeded: true },
287+
);
288+
const context = createEmitterContext(program);
289+
const sdkContext = await createCSharpSdkContext(context);
290+
const [root] = createModel(sdkContext);
291+
292+
// The enum used as a property type of a model only reachable via a
293+
// protocol-only paging operation must be included in the code model's
294+
// enums list, not just the models list.
295+
const statusEnum = root.enums.find((e) => e.name === "Status");
296+
ok(
297+
statusEnum,
298+
`Expected enum 'Status' to be present in code model enums. Found: ${root.enums.map((e) => e.name).join(", ")}`,
299+
);
300+
301+
// The model itself should also be present
302+
const fooModel = root.models.find((m) => m.name === "Foo");
303+
ok(
304+
fooModel,
305+
`Expected model 'Foo' to be present in code model models. Found: ${root.models.map((m) => m.name).join(", ")}`,
306+
);
307+
});
308+
309+
it("includes deeply nested enum from protocol-only response model in code model", async () => {
310+
const program = await typeSpecCompile(
311+
`
312+
@convenientAPI(false)
313+
@list
314+
op link(): {
315+
@pageItems
316+
items: Foo[];
317+
318+
@nextLink
319+
next?: url;
320+
};
321+
enum Priority { low, medium, high };
322+
model Bar {
323+
priority: Priority;
324+
};
325+
model Foo {
326+
name: string;
327+
bar: Bar;
328+
};
329+
`,
330+
runner,
331+
{ IsTCGCNeeded: true },
332+
);
333+
const context = createEmitterContext(program);
334+
const sdkContext = await createCSharpSdkContext(context);
335+
const [root] = createModel(sdkContext);
336+
337+
// An enum nested two levels deep (Foo -> Bar -> Priority) must still
338+
// be captured when the entire graph is only reachable via a
339+
// protocol-only paging operation.
340+
const priorityEnum = root.enums.find((e) => e.name === "Priority");
341+
ok(
342+
priorityEnum,
343+
`Expected enum 'Priority' to be present in code model enums. Found: ${root.enums.map((e) => e.name).join(", ")}`,
344+
);
345+
346+
const barModel = root.models.find((m) => m.name === "Bar");
347+
ok(
348+
barModel,
349+
`Expected model 'Bar' to be present in code model models. Found: ${root.models.map((m) => m.name).join(", ")}`,
350+
);
351+
352+
const fooModel = root.models.find((m) => m.name === "Foo");
353+
ok(
354+
fooModel,
355+
`Expected model 'Foo' to be present in code model models. Found: ${root.models.map((m) => m.name).join(", ")}`,
356+
);
357+
});
266358
});
267359

268360
describe("Continuation token operations", () => {

0 commit comments

Comments
 (0)