Skip to content

Commit 74918b7

Browse files
Fix sample generation for nested @bodyRoot parameters (#3969)
* Fix sample generation for nested @bodyRoot parameters When an operation has a @bodyRoot property nested inside a wrapper model, the sample generator was dropping the body parameter due to a name mismatch between the TCGC body param name and the method signature parameter name. Use bodyParam.methodParameterSegments to detect nested body params and resolve the correct method-level parameter name, wrapping the example value in the appropriate wrapper structure. Fixes #3968 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add a unit test & foramt --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 679311a commit 74918b7

3 files changed

Lines changed: 150 additions & 17 deletions

File tree

packages/typespec-ts/src/modular/emitSamples.ts

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -466,15 +466,47 @@ function prepareExampleParameters(
466466
);
467467
}
468468
} else {
469-
result.push(
470-
prepareExampleValue(
471-
dpgContext,
472-
bodyParam.name,
473-
bodyExample.value,
474-
bodyParam.optional,
475-
bodyParam.onClient
476-
)
477-
);
469+
// Check if the body parameter is nested inside a wrapper (e.g., @bodyRoot)
470+
const segments = bodyParam.methodParameterSegments;
471+
const isNestedBody =
472+
segments.length === 1 &&
473+
segments[0] !== undefined &&
474+
segments[0].length > 1;
475+
if (isNestedBody) {
476+
const path = segments[0]!;
477+
// The first segment is the method-level wrapper param (e.g., "body")
478+
const methodParamName = path[0]!.name;
479+
const methodParamOptional = path[0]!.optional;
480+
// Wrap the example value with the intermediate property names
481+
let wrappedValue = getParameterValue(dpgContext, bodyExample.value);
482+
for (let i = path.length - 1; i >= 1; i--) {
483+
const propName = normalizeName(
484+
path[i]!.name,
485+
NameType.Property,
486+
true
487+
);
488+
wrappedValue = `{ ${propName}: ${wrappedValue} }`;
489+
}
490+
result.push(
491+
prepareExampleValue(
492+
dpgContext,
493+
methodParamName,
494+
wrappedValue,
495+
methodParamOptional,
496+
bodyParam.onClient
497+
)
498+
);
499+
} else {
500+
result.push(
501+
prepareExampleValue(
502+
dpgContext,
503+
bodyParam.name,
504+
bodyExample.value,
505+
bodyParam.optional,
506+
bodyParam.onClient
507+
)
508+
);
509+
}
478510
}
479511
}
480512
// optional parameters

packages/typespec-ts/src/modular/helpers/exampleValueHelpers.ts

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -494,14 +494,41 @@ export function prepareCommonParameters(
494494
);
495495
}
496496
} else {
497-
result.push(
498-
prepareCommonValue(
499-
bodyParam.name,
500-
bodyExample.value,
501-
bodyParam.optional,
502-
bodyParam.onClient
503-
)
504-
);
497+
// Check if the body parameter is nested inside a wrapper (e.g., @bodyRoot)
498+
const segments = bodyParam.methodParameterSegments;
499+
const isNestedBody =
500+
segments.length === 1 &&
501+
segments[0] !== undefined &&
502+
segments[0].length > 1;
503+
if (isNestedBody) {
504+
const path = segments[0]!;
505+
// The first segment is the method-level wrapper param (e.g., "body")
506+
const methodParamName = path[0]!.name;
507+
const methodParamOptional = path[0]!.optional;
508+
// Wrap the example value with the intermediate property names
509+
let wrappedValue = serializeExampleValue(bodyExample.value);
510+
for (let i = path.length - 1; i >= 1; i--) {
511+
const propName = normalizeName(path[i]!.name, NameType.Property);
512+
wrappedValue = `{ ${propName}: ${wrappedValue} }`;
513+
}
514+
result.push(
515+
prepareCommonValue(
516+
methodParamName,
517+
wrappedValue,
518+
methodParamOptional,
519+
bodyParam.onClient
520+
)
521+
);
522+
} else {
523+
result.push(
524+
prepareCommonValue(
525+
bodyParam.name,
526+
bodyExample.value,
527+
bodyParam.optional,
528+
bodyParam.onClient
529+
)
530+
);
531+
}
505532
}
506533
}
507534

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Should generate correct sample for nested @bodyRoot parameter
2+
3+
When a body parameter uses `@bodyRoot` nested inside a wrapper, the sample should
4+
use the wrapper parameter name and nest the body value inside the property structure.
5+
6+
## TypeSpec
7+
8+
This is tsp definition.
9+
10+
```tsp
11+
@doc("Request parameters for stop action.")
12+
model StopParameters {
13+
@doc("The link index.")
14+
linkIndex: string;
15+
@doc("The category.")
16+
category: string;
17+
}
18+
19+
@doc("Stop with nested body root.")
20+
op stop(@path name: string, body: { @bodyRoot stopParameters: StopParameters }): {
21+
@body body: {};
22+
};
23+
```
24+
25+
## Example
26+
27+
Raw json files.
28+
29+
```json
30+
{
31+
"title": "stop",
32+
"operationId": "stop",
33+
"parameters": {
34+
"name": "testResource",
35+
"stopParameters": {
36+
"linkIndex": "link1",
37+
"category": "TestCategory"
38+
}
39+
},
40+
"responses": {
41+
"200": {}
42+
}
43+
}
44+
```
45+
46+
## Samples
47+
48+
Generate correct sample with nested body root parameter:
49+
50+
```ts samples
51+
/** This file path is /samples-dev/stopSample.ts */
52+
import { TestingClient } from "@azure/internal-test";
53+
54+
/**
55+
* This sample demonstrates how to stop with nested body root.
56+
*
57+
* @summary stop with nested body root.
58+
* x-ms-original-file: 2021-10-01-preview/json.json
59+
*/
60+
async function stop(): Promise<void> {
61+
const endpoint = process.env.TESTING_ENDPOINT || "";
62+
const client = new TestingClient(endpoint);
63+
const result = await client.stop("testResource", {
64+
stopParameters: { linkIndex: "link1", category: "TestCategory" },
65+
});
66+
console.log(result);
67+
}
68+
69+
async function main(): Promise<void> {
70+
await stop();
71+
}
72+
73+
main().catch(console.error);
74+
```

0 commit comments

Comments
 (0)