From 11a4b32bda9806281512f50633f7cf27ea93f7ac Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Thu, 11 Jul 2024 17:46:29 -0400 Subject: [PATCH 01/11] for singular base urls, set type to be a string with client default value --- .../src/package.ts | 28 ++++++++++++++++++- .../test/package.test.ts | 24 ++++++++++++++-- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index 6b3cf9c141..75b5d21f16 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -508,7 +508,33 @@ function getSdkEndpointParameter( ); } } - optional = Boolean(servers[0].url.length && templateArguments.every((param) => param.optional)); + if (templateArguments.length === 0) { + templateArguments.push({ + kind: "path", + name: "endpoint", + isGeneratedName: true, + description: "Service host", + onClient: true, + urlEncode: false, + optional: false, + serializedName: "endpoint", + correspondingMethodParams: [], + type: { + kind: "string", + encode: "string", + decorators: [], + }, + clientDefaultValue: servers[0].url, + isApiVersionParam: false, + apiVersions: context.__tspTypeToApiVersions.get(client.type)!, + crossLanguageDefinitionId: `${getCrossLanguageDefinitionId(context, client.service)}.endpoint`, + decorators: [], + }); + } + optional = Boolean( + servers[0].url.length && + templateArguments.every((param) => param.clientDefaultValue !== undefined) + ); } return diagnostics.wrap({ kind: "endpoint", diff --git a/packages/typespec-client-generator-core/test/package.test.ts b/packages/typespec-client-generator-core/test/package.test.ts index f0422a1ad0..4a64135b79 100644 --- a/packages/typespec-client-generator-core/test/package.test.ts +++ b/packages/typespec-client-generator-core/test/package.test.ts @@ -135,7 +135,15 @@ describe("typespec-client-generator-core: package", () => { strictEqual(endpointParam.type.kind, "endpoint"); strictEqual(endpointParam.type.serverUrl, "http://localhost:3000"); strictEqual(endpointParam.urlEncode, false); - strictEqual(endpointParam.type.templateArguments.length, 0); + strictEqual(endpointParam.type.templateArguments.length, 1); + const templateArg = endpointParam.type.templateArguments[0]; + strictEqual(templateArg.kind, "path"); + strictEqual(templateArg.name, "endpoint"); + strictEqual(templateArg.urlEncode, false); + strictEqual(templateArg.type.kind, "string"); + strictEqual(templateArg.optional, false); + strictEqual(templateArg.onClient, true); + strictEqual(templateArg.clientDefaultValue, "http://localhost:3000"); }); it("initialization default endpoint with apikey auth", async () => { @@ -156,7 +164,11 @@ describe("typespec-client-generator-core: package", () => { )[0]; strictEqual(endpointParam.type.kind, "endpoint"); strictEqual(endpointParam.type.serverUrl, "http://localhost:3000"); - strictEqual(endpointParam.type.templateArguments.length, 0); + strictEqual(endpointParam.type.templateArguments.length, 1); + const templateArg = endpointParam.type.templateArguments[0]; + strictEqual(templateArg.kind, "path"); + strictEqual(templateArg.type.kind, "string"); + strictEqual(templateArg.clientDefaultValue, "http://localhost:3000"); const credentialParam = client.initialization.properties.filter( (p): p is SdkCredentialParameter => p.kind === "credential" @@ -195,7 +207,13 @@ describe("typespec-client-generator-core: package", () => { )[0]; strictEqual(endpointParam.type.kind, "endpoint"); strictEqual(endpointParam.type.serverUrl, "http://localhost:3000"); - strictEqual(endpointParam.type.templateArguments.length, 0); + strictEqual(endpointParam.type.templateArguments.length, 1); + const templateArg = endpointParam.type.templateArguments[0]; + strictEqual(templateArg.kind, "path"); + strictEqual(templateArg.type.kind, "string"); + strictEqual(templateArg.optional, false); + strictEqual(templateArg.onClient, true); + strictEqual(templateArg.clientDefaultValue, "http://localhost:3000"); const credentialParam = client.initialization.properties.filter( (p): p is SdkCredentialParameter => p.kind === "credential" From 77733cc1679df41c11c1781dd17cf2b47a044a2d Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Thu, 11 Jul 2024 17:47:47 -0400 Subject: [PATCH 02/11] add changeset --- .chronus/changes/default_endpoint-2024-6-11-17-47-42.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .chronus/changes/default_endpoint-2024-6-11-17-47-42.md diff --git a/.chronus/changes/default_endpoint-2024-6-11-17-47-42.md b/.chronus/changes/default_endpoint-2024-6-11-17-47-42.md new file mode 100644 index 0000000000..9e25431bc8 --- /dev/null +++ b/.chronus/changes/default_endpoint-2024-6-11-17-47-42.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@azure-tools/typespec-client-generator-core" +--- + +Represent endpoints with literal values as having a default value of the literal value instead, so end users can always override \ No newline at end of file From 3dfa08a7511083bf0537387a8dc51598acbe1272 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Thu, 11 Jul 2024 17:49:06 -0400 Subject: [PATCH 03/11] update changeset --- .chronus/changes/default_endpoint-2024-6-11-17-47-42.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.chronus/changes/default_endpoint-2024-6-11-17-47-42.md b/.chronus/changes/default_endpoint-2024-6-11-17-47-42.md index 9e25431bc8..2f9f39c375 100644 --- a/.chronus/changes/default_endpoint-2024-6-11-17-47-42.md +++ b/.chronus/changes/default_endpoint-2024-6-11-17-47-42.md @@ -4,4 +4,4 @@ packages: - "@azure-tools/typespec-client-generator-core" --- -Represent endpoints with literal values as having a default value of the literal value instead, so end users can always override \ No newline at end of file +Make literal endpoints overridable From 18bd34f9e02c5078660fb86defd321fc76273aa4 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Thu, 11 Jul 2024 18:25:13 -0400 Subject: [PATCH 04/11] rename default template arg to baseUrl --- packages/typespec-client-generator-core/src/package.ts | 6 +++--- .../typespec-client-generator-core/test/package.test.ts | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index 75b5d21f16..ab795b8d8d 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -511,13 +511,13 @@ function getSdkEndpointParameter( if (templateArguments.length === 0) { templateArguments.push({ kind: "path", - name: "endpoint", + name: "baseUrl", isGeneratedName: true, description: "Service host", onClient: true, urlEncode: false, optional: false, - serializedName: "endpoint", + serializedName: "baseUrl", correspondingMethodParams: [], type: { kind: "string", @@ -527,7 +527,7 @@ function getSdkEndpointParameter( clientDefaultValue: servers[0].url, isApiVersionParam: false, apiVersions: context.__tspTypeToApiVersions.get(client.type)!, - crossLanguageDefinitionId: `${getCrossLanguageDefinitionId(context, client.service)}.endpoint`, + crossLanguageDefinitionId: `${getCrossLanguageDefinitionId(context, client.service)}.baseUrl`, decorators: [], }); } diff --git a/packages/typespec-client-generator-core/test/package.test.ts b/packages/typespec-client-generator-core/test/package.test.ts index 4a64135b79..f4508ee9c5 100644 --- a/packages/typespec-client-generator-core/test/package.test.ts +++ b/packages/typespec-client-generator-core/test/package.test.ts @@ -138,7 +138,8 @@ describe("typespec-client-generator-core: package", () => { strictEqual(endpointParam.type.templateArguments.length, 1); const templateArg = endpointParam.type.templateArguments[0]; strictEqual(templateArg.kind, "path"); - strictEqual(templateArg.name, "endpoint"); + strictEqual(templateArg.name, "baseUrl"); + strictEqual(templateArg.serializedName, "baseUrl"); strictEqual(templateArg.urlEncode, false); strictEqual(templateArg.type.kind, "string"); strictEqual(templateArg.optional, false); From 825e7aa0db0330ff0243aac9a9849d087df4ea19 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Sat, 3 Aug 2024 18:28:01 -0400 Subject: [PATCH 05/11] add overridable endpoint as union to templated endpoints --- .../src/interfaces.ts | 2 +- .../src/package.ts | 82 +++++++++---------- .../test/package.test.ts | 60 ++++++++++---- 3 files changed, 85 insertions(+), 59 deletions(-) diff --git a/packages/typespec-client-generator-core/src/interfaces.ts b/packages/typespec-client-generator-core/src/interfaces.ts index c6d77f0a4b..e49036ff1e 100644 --- a/packages/typespec-client-generator-core/src/interfaces.ts +++ b/packages/typespec-client-generator-core/src/interfaces.ts @@ -420,7 +420,7 @@ export interface SdkEndpointParameter extends SdkModelPropertyTypeBase { urlEncode: boolean; onClient: true; serializedName?: string; - type: SdkEndpointType; + type: SdkEndpointType | SdkUnionType; } export interface SdkCredentialParameter extends SdkModelPropertyTypeBase { diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index 4087a4d264..f2e01d44ce 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -44,6 +44,7 @@ import { SdkServiceOperation, SdkServiceParameter, SdkType, + SdkUnionType, TCGCContext, UsageFlags, } from "./interfaces.js"; @@ -419,35 +420,35 @@ function getSdkEndpointParameter( ): [SdkEndpointParameter, readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); const servers = getServers(context.program, client.service); - let type: SdkEndpointType; + let type: SdkEndpointType | SdkUnionType; let optional: boolean = false; + const defaultOverridableEndpointType: SdkEndpointType = { + kind: "endpoint", + serverUrl: "{endpoint}", + templateArguments: [ + { + name: "endpoint", + isGeneratedName: true, + description: "Service host", + kind: "path", + onClient: true, + urlEncode: false, + optional: false, + serializedName: "endpoint", + correspondingMethodParams: [], + type: getTypeSpecBuiltInType(context, "string"), + isApiVersionParam: false, + apiVersions: context.__tspTypeToApiVersions.get(client.type)!, + crossLanguageDefinitionId: `${getCrossLanguageDefinitionId(context, client.service)}.endpoint`, + decorators: [], + } + ], + decorators: [], + }; if (servers === undefined || servers.length > 1) { // if there is no defined server url, or if there are more than one // we will return a mandatory endpoint parameter in initialization - const name = "endpoint"; - type = { - kind: "endpoint", - serverUrl: "{endpoint}", - templateArguments: [ - { - name, - isGeneratedName: true, - description: "Service host", - kind: "path", - onClient: true, - urlEncode: false, - optional: false, - serializedName: "endpoint", - correspondingMethodParams: [], - type: getTypeSpecBuiltInType(context, "string"), - isApiVersionParam: false, - apiVersions: context.__tspTypeToApiVersions.get(client.type)!, - crossLanguageDefinitionId: `${getCrossLanguageDefinitionId(context, client.service)}.endpoint`, - decorators: [], - }, - ], - decorators: [], - }; + type = defaultOverridableEndpointType; } else { // this means we have one server const templateArguments: SdkPathParameter[] = []; @@ -484,28 +485,21 @@ function getSdkEndpointParameter( ); } } + const isOverridable = templateArguments.length === 1 && type.serverUrl.startsWith("{") && type.serverUrl.endsWith("}"); if (templateArguments.length === 0) { - templateArguments.push({ - kind: "path", - name: "baseUrl", + type = defaultOverridableEndpointType; + type.templateArguments[0].clientDefaultValue = servers[0].url; + } else if (!isOverridable) { + // if the entire endpoint is already overridable, we don't need to add + // the defaultOverridableEndpointType as a union + type = { + kind: "union", + values: [defaultOverridableEndpointType, type], + name: createGeneratedName(context, client.service, "Endpoint"), isGeneratedName: true, - description: "Service host", - onClient: true, - urlEncode: false, - optional: false, - serializedName: "baseUrl", - correspondingMethodParams: [], - type: { - kind: "string", - encode: "string", - decorators: [], - }, - clientDefaultValue: servers[0].url, - isApiVersionParam: false, - apiVersions: context.__tspTypeToApiVersions.get(client.type)!, - crossLanguageDefinitionId: `${getCrossLanguageDefinitionId(context, client.service)}.baseUrl`, + crossLanguageDefinitionId: `${getCrossLanguageDefinitionId(context, client.service)}.endpoint`, decorators: [], - }); + } } optional = Boolean( servers[0].url.length && diff --git a/packages/typespec-client-generator-core/test/package.test.ts b/packages/typespec-client-generator-core/test/package.test.ts index e91e0396fb..0753cf8faf 100644 --- a/packages/typespec-client-generator-core/test/package.test.ts +++ b/packages/typespec-client-generator-core/test/package.test.ts @@ -8,6 +8,7 @@ import { SdkCredentialParameter, SdkCredentialType, SdkEndpointParameter, + SdkEndpointType, SdkHeaderParameter, SdkHttpOperation, SdkPackage, @@ -134,13 +135,13 @@ describe("typespec-client-generator-core: package", () => { strictEqual(endpointParam.onClient, true); strictEqual(endpointParam.optional, true); strictEqual(endpointParam.type.kind, "endpoint"); - strictEqual(endpointParam.type.serverUrl, "http://localhost:3000"); + strictEqual(endpointParam.type.serverUrl, "{endpoint}"); strictEqual(endpointParam.urlEncode, false); strictEqual(endpointParam.type.templateArguments.length, 1); const templateArg = endpointParam.type.templateArguments[0]; strictEqual(templateArg.kind, "path"); - strictEqual(templateArg.name, "baseUrl"); - strictEqual(templateArg.serializedName, "baseUrl"); + strictEqual(templateArg.name, "endpoint"); + strictEqual(templateArg.serializedName, "endpoint"); strictEqual(templateArg.urlEncode, false); strictEqual(templateArg.type.kind, "string"); strictEqual(templateArg.optional, false); @@ -165,7 +166,7 @@ describe("typespec-client-generator-core: package", () => { (p): p is SdkEndpointParameter => p.kind === "endpoint" )[0]; strictEqual(endpointParam.type.kind, "endpoint"); - strictEqual(endpointParam.type.serverUrl, "http://localhost:3000"); + strictEqual(endpointParam.type.serverUrl, "{endpoint}"); strictEqual(endpointParam.type.templateArguments.length, 1); const templateArg = endpointParam.type.templateArguments[0]; strictEqual(templateArg.kind, "path"); @@ -208,7 +209,7 @@ describe("typespec-client-generator-core: package", () => { (p): p is SdkEndpointParameter => p.kind === "endpoint" )[0]; strictEqual(endpointParam.type.kind, "endpoint"); - strictEqual(endpointParam.type.serverUrl, "http://localhost:3000"); + strictEqual(endpointParam.type.serverUrl, "{endpoint}"); strictEqual(endpointParam.type.templateArguments.length, 1); const templateArg = endpointParam.type.templateArguments[0]; strictEqual(templateArg.kind, "path"); @@ -259,7 +260,12 @@ describe("typespec-client-generator-core: package", () => { (p): p is SdkEndpointParameter => p.kind === "endpoint" )[0]; strictEqual(endpointParam.type.kind, "endpoint"); - strictEqual(endpointParam.type.serverUrl, "http://localhost:3000"); + strictEqual(endpointParam.type.serverUrl, "{endpoint}"); + strictEqual(endpointParam.type.templateArguments.length, 1); + const templateArg = endpointParam.type.templateArguments[0]; + strictEqual(templateArg.kind, "path"); + strictEqual(templateArg.name, "endpoint"); + strictEqual(templateArg.clientDefaultValue, "http://localhost:3000"); const credentialParam = client.initialization.properties.filter( (p): p is SdkCredentialParameter => p.kind === "credential" @@ -391,17 +397,29 @@ describe("typespec-client-generator-core: package", () => { strictEqual(endpointParam.kind, "endpoint"); const endpointParamType = endpointParam.type; - strictEqual(endpointParamType.kind, "endpoint"); - strictEqual(endpointParamType.serverUrl, "{endpoint}/server/path/multiple/{apiVersion}"); - - strictEqual(endpointParamType.templateArguments.length, 2); - const endpointTemplateArg = endpointParamType.templateArguments[0]; + strictEqual(endpointParamType.kind, "union"); + strictEqual(endpointParamType.values.length, 2); + + const overridableEndpoint = endpointParamType.values.find( + x => x.kind === "endpoint" && x.serverUrl === "{endpoint}" + ) as SdkEndpointType; + ok(overridableEndpoint); + strictEqual(overridableEndpoint.templateArguments.length, 1); + strictEqual(overridableEndpoint.templateArguments[0].name, "endpoint"); + strictEqual(overridableEndpoint.templateArguments[0].clientDefaultValue, undefined); + + const templatedEndpoint = endpointParamType.values.find( + x => x.kind === "endpoint" && x.serverUrl === "{endpoint}/server/path/multiple/{apiVersion}" + ) as SdkEndpointType; + ok(templatedEndpoint); + strictEqual(templatedEndpoint.templateArguments.length, 2); + const endpointTemplateArg = templatedEndpoint.templateArguments[0]; strictEqual(endpointTemplateArg.name, "endpoint"); strictEqual(endpointTemplateArg.onClient, true); strictEqual(endpointTemplateArg.optional, false); strictEqual(endpointTemplateArg.kind, "path"); - const apiVersionParam = endpointParamType.templateArguments[1]; + const apiVersionParam = templatedEndpoint.templateArguments[1]; strictEqual(apiVersionParam.clientDefaultValue, "v1.0"); strictEqual(apiVersionParam.urlEncode, true); strictEqual(apiVersionParam.name, "apiVersion"); @@ -510,7 +528,15 @@ describe("typespec-client-generator-core: package", () => { strictEqual(endpointParam.optional, true); strictEqual(endpointParam.onClient, true); strictEqual(endpointParam.type.kind, "endpoint"); - strictEqual(endpointParam.type.serverUrl, "http://localhost:3000"); + strictEqual(endpointParam.type.serverUrl, "{endpoint}"); + + strictEqual(endpointParam.type.templateArguments.length, 1); + const endpointTemplateArg = endpointParam.type.templateArguments[0]; + strictEqual(endpointTemplateArg.name, "endpoint"); + strictEqual(endpointTemplateArg.onClient, true); + strictEqual(endpointTemplateArg.optional, false); + strictEqual(endpointTemplateArg.kind, "path"); + strictEqual(endpointTemplateArg.clientDefaultValue, "http://localhost:3000"); const apiVersionParam = client.initialization.properties.filter( (p) => p.isApiVersionParam @@ -576,7 +602,13 @@ describe("typespec-client-generator-core: package", () => { const endpointParam = client.initialization.properties.find((x) => x.kind === "endpoint"); ok(endpointParam); strictEqual(endpointParam.type.kind, "endpoint"); - strictEqual(endpointParam.type.serverUrl, "http://localhost:3000"); + strictEqual(endpointParam.type.serverUrl, "{endpoint}"); + strictEqual(endpointParam.type.templateArguments.length, 1); + const templateArg = endpointParam.type.templateArguments[0]; + strictEqual(templateArg.kind, "path"); + strictEqual(templateArg.name, "endpoint"); + strictEqual(templateArg.onClient, true); + strictEqual(templateArg.clientDefaultValue, "http://localhost:3000"); const apiVersionParam = client.initialization.properties.filter( (p) => p.isApiVersionParam From b0a4144b86004fd775420e0701df6628e2519d4e Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Sat, 3 Aug 2024 18:33:44 -0400 Subject: [PATCH 06/11] format --- packages/typespec-client-generator-core/src/package.ts | 9 ++++++--- .../typespec-client-generator-core/test/package.test.ts | 5 +++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index f2e01d44ce..c86ff26575 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -441,7 +441,7 @@ function getSdkEndpointParameter( apiVersions: context.__tspTypeToApiVersions.get(client.type)!, crossLanguageDefinitionId: `${getCrossLanguageDefinitionId(context, client.service)}.endpoint`, decorators: [], - } + }, ], decorators: [], }; @@ -485,7 +485,10 @@ function getSdkEndpointParameter( ); } } - const isOverridable = templateArguments.length === 1 && type.serverUrl.startsWith("{") && type.serverUrl.endsWith("}"); + const isOverridable = + templateArguments.length === 1 && + type.serverUrl.startsWith("{") && + type.serverUrl.endsWith("}"); if (templateArguments.length === 0) { type = defaultOverridableEndpointType; type.templateArguments[0].clientDefaultValue = servers[0].url; @@ -499,7 +502,7 @@ function getSdkEndpointParameter( isGeneratedName: true, crossLanguageDefinitionId: `${getCrossLanguageDefinitionId(context, client.service)}.endpoint`, decorators: [], - } + }; } optional = Boolean( servers[0].url.length && diff --git a/packages/typespec-client-generator-core/test/package.test.ts b/packages/typespec-client-generator-core/test/package.test.ts index 0753cf8faf..af0d5230bd 100644 --- a/packages/typespec-client-generator-core/test/package.test.ts +++ b/packages/typespec-client-generator-core/test/package.test.ts @@ -401,7 +401,7 @@ describe("typespec-client-generator-core: package", () => { strictEqual(endpointParamType.values.length, 2); const overridableEndpoint = endpointParamType.values.find( - x => x.kind === "endpoint" && x.serverUrl === "{endpoint}" + (x) => x.kind === "endpoint" && x.serverUrl === "{endpoint}" ) as SdkEndpointType; ok(overridableEndpoint); strictEqual(overridableEndpoint.templateArguments.length, 1); @@ -409,7 +409,8 @@ describe("typespec-client-generator-core: package", () => { strictEqual(overridableEndpoint.templateArguments[0].clientDefaultValue, undefined); const templatedEndpoint = endpointParamType.values.find( - x => x.kind === "endpoint" && x.serverUrl === "{endpoint}/server/path/multiple/{apiVersion}" + (x) => + x.kind === "endpoint" && x.serverUrl === "{endpoint}/server/path/multiple/{apiVersion}" ) as SdkEndpointType; ok(templatedEndpoint); strictEqual(templatedEndpoint.templateArguments.length, 2); From fddfaed36017b485ecc17a5fe0593afad352cce7 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Sat, 3 Aug 2024 18:43:43 -0400 Subject: [PATCH 07/11] update docs --- .../07tcgcTypes.mdx | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/docs/howtos/DataPlane Generation - DPG/07tcgcTypes.mdx b/docs/howtos/DataPlane Generation - DPG/07tcgcTypes.mdx index 80d97c713c..f44566eb99 100644 --- a/docs/howtos/DataPlane Generation - DPG/07tcgcTypes.mdx +++ b/docs/howtos/DataPlane Generation - DPG/07tcgcTypes.mdx @@ -68,13 +68,29 @@ const sdkContext = { { kind: "endpoint", type: { - kind: "endpoint", - serverUrl: "{endpoint}/widget", - templateArguments: [ + kind: "union", + values: [ { - name: "endpoint", - kind: "path", - ... + kind: "endpoint", + serverUrl: "{endpoint}", + templateArguments: [ + { + name: "endpoint", + kind: "path", + ... + } + ] + }, + { + kind: "endpoint", + serverUrl: "{endpoint}/widget", + templateArguments: [ + { + name: "endpoint", + kind: "path", + ... + } + ] } ] } @@ -973,15 +989,18 @@ These are parameters to client initialization and method son the client. These w #### SdkEndpointParameter -An `SdkEndpointParameter` represents a parameter to a client's endpoint. If the client is not parameterized, the endpoint parameter represents the entire endpoint input for the client. For parameterized clients, there will be as many `SdkEndpointParameter`s as needed to parameterize the client endpoint. +An `SdkEndpointParameter` represents a parameter to a client's endpoint. -```ts +TCGC will always give it to you as overridable: If the server URL is a constant, we will return a templated endpoint with a default value of the constant server URL. +In the case where the endpoint has extra template arguments, the type is a union of a completely-overridable endpoint, and an endpoint that accepts template arguments. + +```tsp export interface SdkEndpointParameter extends SdkModelPropertyTypeBase { kind: "endpoint"; urlEncode: boolean; onClient: true; serializedName?: string; - type: SdkEndpointType; + type: SdkEndpointType | SdkUnionType; } ``` @@ -1407,7 +1426,7 @@ export type AccessFlags = "internal" | "public"; ```ts export interface SdkEndpointType { kind: "endpoint"; - serverUrl?: string; + serverUrl: string; templateArguments: SdkPathParameter[]; } ``` From 4c45cec2bbc0ce63e4322c02ee596debe7fd5fc9 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Mon, 5 Aug 2024 12:35:38 -0400 Subject: [PATCH 08/11] allow multiple servers --- .../src/package.ts | 151 ++++++++++-------- .../test/package.test.ts | 2 +- 2 files changed, 83 insertions(+), 70 deletions(-) diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index c86ff26575..2e3ff3b028 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -2,13 +2,14 @@ import { getLroMetadata, getPagedResult } from "@azure-tools/typespec-azure-core import { Diagnostic, Operation, + Server, Type, createDiagnosticCollector, getNamespaceFullName, getService, ignoreDiagnostics, } from "@typespec/compiler"; -import { getServers } from "@typespec/http"; +import { getServers, HttpServer } from "@typespec/http"; import { resolveVersions } from "@typespec/versioning"; import { camelCase } from "change-case"; import { @@ -414,15 +415,14 @@ function getSdkMethods( return diagnostics.wrap(retval); } -function getSdkEndpointParameter( +function getEndpointTypeFromSingleServer( context: TCGCContext, - client: SdkClient | SdkOperationGroup -): [SdkEndpointParameter, readonly Diagnostic[]] { + client: SdkClient | SdkOperationGroup, + server: HttpServer | undefined, +): [SdkEndpointType[], readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); - const servers = getServers(context.program, client.service); - let type: SdkEndpointType | SdkUnionType; - let optional: boolean = false; - const defaultOverridableEndpointType: SdkEndpointType = { + const templateArguments: SdkPathParameter[] = []; + const types: SdkEndpointType[] = [{ kind: "endpoint", serverUrl: "{endpoint}", templateArguments: [ @@ -444,70 +444,83 @@ function getSdkEndpointParameter( }, ], decorators: [], - }; - if (servers === undefined || servers.length > 1) { - // if there is no defined server url, or if there are more than one - // we will return a mandatory endpoint parameter in initialization - type = defaultOverridableEndpointType; - } else { - // this means we have one server - const templateArguments: SdkPathParameter[] = []; - type = { + }]; + if (!server) return diagnostics.wrap(types); + for (const param of server.parameters.values()) { + const sdkParam = diagnostics.pipe(getSdkHttpParameter(context, param, undefined, "path")); + if (sdkParam.kind === "path") { + templateArguments.push(sdkParam); + sdkParam.onClient = true; + if (param.defaultValue && "value" in param.defaultValue) { + sdkParam.clientDefaultValue = param.defaultValue.value; + } + const apiVersionInfo = updateWithApiVersionInformation(context, param, client.type); + sdkParam.isApiVersionParam = apiVersionInfo.isApiVersionParam; + if (sdkParam.isApiVersionParam) { + sdkParam.clientDefaultValue = apiVersionInfo.clientDefaultValue; + } + sdkParam.apiVersions = getAvailableApiVersions(context, param, client.type); + } else { + diagnostics.add( + createDiagnostic({ + code: "server-param-not-path", + target: param, + format: { + templateArgumentName: sdkParam.name, + templateArgumentType: sdkParam.kind, + }, + }) + ); + } + } + const isOverridable = + templateArguments.length === 1 && + server.url.startsWith("{") && + server.url.endsWith("}"); + + if (templateArguments.length === 0) { + types[0].templateArguments[0].clientDefaultValue = server.url; + } else if (!isOverridable) { + // if the entire endpoint is already overridable, we don't need to add + // the defaultOverridableEndpointType as a union + types.push({ kind: "endpoint", - serverUrl: servers[0].url, + serverUrl: server.url, templateArguments, decorators: [], - }; - for (const param of servers[0].parameters.values()) { - const sdkParam = diagnostics.pipe(getSdkHttpParameter(context, param, undefined, "path")); - if (sdkParam.kind === "path") { - templateArguments.push(sdkParam); - sdkParam.onClient = true; - if (param.defaultValue && "value" in param.defaultValue) { - sdkParam.clientDefaultValue = param.defaultValue.value; - } - const apiVersionInfo = updateWithApiVersionInformation(context, param, client.type); - sdkParam.isApiVersionParam = apiVersionInfo.isApiVersionParam; - if (sdkParam.isApiVersionParam) { - sdkParam.clientDefaultValue = apiVersionInfo.clientDefaultValue; - } - sdkParam.apiVersions = getAvailableApiVersions(context, param, client.type); - } else { - diagnostics.add( - createDiagnostic({ - code: "server-param-not-path", - target: param, - format: { - templateArgumentName: sdkParam.name, - templateArgumentType: sdkParam.kind, - }, - }) - ); - } - } - const isOverridable = - templateArguments.length === 1 && - type.serverUrl.startsWith("{") && - type.serverUrl.endsWith("}"); - if (templateArguments.length === 0) { - type = defaultOverridableEndpointType; - type.templateArguments[0].clientDefaultValue = servers[0].url; - } else if (!isOverridable) { - // if the entire endpoint is already overridable, we don't need to add - // the defaultOverridableEndpointType as a union - type = { - kind: "union", - values: [defaultOverridableEndpointType, type], - name: createGeneratedName(context, client.service, "Endpoint"), - isGeneratedName: true, - crossLanguageDefinitionId: `${getCrossLanguageDefinitionId(context, client.service)}.endpoint`, - decorators: [], - }; + }) + } + return diagnostics.wrap(types); +} + +function getSdkEndpointParameter( + context: TCGCContext, + client: SdkClient | SdkOperationGroup +): [SdkEndpointParameter, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + const servers = getServers(context.program, client.service); + const types: SdkEndpointType[] = []; + + if (servers === undefined) { + // if there is no defined server url, we will return an overridable endpoint + types.push(...diagnostics.pipe(getEndpointTypeFromSingleServer(context, client, undefined))); + } else { + for (const server of servers) { + types.push(...diagnostics.pipe(getEndpointTypeFromSingleServer(context, client, server))); } - optional = Boolean( - servers[0].url.length && - templateArguments.every((param) => param.clientDefaultValue !== undefined) - ); + } + let type: SdkEndpointType | SdkUnionType; + if (types.length > 1) { + type = { + kind: "union", + values: types, + name: createGeneratedName(context, client.service, "Endpoint"), + isGeneratedName: true, + crossLanguageDefinitionId: getCrossLanguageDefinitionId(context, client.service), + decorators: [], + }; + } else { + type = types[0]; } return diagnostics.wrap({ kind: "endpoint", @@ -518,7 +531,7 @@ function getSdkEndpointParameter( onClient: true, urlEncode: false, apiVersions: context.__tspTypeToApiVersions.get(client.type)!, - optional, + optional: false, isApiVersionParam: false, crossLanguageDefinitionId: `${getCrossLanguageDefinitionId(context, client.service)}.endpoint`, decorators: [], diff --git a/packages/typespec-client-generator-core/test/package.test.ts b/packages/typespec-client-generator-core/test/package.test.ts index af0d5230bd..06046b9a38 100644 --- a/packages/typespec-client-generator-core/test/package.test.ts +++ b/packages/typespec-client-generator-core/test/package.test.ts @@ -526,7 +526,7 @@ describe("typespec-client-generator-core: package", () => { ok(endpointParam); strictEqual(endpointParam.name, "endpoint"); strictEqual(endpointParam.kind, "endpoint"); - strictEqual(endpointParam.optional, true); + strictEqual(endpointParam.optional, false); strictEqual(endpointParam.onClient, true); strictEqual(endpointParam.type.kind, "endpoint"); strictEqual(endpointParam.type.serverUrl, "{endpoint}"); From af144e4d56955530c3c8e269991f620c336d9f67 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Mon, 5 Aug 2024 16:00:27 -0400 Subject: [PATCH 09/11] format --- .../src/package.ts | 34 +++++++++---------- .../test/package.test.ts | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index 2e3ff3b028..00c20ad4fd 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -1,13 +1,12 @@ import { getLroMetadata, getPagedResult } from "@azure-tools/typespec-azure-core"; import { - Diagnostic, - Operation, - Server, - Type, createDiagnosticCollector, + Diagnostic, getNamespaceFullName, getService, ignoreDiagnostics, + Operation, + Type, } from "@typespec/compiler"; import { getServers, HttpServer } from "@typespec/http"; import { resolveVersions } from "@typespec/versioning"; @@ -418,11 +417,11 @@ function getSdkMethods( function getEndpointTypeFromSingleServer( context: TCGCContext, client: SdkClient | SdkOperationGroup, - server: HttpServer | undefined, + server: HttpServer | undefined ): [SdkEndpointType[], readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); const templateArguments: SdkPathParameter[] = []; - const types: SdkEndpointType[] = [{ + const defaultOverridableEndpointType: SdkEndpointType = { kind: "endpoint", serverUrl: "{endpoint}", templateArguments: [ @@ -444,8 +443,9 @@ function getEndpointTypeFromSingleServer( }, ], decorators: [], - }]; - if (!server) return diagnostics.wrap(types); + }; + const types: SdkEndpointType[] = []; + if (!server) return diagnostics.wrap([defaultOverridableEndpointType]); for (const param of server.parameters.values()) { const sdkParam = diagnostics.pipe(getSdkHttpParameter(context, param, undefined, "path")); if (sdkParam.kind === "path") { @@ -474,21 +474,21 @@ function getEndpointTypeFromSingleServer( } } const isOverridable = - templateArguments.length === 1 && - server.url.startsWith("{") && - server.url.endsWith("}"); - + templateArguments.length === 1 && server.url.startsWith("{") && server.url.endsWith("}"); + if (templateArguments.length === 0) { + types.push(defaultOverridableEndpointType); types[0].templateArguments[0].clientDefaultValue = server.url; - } else if (!isOverridable) { - // if the entire endpoint is already overridable, we don't need to add - // the defaultOverridableEndpointType as a union + } else { types.push({ kind: "endpoint", serverUrl: server.url, templateArguments, decorators: [], - }) + }); + if (!isOverridable) { + types.push(defaultOverridableEndpointType); + } } return diagnostics.wrap(types); } @@ -500,7 +500,7 @@ function getSdkEndpointParameter( const diagnostics = createDiagnosticCollector(); const servers = getServers(context.program, client.service); const types: SdkEndpointType[] = []; - + if (servers === undefined) { // if there is no defined server url, we will return an overridable endpoint types.push(...diagnostics.pipe(getEndpointTypeFromSingleServer(context, client, undefined))); diff --git a/packages/typespec-client-generator-core/test/package.test.ts b/packages/typespec-client-generator-core/test/package.test.ts index 06046b9a38..7891212a32 100644 --- a/packages/typespec-client-generator-core/test/package.test.ts +++ b/packages/typespec-client-generator-core/test/package.test.ts @@ -133,7 +133,7 @@ describe("typespec-client-generator-core: package", () => { strictEqual(endpointParam.kind, "endpoint"); strictEqual(endpointParam.name, "endpoint"); strictEqual(endpointParam.onClient, true); - strictEqual(endpointParam.optional, true); + strictEqual(endpointParam.optional, false); strictEqual(endpointParam.type.kind, "endpoint"); strictEqual(endpointParam.type.serverUrl, "{endpoint}"); strictEqual(endpointParam.urlEncode, false); From c3a722d6610f64cd56dd298a879efe83f49a09d0 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Mon, 5 Aug 2024 19:46:27 -0400 Subject: [PATCH 10/11] udpate docs --- docs/howtos/DataPlane Generation - DPG/07tcgcTypes.mdx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/howtos/DataPlane Generation - DPG/07tcgcTypes.mdx b/docs/howtos/DataPlane Generation - DPG/07tcgcTypes.mdx index f44566eb99..3b554f497e 100644 --- a/docs/howtos/DataPlane Generation - DPG/07tcgcTypes.mdx +++ b/docs/howtos/DataPlane Generation - DPG/07tcgcTypes.mdx @@ -991,8 +991,11 @@ These are parameters to client initialization and method son the client. These w An `SdkEndpointParameter` represents a parameter to a client's endpoint. -TCGC will always give it to you as overridable: If the server URL is a constant, we will return a templated endpoint with a default value of the constant server URL. +TCGC will always give it to you as overridable: + +If the server URL is a constant, we will return a templated endpoint with a default value of the constant server URL. In the case where the endpoint has extra template arguments, the type is a union of a completely-overridable endpoint, and an endpoint that accepts template arguments. +If there are multiple servers, we will return the union of all of the possibilities. ```tsp export interface SdkEndpointParameter extends SdkModelPropertyTypeBase { From beff25901192a4950eeef36f9046fb2d7862380e Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Tue, 6 Aug 2024 11:49:39 -0400 Subject: [PATCH 11/11] format --- docs/howtos/DataPlane Generation - DPG/07tcgcTypes.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/howtos/DataPlane Generation - DPG/07tcgcTypes.mdx b/docs/howtos/DataPlane Generation - DPG/07tcgcTypes.mdx index 3b554f497e..68c25ca20a 100644 --- a/docs/howtos/DataPlane Generation - DPG/07tcgcTypes.mdx +++ b/docs/howtos/DataPlane Generation - DPG/07tcgcTypes.mdx @@ -991,7 +991,7 @@ These are parameters to client initialization and method son the client. These w An `SdkEndpointParameter` represents a parameter to a client's endpoint. -TCGC will always give it to you as overridable: +TCGC will always give it to you as overridable: If the server URL is a constant, we will return a templated endpoint with a default value of the constant server URL. In the case where the endpoint has extra template arguments, the type is a union of a completely-overridable endpoint, and an endpoint that accepts template arguments.