diff --git a/.chronus/changes/spread_model-2024-7-16-15-0-33.md b/.chronus/changes/spread_model-2024-7-16-15-0-33.md new file mode 100644 index 0000000000..cfb140f668 --- /dev/null +++ b/.chronus/changes/spread_model-2024-7-16-15-0-33.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@azure-tools/typespec-client-generator-core" +--- + +use original model for spread if it is from a simple spread \ No newline at end of file diff --git a/packages/typespec-client-generator-core/src/http.ts b/packages/typespec-client-generator-core/src/http.ts index d6dde6d8a2..dedf31370d 100644 --- a/packages/typespec-client-generator-core/src/http.ts +++ b/packages/typespec-client-generator-core/src/http.ts @@ -1,5 +1,6 @@ import { Diagnostic, + Model, ModelProperty, Operation, Type, @@ -46,11 +47,13 @@ import { import { getAvailableApiVersions, getDocHelper, + getHttpBodySpreadModel, getHttpOperationResponseHeaders, getLocationOfOperation, getTypeDecorators, isAcceptHeader, isContentTypeHeader, + isHttpBodySpread, isNeverOrVoidType, isSubscriptionId, } from "./internal-utils.js"; @@ -155,9 +158,21 @@ function getSdkHttpParameters( } retval.bodyParam = bodyParam; } else if (!isNeverOrVoidType(tspBody.type)) { - const type = diagnostics.pipe( - getClientTypeWithDiagnostics(context, tspBody.type, httpOperation.operation) - ); + const spread = isHttpBodySpread(tspBody); + let type: SdkType; + if (spread) { + type = diagnostics.pipe( + getClientTypeWithDiagnostics( + context, + getHttpBodySpreadModel(tspBody.type as Model), + httpOperation.operation + ) + ); + } else { + type = diagnostics.pipe( + getClientTypeWithDiagnostics(context, tspBody.type, httpOperation.operation) + ); + } const name = camelCase((type as { name: string }).name ?? "body"); retval.bodyParam = { kind: "body", @@ -575,8 +590,7 @@ function findMapping( if ( methodParam.__raw && serviceParam.__raw && - (methodParam.__raw === serviceParam.__raw || - methodParam.__raw === serviceParam.__raw.sourceProperty) + methodParam.__raw.node === serviceParam.__raw.node ) { return methodParam; } diff --git a/packages/typespec-client-generator-core/src/interfaces.ts b/packages/typespec-client-generator-core/src/interfaces.ts index fec31472db..e7ecd615c2 100644 --- a/packages/typespec-client-generator-core/src/interfaces.ts +++ b/packages/typespec-client-generator-core/src/interfaces.ts @@ -37,7 +37,6 @@ export interface TCGCContext { modelsMap?: Map; operationModelsMap?: Map>; generatedNames?: Map; - spreadModels?: Map; httpOperationCache?: Map; unionsMap?: Map; __namespaceToApiVersionParameter: Map; diff --git a/packages/typespec-client-generator-core/src/internal-utils.ts b/packages/typespec-client-generator-core/src/internal-utils.ts index 156c1fcc5a..66185e2dd8 100644 --- a/packages/typespec-client-generator-core/src/internal-utils.ts +++ b/packages/typespec-client-generator-core/src/internal-utils.ts @@ -10,6 +10,7 @@ import { isNeverType, isNullType, isVoidType, + Model, ModelProperty, Namespace, Numeric, @@ -21,7 +22,13 @@ import { Union, Value, } from "@typespec/compiler"; -import { HttpOperation, HttpOperationResponseContent, HttpStatusCodeRange } from "@typespec/http"; +import { + HttpOperation, + HttpOperationBody, + HttpOperationMultipartBody, + HttpOperationResponseContent, + HttpStatusCodeRange, +} from "@typespec/http"; import { getAddedOnVersions, getRemovedOnVersions, getVersions } from "@typespec/versioning"; import { DecoratorInfo, @@ -546,3 +553,38 @@ export function isXmlContentType(contentType: string): boolean { const regex = new RegExp(/^(application|text)\/(.+\+)?xml$/); return regex.test(contentType); } + +/** + * If body is from spread, then it does not directly from a model property. + * @param httpBody + * @param parameters + * @returns + */ +export function isHttpBodySpread(httpBody: HttpOperationBody | HttpOperationMultipartBody) { + return httpBody.property === undefined; +} + +/** + * If body is from simple spread, then we use the original model as body model. + * @param type + * @returns + */ +export function getHttpBodySpreadModel(type: Model): Model { + if (type.sourceModels.length === 1 && type.sourceModels[0].usage === "spread") { + const innerModel = type.sourceModels[0].model; + // for case: `op test(...Model):void;` + if (innerModel.name !== "" && innerModel.properties.size === type.properties.size) { + return innerModel; + } + // for case: `op test(@header h: string, @query q: string, ...Model): void;` + if ( + innerModel.sourceModels.length === 1 && + innerModel.sourceModels[0].usage === "spread" && + innerModel.sourceModels[0].model.name !== "" && + innerModel.sourceModels[0].model.properties.size === type.properties.size + ) { + return innerModel.sourceModels[0].model; + } + } + return type; +} diff --git a/packages/typespec-client-generator-core/src/public-utils.ts b/packages/typespec-client-generator-core/src/public-utils.ts index f3633cd07b..dda9f00a76 100644 --- a/packages/typespec-client-generator-core/src/public-utils.ts +++ b/packages/typespec-client-generator-core/src/public-utils.ts @@ -37,7 +37,9 @@ import { import { TspLiteralType, getClientNamespaceStringHelper, + getHttpBodySpreadModel, getHttpOperationResponseHeaders, + isHttpBodySpread, parseEmitterName, removeVersionsLargerThanExplicitlySpecified, } from "./internal-utils.js"; @@ -379,7 +381,13 @@ function getContextPath( if (httpOperation.parameters.body) { visited.clear(); result = [{ name: root.name }]; - if (dfsModelProperties(typeToFind, httpOperation.parameters.body.type, "Request")) { + let bodyType: Type; + if (isHttpBodySpread(httpOperation.parameters.body)) { + bodyType = getHttpBodySpreadModel(httpOperation.parameters.body.type as Model); + } else { + bodyType = httpOperation.parameters.body.type; + } + if (dfsModelProperties(typeToFind, bodyType, "Request")) { return result; } } diff --git a/packages/typespec-client-generator-core/src/types.ts b/packages/typespec-client-generator-core/src/types.ts index 1a632da210..30baf2036e 100644 --- a/packages/typespec-client-generator-core/src/types.ts +++ b/packages/typespec-client-generator-core/src/types.ts @@ -87,6 +87,7 @@ import { filterApiVersionsInEnum, getAvailableApiVersions, getDocHelper, + getHttpBodySpreadModel, getHttpOperationResponseHeaders, getLocationOfOperation, getNonNullOptions, @@ -95,6 +96,7 @@ import { getTypeDecorators, intOrFloat, isAzureCoreModel, + isHttpBodySpread, isJsonContentType, isMultipartFormData, isMultipartOperation, @@ -1378,15 +1380,17 @@ export function getSdkModelPropertyType( isMultipartFileInput: false, flatten: shouldFlattenProperty(context, type), }; - if (operation) { + if (operation && type.model) { const httpOperation = getHttpOperationWithCache(context, operation); - if ( - type.model && - httpOperation.parameters.body && - httpOperation.parameters.body.type === type.model - ) { - // only add multipartOptions for property of multipart body - diagnostics.pipe(updateMultiPartInfo(context, type, result, operation)); + const httpBody = httpOperation.parameters.body; + if (httpBody) { + const httpBodyType = isHttpBodySpread(httpBody) + ? getHttpBodySpreadModel(httpBody.type as Model) + : httpBody.type; + if (type.model === httpBodyType) { + // only try to add multipartOptions for property of body + diagnostics.pipe(updateMultiPartInfo(context, type, result, operation)); + } } } return diagnostics.wrap(result); @@ -1491,6 +1495,7 @@ function updateModelsMap( interface ModelUsageOptions { seenModelNames?: Set; propagation?: boolean; + skipFirst?: boolean; // this is used to prevent propagation usage from subtype to base type's other subtypes ignoreSubTypeStack?: boolean[]; } @@ -1529,11 +1534,15 @@ function updateUsageOfModel( if (type.kind !== "model" && type.kind !== "enum") return; options.seenModelNames.add(type); - const usageOverride = getUsageOverride(context, type.__raw as any); - if (usageOverride) { - type.usage |= usageOverride | usage; + if (!options.skipFirst) { + const usageOverride = getUsageOverride(context, type.__raw as any); + if (usageOverride) { + type.usage |= usageOverride | usage; + } else { + type.usage |= usage; + } } else { - type.usage |= usage; + options.skipFirst = false; } if (type.kind === "enum") return; @@ -1595,32 +1604,27 @@ function updateTypesFromOperation( } const httpBody = httpOperation.parameters.body; if (httpBody && !isNeverOrVoidType(httpBody.type)) { - const sdkType = diagnostics.pipe( - getClientTypeWithDiagnostics(context, httpBody.type, operation) - ); - if (generateConvenient) { - // Special logic for spread body model: - // If body is from spread, then it should be an anonymous model. - // Also all model properties should be - // either equal to one of operation parameters (for case spread from model without property with metadata decorator) - // or its source property equal to one of operation parameters (for case spread from model with property with metadata decorator) - if ( - httpBody.type.kind === "Model" && - httpBody.type.name === "" && - [...httpBody.type.properties.keys()].every( - (k) => - operation.parameters.properties.has(k) && - (operation.parameters.properties.get(k) === - (httpBody.type as Model).properties.get(k) || - operation.parameters.properties.get(k) === - (httpBody.type as Model).properties.get(k)?.sourceProperty) + const spread = isHttpBodySpread(httpBody); + let sdkType: SdkType; + if (spread) { + sdkType = diagnostics.pipe( + getClientTypeWithDiagnostics( + context, + getHttpBodySpreadModel(httpBody.type as Model), + operation ) - ) { - if (!context.spreadModels?.has(httpBody.type)) { - context.spreadModels?.set(httpBody.type as Model, sdkType as SdkModelType); - } + ); + } else { + sdkType = diagnostics.pipe(getClientTypeWithDiagnostics(context, httpBody.type, operation)); + } + if (generateConvenient) { + if (spread) { + updateUsageOfModel(context, UsageFlags.Spread, sdkType, { propagation: false }); + updateUsageOfModel(context, UsageFlags.Input, sdkType, { skipFirst: true }); + } else { + updateUsageOfModel(context, UsageFlags.Input, sdkType); } - updateUsageOfModel(context, UsageFlags.Input, sdkType); + if (httpBody.contentTypes.some((x) => isJsonContentType(x))) { updateUsageOfModel(context, UsageFlags.Json, sdkType); } @@ -1725,10 +1729,14 @@ function updateAccessOfModel(context: TCGCContext): void { } function updateSpreadModelUsageAndAccess(context: TCGCContext): void { - for (const sdkType of context.spreadModels?.values() ?? []) { - // if a type has spread usage, then it must be internal - sdkType.access = "internal"; - sdkType.usage = (sdkType.usage & ~UsageFlags.Input) | UsageFlags.Spread; + for (const [_, sdkType] of context.modelsMap?.entries() ?? []) { + if ( + (sdkType.usage & UsageFlags.Spread) > 0 && + (sdkType.usage & (UsageFlags.Input | UsageFlags.Output)) === 0 + ) { + // if a type has spread usage, but not used in any other operation, then set it to be internal + sdkType.access = "internal"; + } } } @@ -1811,9 +1819,6 @@ export function getAllModelsWithDiagnostics( if (context.operationModelsMap === undefined) { context.operationModelsMap = new Map>(); } - if (context.spreadModels === undefined) { - context.spreadModels = new Map(); - } for (const client of listClients(context)) { for (const operation of listOperationsInOperationGroup(context, client)) { // operations on a client diff --git a/packages/typespec-client-generator-core/test/decorators.test.ts b/packages/typespec-client-generator-core/test/decorators.test.ts index 8ef8609e50..689009bf07 100644 --- a/packages/typespec-client-generator-core/test/decorators.test.ts +++ b/packages/typespec-client-generator-core/test/decorators.test.ts @@ -4031,14 +4031,10 @@ describe("typespec-client-generator-core: decorators", () => { "stableFunctionality" ); strictEqual(runnerWithVersion.context.sdkPackage.models.length, 2); - strictEqual( - runnerWithVersion.context.sdkPackage.models[0].name, - "PreviewFunctionalityRequest" - ); - strictEqual( - runnerWithVersion.context.sdkPackage.models[1].name, - "StableFunctionalityRequest" - ); + strictEqual(runnerWithVersion.context.sdkPackage.models[0].name, "PreviewModel"); + strictEqual(runnerWithVersion.context.sdkPackage.models[0].access, "internal"); + strictEqual(runnerWithVersion.context.sdkPackage.models[1].name, "StableModel"); + strictEqual(runnerWithVersion.context.sdkPackage.models[1].access, "internal"); runnerWithVersion = await createSdkTestRunner({ emitterName: "@azure-tools/typespec-python", @@ -4053,9 +4049,11 @@ describe("typespec-client-generator-core: decorators", () => { "stableFunctionality" ); strictEqual(runnerWithVersion.context.sdkPackage.models.length, 1); + strictEqual(runnerWithVersion.context.sdkPackage.models[0].name, "StableModel"); + strictEqual(runnerWithVersion.context.sdkPackage.models[0].access, "internal"); strictEqual( - runnerWithVersion.context.sdkPackage.models[0].name, - "StableFunctionalityRequest" + runnerWithVersion.context.sdkPackage.models[0].usage, + UsageFlags.Spread | UsageFlags.Json ); }); it("add client", async () => { @@ -4549,7 +4547,7 @@ describe("typespec-client-generator-core: decorators", () => { await runner.compileWithCustomization(mainCode, customizationCode); // runner has python scope, so shouldn't be overridden - ok(!runner.context.sdkPackage.models.find((x) => x.name === "Params")); + ok(runner.context.sdkPackage.models.find((x) => x.name === "Params")); const sdkPackage = runner.context.sdkPackage; const client = sdkPackage.clients[0]; strictEqual(client.methods.length, 1); diff --git a/packages/typespec-client-generator-core/test/package.test.ts b/packages/typespec-client-generator-core/test/package.test.ts index 4e70085e27..50229ea0d4 100644 --- a/packages/typespec-client-generator-core/test/package.test.ts +++ b/packages/typespec-client-generator-core/test/package.test.ts @@ -12,9 +12,7 @@ import { SdkHttpOperation, SdkPackage, SdkServiceMethod, - UsageFlags, } from "../src/interfaces.js"; -import { getAllModels } from "../src/types.js"; import { SdkTestRunner, createSdkTestRunner } from "./test-host.js"; describe("typespec-client-generator-core: package", () => { @@ -2105,809 +2103,6 @@ describe("typespec-client-generator-core: package", () => { }); }); - describe("spread", () => { - it("plain model with no decorators", async () => { - await runner.compile(`@server("http://localhost:3000", "endpoint") - @service({}) - namespace My.Service; - - model Input { - key: string; - } - - op myOp(...Input): void; - `); - const sdkPackage = runner.context.sdkPackage; - const method = getServiceMethodOfClient(sdkPackage); - strictEqual(method.name, "myOp"); - strictEqual(method.kind, "basic"); - strictEqual(method.parameters.length, 2); - - const methodParam = method.parameters.find((x) => x.name === "key"); - ok(methodParam); - strictEqual(methodParam.kind, "method"); - strictEqual(methodParam.optional, false); - strictEqual(methodParam.onClient, false); - strictEqual(methodParam.isApiVersionParam, false); - strictEqual(methodParam.type.kind, "string"); - - const contentTypeParam = method.parameters.find((x) => x.name === "contentType"); - ok(contentTypeParam); - strictEqual(contentTypeParam.clientDefaultValue, undefined); - strictEqual(contentTypeParam.type.kind, "constant"); - strictEqual(contentTypeParam.onClient, false); - - const serviceOperation = method.operation; - const bodyParameter = serviceOperation.bodyParam; - ok(bodyParameter); - - strictEqual(bodyParameter.kind, "body"); - deepStrictEqual(bodyParameter.contentTypes, ["application/json"]); - strictEqual(bodyParameter.defaultContentType, "application/json"); - strictEqual(bodyParameter.onClient, false); - strictEqual(bodyParameter.optional, false); - strictEqual(bodyParameter.type, sdkPackage.models[0]); - strictEqual(bodyParameter.type.usage, UsageFlags.Spread | UsageFlags.Json); - strictEqual(bodyParameter.type.access, "internal"); - - const correspondingMethodParams = bodyParameter.correspondingMethodParams; - strictEqual(correspondingMethodParams.length, 1); - strictEqual(correspondingMethodParams[0].name, "key"); - }); - - it("alias with no decorators", async () => { - await runner.compile(`@server("http://localhost:3000", "endpoint") - @service({}) - namespace My.Service; - - alias BodyParameter = { - name: string; - }; - - op myOp(...BodyParameter): void; - `); - const sdkPackage = runner.context.sdkPackage; - strictEqual(sdkPackage.models.length, 1); - - const method = getServiceMethodOfClient(sdkPackage); - strictEqual(method.name, "myOp"); - strictEqual(method.kind, "basic"); - strictEqual(method.parameters.length, 2); - - const methodParam = method.parameters.find((x) => x.name === "name"); - ok(methodParam); - strictEqual(methodParam.kind, "method"); - strictEqual(methodParam.optional, false); - strictEqual(methodParam.onClient, false); - strictEqual(methodParam.isApiVersionParam, false); - strictEqual(methodParam.type.kind, "string"); - - const contentTypeMethodParam = method.parameters.find((x) => x.name === "contentType"); - ok(contentTypeMethodParam); - strictEqual(contentTypeMethodParam.clientDefaultValue, undefined); - strictEqual(contentTypeMethodParam.type.kind, "constant"); - - const serviceOperation = method.operation; - const bodyParameter = serviceOperation.bodyParam; - ok(bodyParameter); - strictEqual(bodyParameter.kind, "body"); - deepStrictEqual(bodyParameter.contentTypes, ["application/json"]); - strictEqual(bodyParameter.defaultContentType, "application/json"); - strictEqual(bodyParameter.onClient, false); - strictEqual(bodyParameter.optional, false); - strictEqual(bodyParameter.type.kind, "model"); - strictEqual(bodyParameter.type.properties.length, 1); - strictEqual(bodyParameter.type.properties[0].name, "name"); - - const correspondingMethodParams = bodyParameter.correspondingMethodParams; - strictEqual(correspondingMethodParams.length, 1); - strictEqual(bodyParameter.type.properties[0].name, correspondingMethodParams[0].name); - }); - - it("rest template spreading of multiple models", async () => { - await runner.compile(` - @service({ - title: "Pet Store Service", - }) - namespace PetStore; - using TypeSpec.Rest.Resource; - - @error - model PetStoreError { - code: int32; - message: string; - } - - @resource("pets") - model Pet { - @key("petId") - id: int32; - } - - @resource("checkups") - model Checkup { - @key("checkupId") - id: int32; - - vetName: string; - notes: string; - } - - interface PetCheckups - extends ExtensionResourceCreateOrUpdate, - ExtensionResourceList {} - `); - const sdkPackage = runner.context.sdkPackage; - strictEqual(sdkPackage.models.length, 4); - deepStrictEqual( - sdkPackage.models.map((x) => x.name).sort(), - ["CheckupCollectionWithNextLink", "Checkup", "PetStoreError", "CheckupUpdate"].sort() - ); - const client = sdkPackage.clients[0].methods.find((x) => x.kind === "clientaccessor") - ?.response as SdkClientType; - const createOrUpdate = client.methods[0]; - strictEqual(createOrUpdate.kind, "basic"); - strictEqual(createOrUpdate.name, "createOrUpdate"); - strictEqual(createOrUpdate.parameters.length, 5); - strictEqual(createOrUpdate.parameters[0].name, "petId"); - strictEqual(createOrUpdate.parameters[1].name, "checkupId"); - strictEqual(createOrUpdate.parameters[2].name, "resource"); - strictEqual(createOrUpdate.parameters[2].type.kind, "model"); - strictEqual(createOrUpdate.parameters[2].type.name, "CheckupUpdate"); - strictEqual(createOrUpdate.parameters[3].name, "contentType"); - strictEqual(createOrUpdate.parameters[4].name, "accept"); - - const opParams = createOrUpdate.operation.parameters; - strictEqual(opParams.length, 4); - ok(opParams.find((x) => x.kind === "path" && x.serializedName === "petId")); - ok(opParams.find((x) => x.kind === "path" && x.serializedName === "checkupId")); - ok(opParams.find((x) => x.kind === "header" && x.serializedName === "Content-Type")); - ok(opParams.find((x) => x.kind === "header" && x.serializedName === "Accept")); - strictEqual(createOrUpdate.operation.responses.size, 2); - const response200 = createOrUpdate.operation.responses.get(200); - ok(response200); - ok(response200.type); - strictEqual(response200.type.kind, "model"); - strictEqual(response200.type.name, "Checkup"); - const response201 = createOrUpdate.operation.responses.get(201); - ok(response201); - ok(response201.type); - deepStrictEqual(response200.type, response201?.type); - }); - - it("multi layer template with discriminated model spread", async () => { - const runnerWithCore = await createSdkTestRunner({ - librariesToAdd: [AzureCoreTestLibrary], - autoUsings: ["Azure.Core", "Azure.Core.Traits"], - emitterName: "@azure-tools/typespec-java", - }); - await runnerWithCore.compile(` - @versioned(MyVersions) - @server("http://localhost:3000", "endpoint") - @useAuth(ApiKeyAuth) - @service({name: "Service"}) - namespace My.Service; - - alias ServiceTraits = NoRepeatableRequests & - NoConditionalRequests & - NoClientRequestId; - - alias Operations = Azure.Core.ResourceOperations; - - @doc("The version of the API.") - enum MyVersions { - @doc("The version 2022-12-01-preview.") - @useDependency(Versions.v1_0_Preview_2) - v2022_12_01_preview: "2022-12-01-preview", - } - - @discriminator("kind") - @resource("dataConnections") - model DataConnection { - id?: string; - - @key("dataConnectionName") - @visibility("read") - name: string; - - @visibility("read") - createdDate?: utcDateTime; - - frequencyOffset?: int32; - } - - @discriminator("kind") - model DataConnectionData { - name?: string; - frequencyOffset?: int32; - } - - interface DataConnections { - - getDataConnection is Operations.ResourceRead; - - @createsOrReplacesResource(DataConnection) - @put - createOrReplaceDataConnection is Foundations.ResourceOperation< - DataConnection, - DataConnectionData, - DataConnection - >; - - deleteDataConnection is Operations.ResourceDelete; - } - `); - const sdkPackage = runnerWithCore.context.sdkPackage; - strictEqual(sdkPackage.models.length, 2); - - const client = sdkPackage.clients[0].methods.find((x) => x.kind === "clientaccessor") - ?.response as SdkClientType; - - const createOrReplace = client.methods[1]; - strictEqual(createOrReplace.kind, "basic"); - strictEqual(createOrReplace.name, "createOrReplaceDataConnection"); - strictEqual(createOrReplace.parameters.length, 6); - ok( - createOrReplace.parameters.find( - (x) => x.name === "dataConnectionName" && x.type.kind === "string" - ) - ); - ok(createOrReplace.parameters.find((x) => x.name === "name" && x.type.kind === "string")); - ok( - createOrReplace.parameters.find( - (x) => x.name === "frequencyOffset" && x.type.kind === "int32" - ) - ); - ok(createOrReplace.parameters.find((x) => x.name === "contentType")); - ok(createOrReplace.parameters.find((x) => x.name === "accept")); - ok(createOrReplace.parameters.find((x) => x.isApiVersionParam && x.onClient)); - - const opParams = createOrReplace.operation.parameters; - strictEqual(opParams.length, 4); - ok(opParams.find((x) => x.isApiVersionParam === true && x.kind === "query")); - ok(opParams.find((x) => x.kind === "path" && x.serializedName === "dataConnectionName")); - ok(opParams.find((x) => x.kind === "header" && x.serializedName === "Content-Type")); - ok(opParams.find((x) => x.kind === "header" && x.serializedName === "Accept")); - strictEqual(createOrReplace.operation.bodyParam?.type.kind, "model"); - strictEqual( - createOrReplace.operation.bodyParam?.type.name, - "CreateOrReplaceDataConnectionRequest" - ); - deepStrictEqual( - createOrReplace.operation.bodyParam.correspondingMethodParams[0], - createOrReplace.parameters[2] - ); - deepStrictEqual( - createOrReplace.operation.bodyParam.correspondingMethodParams[1], - createOrReplace.parameters[3] - ); - strictEqual(createOrReplace.operation.responses.size, 1); - const response200 = createOrReplace.operation.responses.get(200); - ok(response200); - ok(response200.type); - strictEqual(response200.type.kind, "model"); - strictEqual(response200.type.name, "DataConnection"); - }); - - it("model with @body decorator", async () => { - await runner.compileWithBuiltInService(` - model Shelf { - name: string; - theme?: string; - } - model CreateShelfRequest { - @body - body: Shelf; - } - op createShelf(...CreateShelfRequest): Shelf; - `); - const method = getServiceMethodOfClient(runner.context.sdkPackage); - const models = runner.context.sdkPackage.models; - strictEqual(models.length, 1); - const shelfModel = models.find((x) => x.name === "Shelf"); - ok(shelfModel); - strictEqual(method.parameters.length, 3); - const shelfParameter = method.parameters[0]; - strictEqual(shelfParameter.kind, "method"); - strictEqual(shelfParameter.name, "body"); - strictEqual(shelfParameter.optional, false); - strictEqual(shelfParameter.isGeneratedName, false); - deepStrictEqual(shelfParameter.type, shelfModel); - const contentTypeMethoParam = method.parameters.find((x) => x.name === "contentType"); - ok(contentTypeMethoParam); - const acceptMethodParam = method.parameters.find((x) => x.name === "accept"); - ok(acceptMethodParam); - - const op = method.operation; - strictEqual(op.parameters.length, 2); - ok( - op.parameters.find( - (x) => - x.kind === "header" && - x.serializedName === "Content-Type" && - x.correspondingMethodParams[0] === contentTypeMethoParam - ) - ); - ok( - op.parameters.find( - (x) => - x.kind === "header" && - x.serializedName === "Accept" && - x.correspondingMethodParams[0] === acceptMethodParam - ) - ); - - const bodyParam = op.bodyParam; - ok(bodyParam); - strictEqual(bodyParam.kind, "body"); - strictEqual(bodyParam.name, "body"); - strictEqual(bodyParam.optional, false); - strictEqual(bodyParam.isGeneratedName, false); - deepStrictEqual(bodyParam.type, shelfModel); - deepStrictEqual(bodyParam.correspondingMethodParams[0], shelfParameter); - }); - it("formdata model without body decorator in spread model", async () => { - await runner.compileWithBuiltInService(` - - model DocumentTranslateContent { - @header contentType: "multipart/form-data"; - document: bytes; - } - alias Intersected = DocumentTranslateContent & {}; - op test(...Intersected): void; - `); - const method = getServiceMethodOfClient(runner.context.sdkPackage); - const documentMethodParam = method.parameters.find((x) => x.name === "document"); - ok(documentMethodParam); - strictEqual(documentMethodParam.kind, "method"); - const op = method.operation; - ok(op.bodyParam); - strictEqual(op.bodyParam.kind, "body"); - strictEqual(op.bodyParam.name, "testRequest"); - deepStrictEqual(op.bodyParam.correspondingMethodParams, [documentMethodParam]); - - const anonymousModel = runner.context.sdkPackage.models[0]; - strictEqual(anonymousModel.properties.length, 1); - strictEqual(anonymousModel.properties[0].kind, "property"); - strictEqual(anonymousModel.properties[0].isMultipartFileInput, true); - ok(anonymousModel.properties[0].multipartOptions); - strictEqual(anonymousModel.properties[0].multipartOptions.isFilePart, true); - strictEqual(anonymousModel.properties[0].multipartOptions.isMulti, false); - }); - - it("anonymous model with @body should not be spread", async () => { - await runner.compileWithBuiltInService(` - op test(@body body: {prop: string}): void; - `); - const method = getServiceMethodOfClient(runner.context.sdkPackage); - const models = runner.context.sdkPackage.models; - strictEqual(models.length, 1); - const model = models.find((x) => x.name === "TestRequest"); - ok(model); - strictEqual(model.usage, UsageFlags.Input | UsageFlags.Json); - - strictEqual(method.parameters.length, 2); - const param = method.parameters[0]; - strictEqual(param.kind, "method"); - strictEqual(param.name, "body"); - strictEqual(param.optional, false); - strictEqual(param.isGeneratedName, false); - deepStrictEqual(param.type, model); - const contentTypeMethoParam = method.parameters.find((x) => x.name === "contentType"); - ok(contentTypeMethoParam); - - const op = method.operation; - strictEqual(op.parameters.length, 1); - ok( - op.parameters.find( - (x) => - x.kind === "header" && - x.serializedName === "Content-Type" && - x.correspondingMethodParams[0] === contentTypeMethoParam - ) - ); - - const bodyParam = op.bodyParam; - ok(bodyParam); - strictEqual(bodyParam.kind, "body"); - strictEqual(bodyParam.name, "body"); - strictEqual(bodyParam.optional, false); - strictEqual(bodyParam.isGeneratedName, false); - deepStrictEqual(bodyParam.type, model); - deepStrictEqual(bodyParam.correspondingMethodParams[0].type, model); - }); - - it("anonymous model from spread with @bodyRoot should not be spread", async () => { - await runner.compileWithBuiltInService(` - model Test { - prop: string; - } - op test(@bodyRoot body: {...Test}): void; - `); - const method = getServiceMethodOfClient(runner.context.sdkPackage); - const models = runner.context.sdkPackage.models; - strictEqual(models.length, 1); - const model = models.find((x) => x.name === "TestRequest"); - ok(model); - strictEqual(model.usage, UsageFlags.Input | UsageFlags.Json); - - strictEqual(method.parameters.length, 2); - const param = method.parameters[0]; - strictEqual(param.kind, "method"); - strictEqual(param.name, "body"); - strictEqual(param.optional, false); - strictEqual(param.isGeneratedName, false); - deepStrictEqual(param.type, model); - const contentTypeMethoParam = method.parameters.find((x) => x.name === "contentType"); - ok(contentTypeMethoParam); - - const op = method.operation; - strictEqual(op.parameters.length, 1); - ok( - op.parameters.find( - (x) => - x.kind === "header" && - x.serializedName === "Content-Type" && - x.correspondingMethodParams[0] === contentTypeMethoParam - ) - ); - - const bodyParam = op.bodyParam; - ok(bodyParam); - strictEqual(bodyParam.kind, "body"); - strictEqual(bodyParam.name, "body"); - strictEqual(bodyParam.optional, false); - strictEqual(bodyParam.isGeneratedName, false); - deepStrictEqual(bodyParam.type, model); - deepStrictEqual(bodyParam.correspondingMethodParams[0].type, model); - }); - - it("implicit spread", async () => { - await runner.compile(`@server("http://localhost:3000", "endpoint") - @service({}) - namespace My.Service; - op myOp(a: string, b: string): void; - `); - const sdkPackage = runner.context.sdkPackage; - strictEqual(sdkPackage.models.length, 1); - - const method = getServiceMethodOfClient(sdkPackage); - strictEqual(method.name, "myOp"); - strictEqual(method.kind, "basic"); - strictEqual(method.parameters.length, 3); - - const a = method.parameters.find((x) => x.name === "a"); - ok(a); - strictEqual(a.kind, "method"); - strictEqual(a.optional, false); - strictEqual(a.onClient, false); - strictEqual(a.isApiVersionParam, false); - strictEqual(a.type.kind, "string"); - - const b = method.parameters.find((x) => x.name === "b"); - ok(b); - strictEqual(b.kind, "method"); - strictEqual(b.optional, false); - strictEqual(b.onClient, false); - strictEqual(b.isApiVersionParam, false); - strictEqual(b.type.kind, "string"); - - const serviceOperation = method.operation; - const bodyParameter = serviceOperation.bodyParam; - ok(bodyParameter); - - strictEqual(bodyParameter.kind, "body"); - strictEqual(bodyParameter.onClient, false); - strictEqual(bodyParameter.optional, false); - strictEqual(bodyParameter.type, sdkPackage.models[0]); - strictEqual(bodyParameter.type.usage, UsageFlags.Spread | UsageFlags.Json); - strictEqual(bodyParameter.type.access, "internal"); - - strictEqual(bodyParameter.correspondingMethodParams.length, 2); - deepStrictEqual(bodyParameter.correspondingMethodParams[0], a); - deepStrictEqual(bodyParameter.correspondingMethodParams[1], b); - }); - - it("implicit spread with metadata", async () => { - await runner.compile(`@server("http://localhost:3000", "endpoint") - @service({}) - namespace My.Service; - op myOp(@header a: string, b: string): void; - `); - const sdkPackage = runner.context.sdkPackage; - strictEqual(sdkPackage.models.length, 1); - - const method = getServiceMethodOfClient(sdkPackage); - strictEqual(method.name, "myOp"); - strictEqual(method.kind, "basic"); - strictEqual(method.parameters.length, 3); - - const a = method.parameters.find((x) => x.name === "a"); - ok(a); - strictEqual(a.kind, "method"); - strictEqual(a.optional, false); - strictEqual(a.onClient, false); - strictEqual(a.isApiVersionParam, false); - strictEqual(a.type.kind, "string"); - - const b = method.parameters.find((x) => x.name === "b"); - ok(b); - strictEqual(b.kind, "method"); - strictEqual(b.optional, false); - strictEqual(b.onClient, false); - strictEqual(b.isApiVersionParam, false); - strictEqual(b.type.kind, "string"); - - const serviceOperation = method.operation; - const headerParameter = serviceOperation.parameters.find((p) => (p.name = "a")); - ok(headerParameter); - strictEqual(headerParameter.kind, "header"); - strictEqual(headerParameter.onClient, false); - strictEqual(headerParameter.optional, false); - strictEqual(headerParameter.type.kind, "string"); - - strictEqual(headerParameter.correspondingMethodParams.length, 1); - deepStrictEqual(headerParameter.correspondingMethodParams[0], a); - - const bodyParameter = serviceOperation.bodyParam; - ok(bodyParameter); - - strictEqual(bodyParameter.kind, "body"); - strictEqual(bodyParameter.onClient, false); - strictEqual(bodyParameter.optional, false); - strictEqual(bodyParameter.type, sdkPackage.models[0]); - strictEqual(bodyParameter.type.usage, UsageFlags.Spread | UsageFlags.Json); - strictEqual(bodyParameter.type.access, "internal"); - - strictEqual(bodyParameter.correspondingMethodParams.length, 1); - deepStrictEqual(bodyParameter.correspondingMethodParams[0], b); - }); - - it("explicit spread", async () => { - await runner.compile(`@server("http://localhost:3000", "endpoint") - @service({}) - namespace My.Service; - model Test { - a: string; - b: string; - } - op myOp(...Test): void; - `); - const sdkPackage = runner.context.sdkPackage; - strictEqual(sdkPackage.models.length, 1); - - const method = getServiceMethodOfClient(sdkPackage); - strictEqual(method.name, "myOp"); - strictEqual(method.kind, "basic"); - strictEqual(method.parameters.length, 3); - - const a = method.parameters.find((x) => x.name === "a"); - ok(a); - strictEqual(a.kind, "method"); - strictEqual(a.optional, false); - strictEqual(a.onClient, false); - strictEqual(a.isApiVersionParam, false); - strictEqual(a.type.kind, "string"); - - const b = method.parameters.find((x) => x.name === "b"); - ok(b); - strictEqual(b.kind, "method"); - strictEqual(b.optional, false); - strictEqual(b.onClient, false); - strictEqual(b.isApiVersionParam, false); - strictEqual(b.type.kind, "string"); - - const serviceOperation = method.operation; - const bodyParameter = serviceOperation.bodyParam; - ok(bodyParameter); - - strictEqual(bodyParameter.kind, "body"); - strictEqual(bodyParameter.onClient, false); - strictEqual(bodyParameter.optional, false); - strictEqual(bodyParameter.type, sdkPackage.models[0]); - strictEqual(bodyParameter.type.usage, UsageFlags.Spread | UsageFlags.Json); - strictEqual(bodyParameter.type.access, "internal"); - - strictEqual(bodyParameter.correspondingMethodParams.length, 2); - deepStrictEqual(bodyParameter.correspondingMethodParams[0], a); - deepStrictEqual(bodyParameter.correspondingMethodParams[1], b); - }); - - it("explicit spread with metadata", async () => { - await runner.compile(`@server("http://localhost:3000", "endpoint") - @service({}) - namespace My.Service; - model Test { - @header - a: string; - b: string; - } - op myOp(...Test): void; - `); - const sdkPackage = runner.context.sdkPackage; - strictEqual(sdkPackage.models.length, 1); - - const method = getServiceMethodOfClient(sdkPackage); - strictEqual(method.name, "myOp"); - strictEqual(method.kind, "basic"); - strictEqual(method.parameters.length, 3); - - const a = method.parameters.find((x) => x.name === "a"); - ok(a); - strictEqual(a.kind, "method"); - strictEqual(a.optional, false); - strictEqual(a.onClient, false); - strictEqual(a.isApiVersionParam, false); - strictEqual(a.type.kind, "string"); - - const b = method.parameters.find((x) => x.name === "b"); - ok(b); - strictEqual(b.kind, "method"); - strictEqual(b.optional, false); - strictEqual(b.onClient, false); - strictEqual(b.isApiVersionParam, false); - strictEqual(b.type.kind, "string"); - - const serviceOperation = method.operation; - const headerParameter = serviceOperation.parameters.find((p) => (p.name = "a")); - ok(headerParameter); - strictEqual(headerParameter.kind, "header"); - strictEqual(headerParameter.onClient, false); - strictEqual(headerParameter.optional, false); - strictEqual(headerParameter.type.kind, "string"); - - strictEqual(headerParameter.correspondingMethodParams.length, 1); - deepStrictEqual(headerParameter.correspondingMethodParams[0], a); - - const bodyParameter = serviceOperation.bodyParam; - ok(bodyParameter); - - strictEqual(bodyParameter.kind, "body"); - strictEqual(bodyParameter.onClient, false); - strictEqual(bodyParameter.optional, false); - strictEqual(bodyParameter.type, sdkPackage.models[0]); - strictEqual(bodyParameter.type.usage, UsageFlags.Spread | UsageFlags.Json); - strictEqual(bodyParameter.type.access, "internal"); - - strictEqual(bodyParameter.correspondingMethodParams.length, 1); - deepStrictEqual(bodyParameter.correspondingMethodParams[0], b); - }); - - it("explicit multiple spread", async () => { - await runner.compile(`@server("http://localhost:3000", "endpoint") - @service({}) - namespace My.Service; - model Test1 { - a: string; - - } - - model Test2 { - b: string; - } - op myOp(...Test1, ...Test2): void; - `); - const sdkPackage = runner.context.sdkPackage; - strictEqual(sdkPackage.models.length, 1); - - const method = getServiceMethodOfClient(sdkPackage); - strictEqual(method.name, "myOp"); - strictEqual(method.kind, "basic"); - strictEqual(method.parameters.length, 3); - - const a = method.parameters.find((x) => x.name === "a"); - ok(a); - strictEqual(a.kind, "method"); - strictEqual(a.optional, false); - strictEqual(a.onClient, false); - strictEqual(a.isApiVersionParam, false); - strictEqual(a.type.kind, "string"); - - const b = method.parameters.find((x) => x.name === "b"); - ok(b); - strictEqual(b.kind, "method"); - strictEqual(b.optional, false); - strictEqual(b.onClient, false); - strictEqual(b.isApiVersionParam, false); - strictEqual(b.type.kind, "string"); - - const serviceOperation = method.operation; - const bodyParameter = serviceOperation.bodyParam; - ok(bodyParameter); - - strictEqual(bodyParameter.kind, "body"); - strictEqual(bodyParameter.onClient, false); - strictEqual(bodyParameter.optional, false); - strictEqual(bodyParameter.type, sdkPackage.models[0]); - strictEqual(bodyParameter.type.usage, UsageFlags.Spread | UsageFlags.Json); - strictEqual(bodyParameter.type.access, "internal"); - - strictEqual(bodyParameter.correspondingMethodParams.length, 2); - deepStrictEqual(bodyParameter.correspondingMethodParams[0], a); - deepStrictEqual(bodyParameter.correspondingMethodParams[1], b); - }); - - it("explicit multiple spread with metadata", async () => { - await runner.compile(`@server("http://localhost:3000", "endpoint") - @service({}) - namespace My.Service; - model Test1 { - @header - a: string; - } - model Test2 { - b: string; - } - op myOp(...Test1, ...Test2): void; - `); - const sdkPackage = runner.context.sdkPackage; - strictEqual(sdkPackage.models.length, 1); - - const method = getServiceMethodOfClient(sdkPackage); - strictEqual(method.name, "myOp"); - strictEqual(method.kind, "basic"); - strictEqual(method.parameters.length, 3); - - const a = method.parameters.find((x) => x.name === "a"); - ok(a); - strictEqual(a.kind, "method"); - strictEqual(a.optional, false); - strictEqual(a.onClient, false); - strictEqual(a.isApiVersionParam, false); - strictEqual(a.type.kind, "string"); - - const b = method.parameters.find((x) => x.name === "b"); - ok(b); - strictEqual(b.kind, "method"); - strictEqual(b.optional, false); - strictEqual(b.onClient, false); - strictEqual(b.isApiVersionParam, false); - strictEqual(b.type.kind, "string"); - - const serviceOperation = method.operation; - const headerParameter = serviceOperation.parameters.find((p) => (p.name = "a")); - ok(headerParameter); - strictEqual(headerParameter.kind, "header"); - strictEqual(headerParameter.onClient, false); - strictEqual(headerParameter.optional, false); - strictEqual(headerParameter.type.kind, "string"); - - strictEqual(headerParameter.correspondingMethodParams.length, 1); - deepStrictEqual(headerParameter.correspondingMethodParams[0], a); - - const bodyParameter = serviceOperation.bodyParam; - ok(bodyParameter); - - strictEqual(bodyParameter.kind, "body"); - strictEqual(bodyParameter.onClient, false); - strictEqual(bodyParameter.optional, false); - strictEqual(bodyParameter.type, sdkPackage.models[0]); - strictEqual(bodyParameter.type.usage, UsageFlags.Spread | UsageFlags.Json); - strictEqual(bodyParameter.type.access, "internal"); - - strictEqual(bodyParameter.correspondingMethodParams.length, 1); - deepStrictEqual(bodyParameter.correspondingMethodParams[0], b); - }); - - it("spread idempotent", async () => { - await runner.compile(`@server("http://localhost:3000", "endpoint") - @service({}) - namespace My.Service; - alias FooAlias = { - @path id: string; - @doc("name of the Foo") - name: string; - }; - op test(...FooAlias): void; - `); - const sdkPackage = runner.context.sdkPackage; - strictEqual(sdkPackage.models.length, 1); - getAllModels(runner.context); - - strictEqual(sdkPackage.models[0].name, "TestRequest"); - strictEqual(sdkPackage.models[0].usage, UsageFlags.Spread | UsageFlags.Json); - }); - }); describe("versioning", () => { it("define own api version param", async () => { await runner.compileWithBuiltInService(` diff --git a/packages/typespec-client-generator-core/test/packages/spread.test.ts b/packages/typespec-client-generator-core/test/packages/spread.test.ts new file mode 100644 index 0000000000..b4069b32f3 --- /dev/null +++ b/packages/typespec-client-generator-core/test/packages/spread.test.ts @@ -0,0 +1,901 @@ +import { AzureCoreTestLibrary } from "@azure-tools/typespec-azure-core/testing"; +import { deepStrictEqual, ok, strictEqual } from "assert"; +import { beforeEach, describe, it } from "vitest"; +import { SdkClientType, SdkHttpOperation, UsageFlags } from "../../src/interfaces.js"; +import { getAllModels } from "../../src/types.js"; +import { SdkTestRunner, createSdkTestRunner } from "../test-host.js"; +import { getServiceMethodOfClient } from "./utils.js"; + +describe("typespec-client-generator-core: spread", () => { + let runner: SdkTestRunner; + + beforeEach(async () => { + runner = await createSdkTestRunner({ emitterName: "@azure-tools/typespec-python" }); + }); + + it("plain model with no decorators", async () => { + await runner.compile(`@server("http://localhost:3000", "endpoint") + @service({}) + namespace My.Service; + + model Input { + key: string; + } + + op myOp(...Input): void; + `); + const sdkPackage = runner.context.sdkPackage; + const method = getServiceMethodOfClient(sdkPackage); + strictEqual(method.name, "myOp"); + strictEqual(method.kind, "basic"); + strictEqual(method.parameters.length, 2); + + const methodParam = method.parameters.find((x) => x.name === "key"); + ok(methodParam); + strictEqual(methodParam.kind, "method"); + strictEqual(methodParam.optional, false); + strictEqual(methodParam.onClient, false); + strictEqual(methodParam.isApiVersionParam, false); + strictEqual(methodParam.type.kind, "string"); + + const contentTypeParam = method.parameters.find((x) => x.name === "contentType"); + ok(contentTypeParam); + strictEqual(contentTypeParam.clientDefaultValue, undefined); + strictEqual(contentTypeParam.type.kind, "constant"); + strictEqual(contentTypeParam.onClient, false); + + const serviceOperation = method.operation; + const bodyParameter = serviceOperation.bodyParam; + ok(bodyParameter); + + strictEqual(bodyParameter.kind, "body"); + deepStrictEqual(bodyParameter.contentTypes, ["application/json"]); + strictEqual(bodyParameter.defaultContentType, "application/json"); + strictEqual(bodyParameter.onClient, false); + strictEqual(bodyParameter.optional, false); + strictEqual(bodyParameter.type, sdkPackage.models[0]); + strictEqual(bodyParameter.type.usage, UsageFlags.Spread | UsageFlags.Json); + strictEqual(bodyParameter.type.access, "internal"); + + const correspondingMethodParams = bodyParameter.correspondingMethodParams; + strictEqual(correspondingMethodParams.length, 1); + strictEqual(correspondingMethodParams[0].name, "key"); + }); + + it("alias with no decorators", async () => { + await runner.compile(`@server("http://localhost:3000", "endpoint") + @service({}) + namespace My.Service; + + alias BodyParameter = { + name: string; + }; + + op myOp(...BodyParameter): void; + `); + const sdkPackage = runner.context.sdkPackage; + strictEqual(sdkPackage.models.length, 1); + + const method = getServiceMethodOfClient(sdkPackage); + strictEqual(method.name, "myOp"); + strictEqual(method.kind, "basic"); + strictEqual(method.parameters.length, 2); + + const methodParam = method.parameters.find((x) => x.name === "name"); + ok(methodParam); + strictEqual(methodParam.kind, "method"); + strictEqual(methodParam.optional, false); + strictEqual(methodParam.onClient, false); + strictEqual(methodParam.isApiVersionParam, false); + strictEqual(methodParam.type.kind, "string"); + + const contentTypeMethodParam = method.parameters.find((x) => x.name === "contentType"); + ok(contentTypeMethodParam); + strictEqual(contentTypeMethodParam.clientDefaultValue, undefined); + strictEqual(contentTypeMethodParam.type.kind, "constant"); + + const serviceOperation = method.operation; + const bodyParameter = serviceOperation.bodyParam; + ok(bodyParameter); + strictEqual(bodyParameter.kind, "body"); + deepStrictEqual(bodyParameter.contentTypes, ["application/json"]); + strictEqual(bodyParameter.defaultContentType, "application/json"); + strictEqual(bodyParameter.onClient, false); + strictEqual(bodyParameter.optional, false); + strictEqual(bodyParameter.type.kind, "model"); + strictEqual(bodyParameter.type.properties.length, 1); + strictEqual(bodyParameter.type.properties[0].name, "name"); + + const correspondingMethodParams = bodyParameter.correspondingMethodParams; + strictEqual(correspondingMethodParams.length, 1); + strictEqual(bodyParameter.type.properties[0].name, correspondingMethodParams[0].name); + }); + + it("rest template spreading of multiple models", async () => { + await runner.compile(` + @service({ + title: "Pet Store Service", + }) + namespace PetStore; + using TypeSpec.Rest.Resource; + + @error + model PetStoreError { + code: int32; + message: string; + } + + @resource("pets") + model Pet { + @key("petId") + id: int32; + } + + @resource("checkups") + model Checkup { + @key("checkupId") + id: int32; + + vetName: string; + notes: string; + } + + interface PetCheckups + extends ExtensionResourceCreateOrUpdate, + ExtensionResourceList {} + `); + const sdkPackage = runner.context.sdkPackage; + strictEqual(sdkPackage.models.length, 4); + deepStrictEqual( + sdkPackage.models.map((x) => x.name).sort(), + ["CheckupCollectionWithNextLink", "Checkup", "PetStoreError", "CheckupUpdate"].sort() + ); + const client = sdkPackage.clients[0].methods.find((x) => x.kind === "clientaccessor") + ?.response as SdkClientType; + const createOrUpdate = client.methods[0]; + strictEqual(createOrUpdate.kind, "basic"); + strictEqual(createOrUpdate.name, "createOrUpdate"); + strictEqual(createOrUpdate.parameters.length, 5); + strictEqual(createOrUpdate.parameters[0].name, "petId"); + strictEqual(createOrUpdate.parameters[1].name, "checkupId"); + strictEqual(createOrUpdate.parameters[2].name, "resource"); + strictEqual(createOrUpdate.parameters[2].type.kind, "model"); + strictEqual(createOrUpdate.parameters[2].type.name, "CheckupUpdate"); + strictEqual(createOrUpdate.parameters[3].name, "contentType"); + strictEqual(createOrUpdate.parameters[4].name, "accept"); + + const opParams = createOrUpdate.operation.parameters; + strictEqual(opParams.length, 4); + ok(opParams.find((x) => x.kind === "path" && x.serializedName === "petId")); + ok(opParams.find((x) => x.kind === "path" && x.serializedName === "checkupId")); + ok(opParams.find((x) => x.kind === "header" && x.serializedName === "Content-Type")); + ok(opParams.find((x) => x.kind === "header" && x.serializedName === "Accept")); + strictEqual(createOrUpdate.operation.responses.size, 2); + const response200 = createOrUpdate.operation.responses.get(200); + ok(response200); + ok(response200.type); + strictEqual(response200.type.kind, "model"); + strictEqual(response200.type.name, "Checkup"); + const response201 = createOrUpdate.operation.responses.get(201); + ok(response201); + ok(response201.type); + deepStrictEqual(response200.type, response201?.type); + }); + + it("multi layer template with discriminated model spread", async () => { + const runnerWithCore = await createSdkTestRunner({ + librariesToAdd: [AzureCoreTestLibrary], + autoUsings: ["Azure.Core", "Azure.Core.Traits"], + emitterName: "@azure-tools/typespec-java", + }); + await runnerWithCore.compile(` + @versioned(MyVersions) + @server("http://localhost:3000", "endpoint") + @useAuth(ApiKeyAuth) + @service({name: "Service"}) + namespace My.Service; + + alias ServiceTraits = NoRepeatableRequests & + NoConditionalRequests & + NoClientRequestId; + + alias Operations = Azure.Core.ResourceOperations; + + @doc("The version of the API.") + enum MyVersions { + @doc("The version 2022-12-01-preview.") + @useDependency(Versions.v1_0_Preview_2) + v2022_12_01_preview: "2022-12-01-preview", + } + + @discriminator("kind") + @resource("dataConnections") + model DataConnection { + id?: string; + + @key("dataConnectionName") + @visibility("read") + name: string; + + @visibility("read") + createdDate?: utcDateTime; + + frequencyOffset?: int32; + } + + @discriminator("kind") + model DataConnectionData { + name?: string; + frequencyOffset?: int32; + } + + interface DataConnections { + + getDataConnection is Operations.ResourceRead; + + @createsOrReplacesResource(DataConnection) + @put + createOrReplaceDataConnection is Foundations.ResourceOperation< + DataConnection, + DataConnectionData, + DataConnection + >; + + deleteDataConnection is Operations.ResourceDelete; + } + `); + const sdkPackage = runnerWithCore.context.sdkPackage; + strictEqual(sdkPackage.models.length, 2); + + const client = sdkPackage.clients[0].methods.find((x) => x.kind === "clientaccessor") + ?.response as SdkClientType; + + const createOrReplace = client.methods[1]; + strictEqual(createOrReplace.kind, "basic"); + strictEqual(createOrReplace.name, "createOrReplaceDataConnection"); + strictEqual(createOrReplace.parameters.length, 6); + ok( + createOrReplace.parameters.find( + (x) => x.name === "dataConnectionName" && x.type.kind === "string" + ) + ); + ok(createOrReplace.parameters.find((x) => x.name === "name" && x.type.kind === "string")); + ok( + createOrReplace.parameters.find( + (x) => x.name === "frequencyOffset" && x.type.kind === "int32" + ) + ); + ok(createOrReplace.parameters.find((x) => x.name === "contentType")); + ok(createOrReplace.parameters.find((x) => x.name === "accept")); + ok(createOrReplace.parameters.find((x) => x.isApiVersionParam && x.onClient)); + + const opParams = createOrReplace.operation.parameters; + strictEqual(opParams.length, 4); + ok(opParams.find((x) => x.isApiVersionParam === true && x.kind === "query")); + ok(opParams.find((x) => x.kind === "path" && x.serializedName === "dataConnectionName")); + ok(opParams.find((x) => x.kind === "header" && x.serializedName === "Content-Type")); + ok(opParams.find((x) => x.kind === "header" && x.serializedName === "Accept")); + strictEqual(createOrReplace.operation.bodyParam?.type.kind, "model"); + strictEqual( + createOrReplace.operation.bodyParam?.type.name, + "CreateOrReplaceDataConnectionRequest" + ); + deepStrictEqual( + createOrReplace.operation.bodyParam.correspondingMethodParams[0], + createOrReplace.parameters[2] + ); + deepStrictEqual( + createOrReplace.operation.bodyParam.correspondingMethodParams[1], + createOrReplace.parameters[3] + ); + strictEqual(createOrReplace.operation.responses.size, 1); + const response200 = createOrReplace.operation.responses.get(200); + ok(response200); + ok(response200.type); + strictEqual(response200.type.kind, "model"); + strictEqual(response200.type.name, "DataConnection"); + }); + + it("model with @body decorator", async () => { + await runner.compileWithBuiltInService(` + model Shelf { + name: string; + theme?: string; + } + model CreateShelfRequest { + @body + body: Shelf; + } + op createShelf(...CreateShelfRequest): Shelf; + `); + const method = getServiceMethodOfClient(runner.context.sdkPackage); + const models = runner.context.sdkPackage.models; + strictEqual(models.length, 1); + const shelfModel = models.find((x) => x.name === "Shelf"); + ok(shelfModel); + strictEqual(method.parameters.length, 3); + const shelfParameter = method.parameters[0]; + strictEqual(shelfParameter.kind, "method"); + strictEqual(shelfParameter.name, "body"); + strictEqual(shelfParameter.optional, false); + strictEqual(shelfParameter.isGeneratedName, false); + deepStrictEqual(shelfParameter.type, shelfModel); + const contentTypeMethoParam = method.parameters.find((x) => x.name === "contentType"); + ok(contentTypeMethoParam); + const acceptMethodParam = method.parameters.find((x) => x.name === "accept"); + ok(acceptMethodParam); + + const op = method.operation; + strictEqual(op.parameters.length, 2); + ok( + op.parameters.find( + (x) => + x.kind === "header" && + x.serializedName === "Content-Type" && + x.correspondingMethodParams[0] === contentTypeMethoParam + ) + ); + ok( + op.parameters.find( + (x) => + x.kind === "header" && + x.serializedName === "Accept" && + x.correspondingMethodParams[0] === acceptMethodParam + ) + ); + + const bodyParam = op.bodyParam; + ok(bodyParam); + strictEqual(bodyParam.kind, "body"); + strictEqual(bodyParam.name, "body"); + strictEqual(bodyParam.optional, false); + strictEqual(bodyParam.isGeneratedName, false); + deepStrictEqual(bodyParam.type, shelfModel); + deepStrictEqual(bodyParam.correspondingMethodParams[0], shelfParameter); + }); + it("formdata model without body decorator in spread model", async () => { + await runner.compileWithBuiltInService(` + + model DocumentTranslateContent { + @header contentType: "multipart/form-data"; + document: bytes; + } + alias Intersected = DocumentTranslateContent & {}; + op test(...Intersected): void; + `); + const method = getServiceMethodOfClient(runner.context.sdkPackage); + const documentMethodParam = method.parameters.find((x) => x.name === "document"); + ok(documentMethodParam); + strictEqual(documentMethodParam.kind, "method"); + const op = method.operation; + ok(op.bodyParam); + strictEqual(op.bodyParam.kind, "body"); + strictEqual(op.bodyParam.name, "testRequest"); + deepStrictEqual(op.bodyParam.correspondingMethodParams, [documentMethodParam]); + + const anonymousModel = runner.context.sdkPackage.models[0]; + strictEqual(anonymousModel.properties.length, 1); + strictEqual(anonymousModel.properties[0].kind, "property"); + strictEqual(anonymousModel.properties[0].isMultipartFileInput, true); + ok(anonymousModel.properties[0].multipartOptions); + strictEqual(anonymousModel.properties[0].multipartOptions.isFilePart, true); + strictEqual(anonymousModel.properties[0].multipartOptions.isMulti, false); + }); + + it("anonymous model with @body should not be spread", async () => { + await runner.compileWithBuiltInService(` + op test(@body body: {prop: string}): void; + `); + const method = getServiceMethodOfClient(runner.context.sdkPackage); + const models = runner.context.sdkPackage.models; + strictEqual(models.length, 1); + const model = models.find((x) => x.name === "TestRequest"); + ok(model); + strictEqual(model.usage, UsageFlags.Input | UsageFlags.Json); + + strictEqual(method.parameters.length, 2); + const param = method.parameters[0]; + strictEqual(param.kind, "method"); + strictEqual(param.name, "body"); + strictEqual(param.optional, false); + strictEqual(param.isGeneratedName, false); + deepStrictEqual(param.type, model); + const contentTypeMethoParam = method.parameters.find((x) => x.name === "contentType"); + ok(contentTypeMethoParam); + + const op = method.operation; + strictEqual(op.parameters.length, 1); + ok( + op.parameters.find( + (x) => + x.kind === "header" && + x.serializedName === "Content-Type" && + x.correspondingMethodParams[0] === contentTypeMethoParam + ) + ); + + const bodyParam = op.bodyParam; + ok(bodyParam); + strictEqual(bodyParam.kind, "body"); + strictEqual(bodyParam.name, "body"); + strictEqual(bodyParam.optional, false); + strictEqual(bodyParam.isGeneratedName, false); + deepStrictEqual(bodyParam.type, model); + deepStrictEqual(bodyParam.correspondingMethodParams[0].type, model); + }); + + it("anonymous model from spread with @bodyRoot should not be spread", async () => { + await runner.compileWithBuiltInService(` + model Test { + prop: string; + } + op test(@bodyRoot body: {...Test}): void; + `); + const method = getServiceMethodOfClient(runner.context.sdkPackage); + const models = runner.context.sdkPackage.models; + strictEqual(models.length, 1); + const model = models.find((x) => x.name === "TestRequest"); + ok(model); + strictEqual(model.usage, UsageFlags.Input | UsageFlags.Json); + + strictEqual(method.parameters.length, 2); + const param = method.parameters[0]; + strictEqual(param.kind, "method"); + strictEqual(param.name, "body"); + strictEqual(param.optional, false); + strictEqual(param.isGeneratedName, false); + deepStrictEqual(param.type, model); + const contentTypeMethoParam = method.parameters.find((x) => x.name === "contentType"); + ok(contentTypeMethoParam); + + const op = method.operation; + strictEqual(op.parameters.length, 1); + ok( + op.parameters.find( + (x) => + x.kind === "header" && + x.serializedName === "Content-Type" && + x.correspondingMethodParams[0] === contentTypeMethoParam + ) + ); + + const bodyParam = op.bodyParam; + ok(bodyParam); + strictEqual(bodyParam.kind, "body"); + strictEqual(bodyParam.name, "body"); + strictEqual(bodyParam.optional, false); + strictEqual(bodyParam.isGeneratedName, false); + deepStrictEqual(bodyParam.type, model); + deepStrictEqual(bodyParam.correspondingMethodParams[0].type, model); + }); + + it("implicit spread", async () => { + await runner.compile(`@server("http://localhost:3000", "endpoint") + @service({}) + namespace My.Service; + op myOp(a: string, b: string): void; + `); + const sdkPackage = runner.context.sdkPackage; + strictEqual(sdkPackage.models.length, 1); + + const method = getServiceMethodOfClient(sdkPackage); + strictEqual(method.name, "myOp"); + strictEqual(method.kind, "basic"); + strictEqual(method.parameters.length, 3); + + const a = method.parameters.find((x) => x.name === "a"); + ok(a); + strictEqual(a.kind, "method"); + strictEqual(a.optional, false); + strictEqual(a.onClient, false); + strictEqual(a.isApiVersionParam, false); + strictEqual(a.type.kind, "string"); + + const b = method.parameters.find((x) => x.name === "b"); + ok(b); + strictEqual(b.kind, "method"); + strictEqual(b.optional, false); + strictEqual(b.onClient, false); + strictEqual(b.isApiVersionParam, false); + strictEqual(b.type.kind, "string"); + + const serviceOperation = method.operation; + const bodyParameter = serviceOperation.bodyParam; + ok(bodyParameter); + + strictEqual(bodyParameter.kind, "body"); + strictEqual(bodyParameter.onClient, false); + strictEqual(bodyParameter.optional, false); + strictEqual(bodyParameter.type, sdkPackage.models[0]); + strictEqual(bodyParameter.type.usage, UsageFlags.Spread | UsageFlags.Json); + strictEqual(bodyParameter.type.access, "internal"); + + strictEqual(bodyParameter.correspondingMethodParams.length, 2); + deepStrictEqual(bodyParameter.correspondingMethodParams[0], a); + deepStrictEqual(bodyParameter.correspondingMethodParams[1], b); + }); + + it("implicit spread with metadata", async () => { + await runner.compile(`@server("http://localhost:3000", "endpoint") + @service({}) + namespace My.Service; + op myOp(@header a: string, b: string): void; + `); + const sdkPackage = runner.context.sdkPackage; + strictEqual(sdkPackage.models.length, 1); + + const method = getServiceMethodOfClient(sdkPackage); + strictEqual(method.name, "myOp"); + strictEqual(method.kind, "basic"); + strictEqual(method.parameters.length, 3); + + const a = method.parameters.find((x) => x.name === "a"); + ok(a); + strictEqual(a.kind, "method"); + strictEqual(a.optional, false); + strictEqual(a.onClient, false); + strictEqual(a.isApiVersionParam, false); + strictEqual(a.type.kind, "string"); + + const b = method.parameters.find((x) => x.name === "b"); + ok(b); + strictEqual(b.kind, "method"); + strictEqual(b.optional, false); + strictEqual(b.onClient, false); + strictEqual(b.isApiVersionParam, false); + strictEqual(b.type.kind, "string"); + + const serviceOperation = method.operation; + const headerParameter = serviceOperation.parameters.find((p) => (p.name = "a")); + ok(headerParameter); + strictEqual(headerParameter.kind, "header"); + strictEqual(headerParameter.onClient, false); + strictEqual(headerParameter.optional, false); + strictEqual(headerParameter.type.kind, "string"); + + strictEqual(headerParameter.correspondingMethodParams.length, 1); + deepStrictEqual(headerParameter.correspondingMethodParams[0], a); + + const bodyParameter = serviceOperation.bodyParam; + ok(bodyParameter); + + strictEqual(bodyParameter.kind, "body"); + strictEqual(bodyParameter.onClient, false); + strictEqual(bodyParameter.optional, false); + strictEqual(bodyParameter.type, sdkPackage.models[0]); + strictEqual(bodyParameter.type.usage, UsageFlags.Spread | UsageFlags.Json); + strictEqual(bodyParameter.type.access, "internal"); + + strictEqual(bodyParameter.correspondingMethodParams.length, 1); + deepStrictEqual(bodyParameter.correspondingMethodParams[0], b); + }); + + it("explicit spread", async () => { + await runner.compile(`@server("http://localhost:3000", "endpoint") + @service({}) + namespace My.Service; + model Test { + a: string; + b: string; + } + op myOp(...Test): void; + `); + const sdkPackage = runner.context.sdkPackage; + strictEqual(sdkPackage.models.length, 1); + + const method = getServiceMethodOfClient(sdkPackage); + strictEqual(method.name, "myOp"); + strictEqual(method.kind, "basic"); + strictEqual(method.parameters.length, 3); + + const a = method.parameters.find((x) => x.name === "a"); + ok(a); + strictEqual(a.kind, "method"); + strictEqual(a.optional, false); + strictEqual(a.onClient, false); + strictEqual(a.isApiVersionParam, false); + strictEqual(a.type.kind, "string"); + + const b = method.parameters.find((x) => x.name === "b"); + ok(b); + strictEqual(b.kind, "method"); + strictEqual(b.optional, false); + strictEqual(b.onClient, false); + strictEqual(b.isApiVersionParam, false); + strictEqual(b.type.kind, "string"); + + const serviceOperation = method.operation; + const bodyParameter = serviceOperation.bodyParam; + ok(bodyParameter); + + strictEqual(bodyParameter.kind, "body"); + strictEqual(bodyParameter.onClient, false); + strictEqual(bodyParameter.optional, false); + strictEqual(bodyParameter.type, sdkPackage.models[0]); + strictEqual(bodyParameter.type.usage, UsageFlags.Spread | UsageFlags.Json); + strictEqual(bodyParameter.type.access, "internal"); + + strictEqual(bodyParameter.correspondingMethodParams.length, 2); + deepStrictEqual(bodyParameter.correspondingMethodParams[0], a); + deepStrictEqual(bodyParameter.correspondingMethodParams[1], b); + }); + + it("explicit spread with metadata", async () => { + await runner.compile(`@server("http://localhost:3000", "endpoint") + @service({}) + namespace My.Service; + model Test { + @header + a: string; + b: string; + } + op myOp(...Test): void; + `); + const sdkPackage = runner.context.sdkPackage; + strictEqual(sdkPackage.models.length, 1); + + const method = getServiceMethodOfClient(sdkPackage); + strictEqual(method.name, "myOp"); + strictEqual(method.kind, "basic"); + strictEqual(method.parameters.length, 3); + + const a = method.parameters.find((x) => x.name === "a"); + ok(a); + strictEqual(a.kind, "method"); + strictEqual(a.optional, false); + strictEqual(a.onClient, false); + strictEqual(a.isApiVersionParam, false); + strictEqual(a.type.kind, "string"); + + const b = method.parameters.find((x) => x.name === "b"); + ok(b); + strictEqual(b.kind, "method"); + strictEqual(b.optional, false); + strictEqual(b.onClient, false); + strictEqual(b.isApiVersionParam, false); + strictEqual(b.type.kind, "string"); + + const serviceOperation = method.operation; + const headerParameter = serviceOperation.parameters.find((p) => (p.name = "a")); + ok(headerParameter); + strictEqual(headerParameter.kind, "header"); + strictEqual(headerParameter.onClient, false); + strictEqual(headerParameter.optional, false); + strictEqual(headerParameter.type.kind, "string"); + + strictEqual(headerParameter.correspondingMethodParams.length, 1); + deepStrictEqual(headerParameter.correspondingMethodParams[0], a); + + const bodyParameter = serviceOperation.bodyParam; + ok(bodyParameter); + + strictEqual(bodyParameter.kind, "body"); + strictEqual(bodyParameter.onClient, false); + strictEqual(bodyParameter.optional, false); + strictEqual(bodyParameter.type, sdkPackage.models[0]); + strictEqual(bodyParameter.type.usage, UsageFlags.Spread | UsageFlags.Json); + strictEqual(bodyParameter.type.access, "internal"); + + strictEqual(bodyParameter.correspondingMethodParams.length, 1); + deepStrictEqual(bodyParameter.correspondingMethodParams[0], b); + }); + + it("explicit multiple spread", async () => { + await runner.compile(`@server("http://localhost:3000", "endpoint") + @service({}) + namespace My.Service; + model Test1 { + a: string; + + } + + model Test2 { + b: string; + } + op myOp(...Test1, ...Test2): void; + `); + const sdkPackage = runner.context.sdkPackage; + strictEqual(sdkPackage.models.length, 1); + + const method = getServiceMethodOfClient(sdkPackage); + strictEqual(method.name, "myOp"); + strictEqual(method.kind, "basic"); + strictEqual(method.parameters.length, 3); + + const a = method.parameters.find((x) => x.name === "a"); + ok(a); + strictEqual(a.kind, "method"); + strictEqual(a.optional, false); + strictEqual(a.onClient, false); + strictEqual(a.isApiVersionParam, false); + strictEqual(a.type.kind, "string"); + + const b = method.parameters.find((x) => x.name === "b"); + ok(b); + strictEqual(b.kind, "method"); + strictEqual(b.optional, false); + strictEqual(b.onClient, false); + strictEqual(b.isApiVersionParam, false); + strictEqual(b.type.kind, "string"); + + const serviceOperation = method.operation; + const bodyParameter = serviceOperation.bodyParam; + ok(bodyParameter); + + strictEqual(bodyParameter.kind, "body"); + strictEqual(bodyParameter.onClient, false); + strictEqual(bodyParameter.optional, false); + strictEqual(bodyParameter.type, sdkPackage.models[0]); + strictEqual(bodyParameter.type.usage, UsageFlags.Spread | UsageFlags.Json); + strictEqual(bodyParameter.type.access, "internal"); + + strictEqual(bodyParameter.correspondingMethodParams.length, 2); + deepStrictEqual(bodyParameter.correspondingMethodParams[0], a); + deepStrictEqual(bodyParameter.correspondingMethodParams[1], b); + }); + + it("explicit multiple spread with metadata", async () => { + await runner.compile(`@server("http://localhost:3000", "endpoint") + @service({}) + namespace My.Service; + model Test1 { + @header + a: string; + } + model Test2 { + b: string; + } + op myOp(...Test1, ...Test2): void; + `); + const sdkPackage = runner.context.sdkPackage; + strictEqual(sdkPackage.models.length, 1); + + const method = getServiceMethodOfClient(sdkPackage); + strictEqual(method.name, "myOp"); + strictEqual(method.kind, "basic"); + strictEqual(method.parameters.length, 3); + + const a = method.parameters.find((x) => x.name === "a"); + ok(a); + strictEqual(a.kind, "method"); + strictEqual(a.optional, false); + strictEqual(a.onClient, false); + strictEqual(a.isApiVersionParam, false); + strictEqual(a.type.kind, "string"); + + const b = method.parameters.find((x) => x.name === "b"); + ok(b); + strictEqual(b.kind, "method"); + strictEqual(b.optional, false); + strictEqual(b.onClient, false); + strictEqual(b.isApiVersionParam, false); + strictEqual(b.type.kind, "string"); + + const serviceOperation = method.operation; + const headerParameter = serviceOperation.parameters.find((p) => (p.name = "a")); + ok(headerParameter); + strictEqual(headerParameter.kind, "header"); + strictEqual(headerParameter.onClient, false); + strictEqual(headerParameter.optional, false); + strictEqual(headerParameter.type.kind, "string"); + + strictEqual(headerParameter.correspondingMethodParams.length, 1); + deepStrictEqual(headerParameter.correspondingMethodParams[0], a); + + const bodyParameter = serviceOperation.bodyParam; + ok(bodyParameter); + + strictEqual(bodyParameter.kind, "body"); + strictEqual(bodyParameter.onClient, false); + strictEqual(bodyParameter.optional, false); + strictEqual(bodyParameter.type, sdkPackage.models[0]); + strictEqual(bodyParameter.type.usage, UsageFlags.Spread | UsageFlags.Json); + strictEqual(bodyParameter.type.access, "internal"); + + strictEqual(bodyParameter.correspondingMethodParams.length, 1); + deepStrictEqual(bodyParameter.correspondingMethodParams[0], b); + }); + + it("spread idempotent", async () => { + await runner.compile(`@server("http://localhost:3000", "endpoint") + @service({}) + namespace My.Service; + alias FooAlias = { + @path id: string; + @doc("name of the Foo") + name: string; + }; + op test(...FooAlias): void; + `); + const sdkPackage = runner.context.sdkPackage; + strictEqual(sdkPackage.models.length, 1); + getAllModels(runner.context); + + strictEqual(sdkPackage.models[0].name, "TestRequest"); + strictEqual(sdkPackage.models[0].usage, UsageFlags.Spread | UsageFlags.Json); + }); + + it("model used as simple spread", async () => { + await runner.compile(`@server("http://localhost:3000", "endpoint") + @service({}) + namespace My.Service; + model Test { + prop: string; + } + op test(...Test): void; + `); + const sdkPackage = runner.context.sdkPackage; + strictEqual(sdkPackage.models.length, 1); + getAllModels(runner.context); + + strictEqual(sdkPackage.models[0].name, "Test"); + strictEqual(sdkPackage.models[0].usage, UsageFlags.Spread | UsageFlags.Json); + strictEqual(sdkPackage.models[0].access, "internal"); + }); + + it("model used as simple spread and output", async () => { + await runner.compile(`@server("http://localhost:3000", "endpoint") + @service({}) + namespace My.Service; + model Test { + prop: string; + } + op test(...Test): Test; + `); + const sdkPackage = runner.context.sdkPackage; + strictEqual(sdkPackage.models.length, 1); + getAllModels(runner.context); + + strictEqual(sdkPackage.models[0].name, "Test"); + strictEqual( + sdkPackage.models[0].usage, + UsageFlags.Spread | UsageFlags.Output | UsageFlags.Json + ); + strictEqual(sdkPackage.models[0].access, "public"); + }); + + it("model used as simple spread and other operation's output", async () => { + await runner.compile(`@server("http://localhost:3000", "endpoint") + @service({}) + namespace My.Service; + model Test { + prop: string; + } + op test(...Test): void; + + @route("/another") + op another(): Test; + `); + const sdkPackage = runner.context.sdkPackage; + strictEqual(sdkPackage.models.length, 1); + getAllModels(runner.context); + + strictEqual(sdkPackage.models[0].name, "Test"); + strictEqual( + sdkPackage.models[0].usage, + UsageFlags.Spread | UsageFlags.Output | UsageFlags.Json + ); + strictEqual(sdkPackage.models[0].access, "public"); + }); + + it("model used as simple spread and other operation's input", async () => { + await runner.compile(`@server("http://localhost:3000", "endpoint") + @service({}) + namespace My.Service; + model Test { + prop: string; + } + op test(...Test): void; + + @route("/another") + op another(@body body: Test): void; + `); + const sdkPackage = runner.context.sdkPackage; + strictEqual(sdkPackage.models.length, 1); + getAllModels(runner.context); + + strictEqual(sdkPackage.models[0].name, "Test"); + strictEqual(sdkPackage.models[0].usage, UsageFlags.Spread | UsageFlags.Input | UsageFlags.Json); + strictEqual(sdkPackage.models[0].access, "public"); + }); +}); diff --git a/packages/typespec-client-generator-core/test/types/multipart-types.test.ts b/packages/typespec-client-generator-core/test/types/multipart-types.test.ts index 6f351daada..ddee9184e1 100644 --- a/packages/typespec-client-generator-core/test/types/multipart-types.test.ts +++ b/packages/typespec-client-generator-core/test/types/multipart-types.test.ts @@ -81,7 +81,7 @@ describe("typespec-client-generator-core: multipart types", () => { ); const models = runner.context.sdkPackage.models; strictEqual(models.length, 2); - const modelA = models.find((x) => x.name === "MultipartOperationRequest"); + const modelA = models.find((x) => x.name === "A"); ok(modelA); strictEqual(modelA.kind, "model"); strictEqual(modelA.isFormDataType, true); // eslint-disable-line deprecation/deprecation @@ -93,7 +93,7 @@ describe("typespec-client-generator-core: multipart types", () => { ok(modelAProp.multipartOptions); strictEqual(modelAProp.multipartOptions.isFilePart, true); - const modelB = models.find((x) => x.name === "NormalOperationRequest"); + const modelB = models.find((x) => x.name === "B"); ok(modelB); strictEqual(modelB.kind, "model"); strictEqual(modelB.isFormDataType, false); // eslint-disable-line deprecation/deprecation