diff --git a/packages/typespec-ts/src/modular/emitSamples.ts b/packages/typespec-ts/src/modular/emitSamples.ts index 349ed44d33..6042e54c68 100644 --- a/packages/typespec-ts/src/modular/emitSamples.ts +++ b/packages/typespec-ts/src/modular/emitSamples.ts @@ -10,7 +10,6 @@ import { SdkHttpOperationExample, SdkHttpParameterExampleValue, SdkInitializationType, - SdkServiceMethod, SdkServiceOperation, SdkExampleValue } from "@azure-tools/typespec-client-generator-core"; @@ -33,6 +32,10 @@ import { hasKeyCredential, hasTokenCredential } from "../utils/credentialUtils.js"; +import { + getMethodHierarchiesMap, + ServiceOperation +} from "../utils/operationUtil.js"; /** * Interfaces for samples generations @@ -74,37 +77,26 @@ function emitClientSamples( client: SdkClientType, options: EmitSampleOptions ) { - for (const operationOrGroup of client.methods) { - // handle client-level methods - if (operationOrGroup.kind !== "clientaccessor") { - emitMethodSamples(dpgContext, operationOrGroup, options); - continue; - } - // handle operation group - let prefix = normalizeName( - operationOrGroup.response.name, - NameType.Property - ); - // append hierarchy prefix if hierarchyClient is enabled - if (dpgContext.rlcOptions?.hierarchyClient === true) { - prefix = - (options.classicalMethodPrefix - ? `${options.classicalMethodPrefix}.` - : "") + prefix; - } else if (dpgContext.rlcOptions?.enableOperationGroup === false) { - prefix = ""; + const methodMap = getMethodHierarchiesMap(dpgContext, client); + for (const [prefixKey, operations] of methodMap) { + const prefix = prefixKey + .split("/") + .map((name) => { + return normalizeName(name, NameType.Property); + }) + .join("."); + for (const op of operations) { + emitMethodSamples(dpgContext, op, { + ...options, + classicalMethodPrefix: prefix + }); } - - emitClientSamples(dpgContext, operationOrGroup.response, { - ...options, - classicalMethodPrefix: prefix - }); } } function emitMethodSamples( dpgContext: SdkContext, - method: SdkServiceMethod, + method: ServiceOperation, options: EmitSampleOptions ): SourceFile | undefined { const examples = method.operation.examples ?? []; @@ -113,7 +105,7 @@ function emitMethodSamples( } const project = useContext("outputProject"); const operationPrefix = `${options.classicalMethodPrefix ?? ""} ${ - method.name + method.oriName ?? method.name }`; const sampleFolder = join( dpgContext.generationPathDetail?.rootDir ?? "", @@ -194,7 +186,7 @@ function emitMethodSamples( ? `${options.classicalMethodPrefix}.` : ""; const isPaging = method.kind === "paging"; - const methodCall = `client.${prefix}${method.name}(${methodParams.join( + const methodCall = `client.${prefix}${normalizeName(method.oriName ?? method.name, NameType.Property)}(${methodParams.join( ", " )})`; if (isPaging) { @@ -212,7 +204,8 @@ function emitMethodSamples( } // Create a function declaration structure - const description = method.doc ?? `execute ${method.name}`; + const description = + method.doc ?? `execute ${method.oriName ?? method.name}`; const normalizedDescription = description.charAt(0).toLowerCase() + description.slice(1); const functionDeclaration: FunctionDeclarationStructure = { @@ -252,7 +245,7 @@ function buildParameterValueMap(example: SdkHttpOperationExample) { function prepareExampleParameters( dpgContext: SdkContext, - method: SdkServiceMethod, + method: ServiceOperation, parameterMap: Record, topLevelClient: SdkClientType ): ExampleValue[] { @@ -284,7 +277,7 @@ function prepareExampleParameters( reportDiagnostic(dpgContext.program, { code: "required-sample-parameter", format: { - exampleName: method.name, + exampleName: method.oriName ?? method.name, paramName: param.name }, target: NoTarget diff --git a/packages/typespec-ts/src/utils/operationUtil.ts b/packages/typespec-ts/src/utils/operationUtil.ts index 9958b8ce22..e94921ad9e 100644 --- a/packages/typespec-ts/src/utils/operationUtil.ts +++ b/packages/typespec-ts/src/utils/operationUtil.ts @@ -576,7 +576,12 @@ export function getMethodHierarchiesMap( if (!method) { continue; } - const prefixes = method[0]; + const prefixes = + context.rlcOptions?.hierarchyClient === false && + context.rlcOptions?.enableOperationGroup && + method[0].length > 0 + ? [method[0][method[0].length - 1] as string] + : method[0]; const operationOrGroup = method[1]; if (operationOrGroup.kind === "clientaccessor") { diff --git a/packages/typespec-ts/test/modularUnit/scenarios/samples/operations/armCurdOperations.md b/packages/typespec-ts/test/modularUnit/scenarios/samples/operations/armCurdOperations.md index d386c36bbe..f664e5da23 100644 --- a/packages/typespec-ts/test/modularUnit/scenarios/samples/operations/armCurdOperations.md +++ b/packages/typespec-ts/test/modularUnit/scenarios/samples/operations/armCurdOperations.md @@ -88,6 +88,13 @@ interface Employees { } ``` +This is the tspconfig.yaml. + +```yaml +hierarchyClient: false +enableOperationGroup: true +``` + ## Example Raw json files. diff --git a/packages/typespec-ts/test/modularUnit/scenarios/samples/operations/disableHierarchyArmCurdOperations.md b/packages/typespec-ts/test/modularUnit/scenarios/samples/operations/disableHierarchyArmCurdOperations.md new file mode 100644 index 0000000000..a7d6cef7ac --- /dev/null +++ b/packages/typespec-ts/test/modularUnit/scenarios/samples/operations/disableHierarchyArmCurdOperations.md @@ -0,0 +1,256 @@ +# Should generate samples for ARM operations disabled hierarchy client + +Sample generation should arm template and operations successfully disabled hierarchy client. + +## TypeSpec + +This is tsp definition. + +```tsp +import "@typespec/http"; +import "@typespec/rest"; +import "@typespec/versioning"; +import "@azure-tools/typespec-azure-core"; +import "@azure-tools/typespec-azure-resource-manager"; + +using TypeSpec.Http; +using TypeSpec.Rest; +using TypeSpec.Versioning; +using Azure.Core; +using Azure.ResourceManager; + +/** Microsoft.Contoso Resource Provider management API. */ +@armProviderNamespace +@service({ + title: "Microsoft.Contoso management service", +}) +@versioned(Microsoft.Contoso.Versions) +namespace Microsoft.Contoso; + +/** The available API versions. */ +enum Versions { + /** 2021-10-01-preview version */ + @useDependency(Azure.ResourceManager.Versions.v1_0_Preview_1) + @useDependency(Azure.Core.Versions.v1_0_Preview_2) + @armCommonTypesVersion(Azure.ResourceManager.CommonTypes.Versions.v5) + v2021_10_01_preview: "2021-10-01-preview", +} + +interface Operations extends Azure.ResourceManager.Operations {} + +/** Employee resource */ +model Employee is TrackedResource { + ...ResourceNameParameter; +} + +/** Employee properties */ +model EmployeeProperties { + /** Age of employee */ + age?: int32; + + /** City of employee */ + city?: string; + + /** Profile of employee */ + @encode("base64url") + profile?: bytes; + + /** The status of the last operation. */ + @visibility("read") + provisioningState?: ProvisioningState; +} + +/** The resource provisioning state. */ +@lroStatus +union ProvisioningState { + ResourceProvisioningState, + + /** The resource is being provisioned */ + Provisioning: "Provisioning", + + /** The resource is updating */ + Updating: "Updating", + + /** The resource is being deleted */ + Deleting: "Deleting", + + /** The resource create request has been accepted */ + Accepted: "Accepted", + + string, +} + +@armResourceOperations +interface Employees { + get is ArmResourceRead; + createOrUpdate is ArmResourceCreateOrReplaceAsync; + delete is ArmResourceDeleteWithoutOkAsync; +} +``` + +This is the tspconfig.yaml. + +```yaml +hierarchyClient: false +``` + +## Example + +Raw json files. + +```json for Operations_List +{ + "title": "Operations_List", + "operationId": "Operations_List", + "parameters": { + "api-version": "2021-10-01-preview" + }, + "responses": { + "200": { + "body": {} + } + } +} +``` + +Generate samples for arm cases: + +```ts samples +/** This file path is /samples-dev/listSample.ts */ +import { ContosoClient } from "@azure/internal-test"; +import { DefaultAzureCredential } from "@azure/identity"; + +/** + * This sample demonstrates how to list the operations for the provider + * + * @summary list the operations for the provider + * x-ms-original-file: 2021-10-01-preview/json_for_Operations_List.json + */ +async function operationsList(): Promise { + const credential = new DefaultAzureCredential(); + const subscriptionId = "00000000-0000-0000-0000-00000000000"; + const client = new ContosoClient(credential, subscriptionId); + const resArray = new Array(); + for await (let item of client.list()) { + resArray.push(item); + } + + console.log(resArray); +} + +async function main(): Promise { + await operationsList(); +} + +main().catch(console.error); +``` + +Raw json files. + +```json for Employees_CreateOrUpdate +{ + "title": "Employees_CreateOrUpdate", + "operationId": "Employees_CreateOrUpdate", + "parameters": { + "api-version": "2021-10-01-preview", + "subscriptionId": "11809CA1-E126-4017-945E-AA795CD5C5A9", + "resourceGroupName": "rgopenapi", + "employeeName": "9KF-f-8b", + "resource": { + "properties": { + "age": 30, + "city": "gydhnntudughbmxlkyzrskcdkotrxn", + "profile": "ms" + }, + "tags": { + "key2913": "urperxmkkhhkp" + }, + "location": "itajgxyqozseoygnl" + } + }, + "responses": { + "200": {} + } +} +``` + +Generate samples for arm cases: + +```ts samples +/** This file path is /samples-dev/createOrUpdateSample.ts */ +import { ContosoClient } from "@azure/internal-test"; +import { DefaultAzureCredential } from "@azure/identity"; + +/** + * This sample demonstrates how to create a Employee + * + * @summary create a Employee + * x-ms-original-file: 2021-10-01-preview/json_for_Employees_CreateOrUpdate.json + */ +async function employeesCreateOrUpdate(): Promise { + const credential = new DefaultAzureCredential(); + const subscriptionId = "11809CA1-E126-4017-945E-AA795CD5C5A9"; + const client = new ContosoClient(credential, subscriptionId); + const result = await client.createOrUpdate("rgopenapi", "9KF-f-8b", { + properties: { + age: 30, + city: "gydhnntudughbmxlkyzrskcdkotrxn", + profile: "ms", + }, + tags: { key2913: "urperxmkkhhkp" }, + location: "itajgxyqozseoygnl", + }); + console.log(result); +} + +async function main(): Promise { + await employeesCreateOrUpdate(); +} + +main().catch(console.error); +``` + +Raw json files. + +```json for Employees_Delete +{ + "title": "Employees_Delete", + "operationId": "Employees_Delete", + "parameters": { + "api-version": "2021-10-01-preview", + "subscriptionId": "11809CA1-E126-4017-945E-AA795CD5C5A9", + "resourceGroupName": "rgopenapi", + "employeeName": "5vX--BxSu3ux48rI4O9OQ569" + }, + "responses": { + "202": {} + } +} +``` + +Generate samples for arm cases: + +```ts samples +/** This file path is /samples-dev/deleteSample.ts */ +import { ContosoClient } from "@azure/internal-test"; +import { DefaultAzureCredential } from "@azure/identity"; + +/** + * This sample demonstrates how to delete a Employee + * + * @summary delete a Employee + * x-ms-original-file: 2021-10-01-preview/json_for_Employees_Delete.json + */ +async function employeesDelete(): Promise { + const credential = new DefaultAzureCredential(); + const subscriptionId = "11809CA1-E126-4017-945E-AA795CD5C5A9"; + const client = new ContosoClient(credential, subscriptionId); + await client.delete("rgopenapi", "5vX--BxSu3ux48rI4O9OQ569"); +} + +async function main(): Promise { + await employeesDelete(); +} + +main().catch(console.error); +``` diff --git a/packages/typespec-ts/test/modularUnit/scenarios/samples/operations/dpgCurdOperations.md b/packages/typespec-ts/test/modularUnit/scenarios/samples/operations/dpgCurdOperations.md index a2bf2f7297..cbd04f6ec0 100644 --- a/packages/typespec-ts/test/modularUnit/scenarios/samples/operations/dpgCurdOperations.md +++ b/packages/typespec-ts/test/modularUnit/scenarios/samples/operations/dpgCurdOperations.md @@ -121,32 +121,45 @@ Raw json files. } ``` +This is the tspconfig.yaml. + +```yaml +hierarchyClient: true +enableOperationGroup: false +``` + ## Samples Generate samples for dpg cases: ```ts samples -/** This file path is /samples-dev/widgetsCreateOrUpdateWidgetSample.ts */ +/** This file path is /samples-dev/widgetsListWidgetsSample.ts */ import { WidgetManagerClient } from "@azure/internal-test"; import { DefaultAzureCredential } from "@azure/identity"; /** - * This sample demonstrates how to creates or updates a Widget asynchronously. + * This sample demonstrates how to list Widget resources * - * @summary creates or updates a Widget asynchronously. - * x-ms-original-file: 2021-10-01-preview/json_for_Widgets_CreateOrUpdateWidget.json + * @summary list Widget resources + * x-ms-original-file: 2021-10-01-preview/json_for_Widgets_ListWidgets.json */ -async function widgetsCreateOrUpdateWidget(): Promise { +async function widgetsListWidgets(): Promise { const credential = new DefaultAzureCredential(); const client = new WidgetManagerClient(credential); - const result = await client.widgets.createOrUpdateWidget("name1", { - manufacturerId: "manufacturer id1", - }); - console.log(result); + const resArray = new Array(); + for await (let item of client.widgets.listWidgets({ + top: 8, + skip: 15, + maxpagesize: 27, + })) { + resArray.push(item); + } + + console.log(resArray); } async function main(): Promise { - await widgetsCreateOrUpdateWidget(); + await widgetsListWidgets(); } main().catch(console.error); @@ -174,33 +187,27 @@ async function main(): Promise { main().catch(console.error); -/** This file path is /samples-dev/widgetsListWidgetsSample.ts */ +/** This file path is /samples-dev/widgetsCreateOrUpdateWidgetSample.ts */ import { WidgetManagerClient } from "@azure/internal-test"; import { DefaultAzureCredential } from "@azure/identity"; /** - * This sample demonstrates how to list Widget resources + * This sample demonstrates how to creates or updates a Widget asynchronously. * - * @summary list Widget resources - * x-ms-original-file: 2021-10-01-preview/json_for_Widgets_ListWidgets.json + * @summary creates or updates a Widget asynchronously. + * x-ms-original-file: 2021-10-01-preview/json_for_Widgets_CreateOrUpdateWidget.json */ -async function widgetsListWidgets(): Promise { +async function widgetsCreateOrUpdateWidget(): Promise { const credential = new DefaultAzureCredential(); const client = new WidgetManagerClient(credential); - const resArray = new Array(); - for await (let item of client.widgets.listWidgets({ - top: 8, - skip: 15, - maxpagesize: 27, - })) { - resArray.push(item); - } - - console.log(resArray); + const result = await client.widgets.createOrUpdateWidget("name1", { + manufacturerId: "manufacturer id1", + }); + console.log(result); } async function main(): Promise { - await widgetsListWidgets(); + await widgetsCreateOrUpdateWidget(); } main().catch(console.error); diff --git a/packages/typespec-ts/test/modularUnit/scenarios/samples/operations/multipleExamplesInOneFile.md b/packages/typespec-ts/test/modularUnit/scenarios/samples/operations/multipleExamplesInOneFile.md index 345b92dc7a..9c71aa3289 100644 --- a/packages/typespec-ts/test/modularUnit/scenarios/samples/operations/multipleExamplesInOneFile.md +++ b/packages/typespec-ts/test/modularUnit/scenarios/samples/operations/multipleExamplesInOneFile.md @@ -149,7 +149,7 @@ Raw json files. Generate one file for multiple samples: ```ts samples -/** This file path is /samples-dev/employeesCreateOrUpdateSample.ts */ +/** This file path is /samples-dev/createOrUpdateSample.ts */ import { ContosoClient } from "@azure/internal-test"; import { DefaultAzureCredential } from "@azure/identity"; @@ -163,19 +163,15 @@ async function employeesCreateOrUpdateMaxage(): Promise { const credential = new DefaultAzureCredential(); const subscriptionId = "11809CA1-E126-4017-945E-AA795CD5C5A9"; const client = new ContosoClient(credential, subscriptionId); - const result = await client.employees.createOrUpdate( - "rgopenapi", - "9KF-f-8b", - { - properties: { - age: 110, - city: "gydhnntudughbmxlkyzrskcdkotrxn", - profile: "ms", - }, - tags: { key2913: "urperxmkkhhkp" }, - location: "itajgxyqozseoygnl", + const result = await client.createOrUpdate("rgopenapi", "9KF-f-8b", { + properties: { + age: 110, + city: "gydhnntudughbmxlkyzrskcdkotrxn", + profile: "ms", }, - ); + tags: { key2913: "urperxmkkhhkp" }, + location: "itajgxyqozseoygnl", + }); console.log(result); } @@ -189,19 +185,15 @@ async function employeesCreateOrUpdateMinage(): Promise { const credential = new DefaultAzureCredential(); const subscriptionId = "11809CA1-E126-4017-945E-AA795CD5C5A9"; const client = new ContosoClient(credential, subscriptionId); - const result = await client.employees.createOrUpdate( - "rgopenapi", - "9KF-f-8b", - { - properties: { - age: 1, - city: "gydhnntudughbmxlkyzrskcdkotrxn", - profile: "ms", - }, - tags: { key2913: "urperxmkkhhkp" }, - location: "itajgxyqozseoygnl", + const result = await client.createOrUpdate("rgopenapi", "9KF-f-8b", { + properties: { + age: 1, + city: "gydhnntudughbmxlkyzrskcdkotrxn", + profile: "ms", }, - ); + tags: { key2913: "urperxmkkhhkp" }, + location: "itajgxyqozseoygnl", + }); console.log(result); }