From d0508f8fc8421ae271239d755d3808a586de81d4 Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Sun, 1 Dec 2024 11:13:17 +0000 Subject: [PATCH 01/24] feat: thread servers through to IROperation --- .../openapi-code-generator/src/core/input.ts | 23 ++++++++++++++++++- .../src/core/openapi-types-normalized.ts | 16 +++++++++++++ .../src/core/utils.spec.ts | 18 +++++++++++++++ .../openapi-code-generator/src/core/utils.ts | 10 ++++++++ .../typescript-koa.generator.spec.ts | 2 ++ 5 files changed, 68 insertions(+), 1 deletion(-) diff --git a/packages/openapi-code-generator/src/core/input.ts b/packages/openapi-code-generator/src/core/input.ts index ef844284..b83ce1ca 100644 --- a/packages/openapi-code-generator/src/core/input.ts +++ b/packages/openapi-code-generator/src/core/input.ts @@ -9,6 +9,7 @@ import type { RequestBody, Responses, Schema, + Server, xInternalPreproccess, } from "./openapi-types" import type { @@ -24,11 +25,13 @@ import type { IRPreprocess, IRRef, IRResponse, + IRServer, MaybeIRModel, } from "./openapi-types-normalized" import {isRef} from "./openapi-utils" import { camelCase, + coalesce, deepEqual, isHttpMethod, mediaTypeToIdentifier, @@ -48,7 +51,7 @@ export class Input { } servers() { - return this.loader.entryPoint.servers?.filter((it) => it.url) ?? [] + return this.normalizeServers(coalesce(this.loader.entryPoint.servers, [])) } allSchemas(): Record { @@ -129,6 +132,14 @@ export class Input { ...additionalAttributes, route, method, + servers: this.normalizeServers( + coalesce( + definition.servers, + paths.servers, + this.loader.entryPoint.servers, + [], + ), + ), parameters: params.concat( this.normalizeParameters(definition.parameters), ), @@ -216,6 +227,16 @@ export class Input { return this.loader.preprocess(maybePreprocess) } + private normalizeServers(servers: Server[]): IRServer[] { + return servers + .filter((it) => it.url) + .map((it) => ({ + url: it.url, + description: it.description, + variables: it.variables ?? {}, + })) + } + private normalizeRequestBodyObject( operationId: string, requestBody?: RequestBody | Reference, diff --git a/packages/openapi-code-generator/src/core/openapi-types-normalized.ts b/packages/openapi-code-generator/src/core/openapi-types-normalized.ts index d861a374..4e269025 100644 --- a/packages/openapi-code-generator/src/core/openapi-types-normalized.ts +++ b/packages/openapi-code-generator/src/core/openapi-types-normalized.ts @@ -1,3 +1,4 @@ +import {ServerVariable} from "./openapi-types" import type {HttpMethod} from "./utils" export interface IRRef { @@ -99,6 +100,20 @@ export type IRModel = export type MaybeIRModel = IRModel | IRRef +export interface IRServer { + url: string + description: string | undefined + variables: { + [k: string]: IRServerVariable + } +} + +export interface IRServerVariable { + enum?: string[] + default: string + description?: string +} + export interface IRParameter { name: string in: "path" | "query" | "header" | "cookie" | "body" @@ -125,6 +140,7 @@ export interface IROperation { summary: string | undefined description: string | undefined deprecated: boolean + servers: IRServer[] } export interface IRRequestBody { diff --git a/packages/openapi-code-generator/src/core/utils.spec.ts b/packages/openapi-code-generator/src/core/utils.spec.ts index 2b46d775..b9b67dcf 100644 --- a/packages/openapi-code-generator/src/core/utils.spec.ts +++ b/packages/openapi-code-generator/src/core/utils.spec.ts @@ -1,6 +1,7 @@ import {describe, expect, it} from "@jest/globals" import { camelCase, + coalesce, kebabCase, mediaTypeToIdentifier, normalizeFilename, @@ -9,6 +10,23 @@ import { } from "./utils" describe("core/utils", () => { + describe("#coalesce", () => { + it("returns the first defined parameter", () => { + expect(coalesce(null, undefined, 1, 2)).toBe(1) + }) + it("returns 0 when it is the first defined parameter", () => { + expect(coalesce(null, undefined, 0, 2)).toBe(0) + }) + it("returns '' when it is the first defined parameter", () => { + expect(coalesce(null, undefined, "", "foo")).toBe("") + }) + it("throws if no parameters are defined", () => { + expect(() => coalesce(null, undefined)).toThrow( + "all arguments are null or undefined", + ) + }) + }) + describe("#titleCase", () => { it.each([ ["single", "Single"], diff --git a/packages/openapi-code-generator/src/core/utils.ts b/packages/openapi-code-generator/src/core/utils.ts index 2bdcfc91..7e7a579a 100644 --- a/packages/openapi-code-generator/src/core/utils.ts +++ b/packages/openapi-code-generator/src/core/utils.ts @@ -10,6 +10,16 @@ export function hasSingleElement(it: T[]): it is [T] { return it.length === 1 } +export function coalesce(...args: (T | undefined | null)[]): T { + const result = args.find((it) => it !== undefined && it !== null) + + if (result === undefined) { + throw new Error("all arguments are null or undefined") + } + + return result +} + export type HttpMethod = | "GET" | "POST" diff --git a/packages/openapi-code-generator/src/typescript/typescript-koa/typescript-koa.generator.spec.ts b/packages/openapi-code-generator/src/typescript/typescript-koa/typescript-koa.generator.spec.ts index 08456d54..3a75407f 100644 --- a/packages/openapi-code-generator/src/typescript/typescript-koa/typescript-koa.generator.spec.ts +++ b/packages/openapi-code-generator/src/typescript/typescript-koa/typescript-koa.generator.spec.ts @@ -78,6 +78,7 @@ describe("typescript/typescript-koa", () => { serverRouterBuilder.add({ method: "GET", parameters: [], + servers: [], operationId: "testOperation", deprecated: false, summary: undefined, @@ -90,6 +91,7 @@ describe("typescript/typescript-koa", () => { serverRouterBuilder.add({ method: "GET", parameters: [], + servers: [], operationId: "anotherTestOperation", deprecated: false, summary: undefined, From 6894024a16b66bb4fc14bf7f123868825fa92aae Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Sun, 1 Dec 2024 11:42:19 +0000 Subject: [PATCH 02/24] wip: generating a servers class --- e2e/src/generated/client/fetch/client.ts | 2 ++ .../generated/api.github.com.yaml/client.ts | 6 +++++ .../client.ts | 6 +++++ .../azure-resource-manager.tsp/client.ts | 6 +++++ .../src/generated/okta.idp.yaml/client.ts | 9 +++++++ .../src/generated/okta.oauth.yaml/client.ts | 9 +++++++ .../petstore-expanded.yaml/client.ts | 6 +++++ .../src/generated/stripe.yaml/client.ts | 6 +++++ .../src/generated/todo-lists.yaml/client.ts | 2 ++ .../openapi-code-generator/src/core/input.ts | 17 +++++++++--- .../src/core/openapi-types-normalized.ts | 4 +-- .../src/typescript/common/client-builder.ts | 26 +++++++++++++++++++ .../typescript-fetch-client-builder.ts | 2 ++ 13 files changed, 96 insertions(+), 5 deletions(-) diff --git a/e2e/src/generated/client/fetch/client.ts b/e2e/src/generated/client/fetch/client.ts index 191cec0c..25705f9a 100644 --- a/e2e/src/generated/client/fetch/client.ts +++ b/e2e/src/generated/client/fetch/client.ts @@ -14,6 +14,8 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" +export class E2ETestClientServers {} + export interface E2ETestClientConfig extends AbstractFetchClientConfig {} export class E2ETestClient extends AbstractFetchClient { diff --git a/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts index 6d2b8871..e9d0f733 100644 --- a/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts @@ -323,6 +323,12 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" +export class GitHubV3RestApiServers { + "https://api.github.com"() { + return "https://api.github.com" + } +} + export interface GitHubV3RestApiConfig extends AbstractFetchClientConfig { basePath: "https://api.github.com" | string } diff --git a/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts b/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts index 488b1738..7e508140 100644 --- a/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts +++ b/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts @@ -28,6 +28,12 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" +export class ContosoWidgetManagerServers { + "{endpoint}/widget"(endpoint: string = "") { + return "{endpoint}/widget".replace("{endpoint}", endpoint) + } +} + export interface ContosoWidgetManagerConfig extends AbstractFetchClientConfig { basePath: "{endpoint}/widget" | string } diff --git a/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts b/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts index 246e9b74..ba484879 100644 --- a/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts +++ b/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts @@ -20,6 +20,12 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" +export class ContosoProviderHubClientServers { + "https://management.azure.com"() { + return "https://management.azure.com" + } +} + export interface ContosoProviderHubClientConfig extends AbstractFetchClientConfig { basePath: "https://management.azure.com" | string diff --git a/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts index 927c5546..3f22b9a3 100644 --- a/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts @@ -27,6 +27,15 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" +export class MyAccountManagementServers { + "https://{yourOktaDomain}"(yourOktaDomain: string = "subdomain.okta.com") { + return "https://{yourOktaDomain}".replace( + "{yourOktaDomain}", + yourOktaDomain, + ) + } +} + export interface MyAccountManagementConfig extends AbstractFetchClientConfig { basePath: "https://{yourOktaDomain}" | string } diff --git a/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts index 2b1ebbfc..128db483 100644 --- a/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts @@ -41,6 +41,15 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" +export class OktaOpenIdConnectOAuth20Servers { + "https://{yourOktaDomain}"(yourOktaDomain: string = "subdomain.okta.com") { + return "https://{yourOktaDomain}".replace( + "{yourOktaDomain}", + yourOktaDomain, + ) + } +} + export interface OktaOpenIdConnectOAuth20Config extends AbstractFetchClientConfig { basePath: "https://{yourOktaDomain}" | string diff --git a/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts index c535e35d..ce0a7f45 100644 --- a/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts @@ -11,6 +11,12 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" +export class SwaggerPetstoreServers { + "https://petstore.swagger.io/v2"() { + return "https://petstore.swagger.io/v2" + } +} + export interface SwaggerPetstoreConfig extends AbstractFetchClientConfig { basePath: "https://petstore.swagger.io/v2" | string } diff --git a/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts index 7d93c866..74f94919 100644 --- a/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts @@ -170,6 +170,12 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" +export class StripeApiServers { + "https://api.stripe.com/"() { + return "https://api.stripe.com/" + } +} + export interface StripeApiConfig extends AbstractFetchClientConfig { basePath: "https://api.stripe.com/" | string } diff --git a/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/client.ts index e074e141..26abcf14 100644 --- a/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/client.ts @@ -18,6 +18,8 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" +export class TodoListsExampleApiServers {} + export interface TodoListsExampleApiConfig extends AbstractFetchClientConfig {} export class TodoListsExampleApi extends AbstractFetchClient { diff --git a/packages/openapi-code-generator/src/core/input.ts b/packages/openapi-code-generator/src/core/input.ts index b83ce1ca..81d788d4 100644 --- a/packages/openapi-code-generator/src/core/input.ts +++ b/packages/openapi-code-generator/src/core/input.ts @@ -26,6 +26,7 @@ import type { IRRef, IRResponse, IRServer, + IRServerVariable, MaybeIRModel, } from "./openapi-types-normalized" import {isRef} from "./openapi-utils" @@ -46,11 +47,12 @@ export class Input { readonly config: {extractInlineSchemas: boolean}, ) {} - name() { + name(): string { return this.loader.entryPoint.info.title } - servers() { + servers(): IRServer[] { + // todo: default of `{url: "/"}` where `/` is resolved relative to base path when generating from http specification return this.normalizeServers(coalesce(this.loader.entryPoint.servers, [])) } @@ -233,7 +235,16 @@ export class Input { .map((it) => ({ url: it.url, description: it.description, - variables: it.variables ?? {}, + variables: Object.fromEntries( + Object.entries(it.variables ?? {}).map(([key, value]) => [ + key, + { + enum: value.enum ?? [], + default: value.default, + description: value.description, + } satisfies IRServerVariable, + ]), + ), })) } diff --git a/packages/openapi-code-generator/src/core/openapi-types-normalized.ts b/packages/openapi-code-generator/src/core/openapi-types-normalized.ts index 4e269025..3e308fd9 100644 --- a/packages/openapi-code-generator/src/core/openapi-types-normalized.ts +++ b/packages/openapi-code-generator/src/core/openapi-types-normalized.ts @@ -109,9 +109,9 @@ export interface IRServer { } export interface IRServerVariable { - enum?: string[] + enum: string[] default: string - description?: string + description: string | undefined } export interface IRParameter { diff --git a/packages/openapi-code-generator/src/typescript/common/client-builder.ts b/packages/openapi-code-generator/src/typescript/common/client-builder.ts index fc68db5b..764f7abf 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-builder.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-builder.ts @@ -25,6 +25,32 @@ export abstract class TypescriptClientBuilder implements ICompilable { this.buildImports(imports) } + serversClass() { + const servers = this.input.servers() + + return ` + export class ${this.exportName}Servers { + ${servers + .map((server) => { + const vars = Object.entries(server.variables) + + const params = vars + .map( + ([name, variable]) => + `${name}: ${union(...variable.enum, "string")} = "${variable.default}"`, + ) + .join(",") + + return `"${server.url}"(${params}){ + return "${server.url}" + ${vars.length ? vars.map(([name]) => `.replace("{${name}}", ${name})`).join("\n") : ""} + }` + }) + .join("\n")} + } + ` + } + basePathType() { const serverUrls = this.input .servers() diff --git a/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts b/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts index 0fa7f05b..81ba1301 100644 --- a/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts +++ b/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts @@ -109,6 +109,8 @@ export class TypescriptFetchClientBuilder extends TypescriptClientBuilder { const basePathType = this.basePathType() return ` + ${this.serversClass()} + export interface ${clientName}Config extends AbstractFetchClientConfig { ${basePathType ? `basePath: ${basePathType}` : ""} } From e843339f3b83afcabe574ff53a439faae407b6e4 Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Sun, 1 Dec 2024 13:30:23 +0000 Subject: [PATCH 03/24] wip --- .../src/typescript/common/client-builder.ts | 82 +++++++++++++++---- 1 file changed, 64 insertions(+), 18 deletions(-) diff --git a/packages/openapi-code-generator/src/typescript/common/client-builder.ts b/packages/openapi-code-generator/src/typescript/common/client-builder.ts index 764f7abf..ee5b9480 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-builder.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-builder.ts @@ -1,5 +1,9 @@ import type {Input} from "../../core/input" -import type {IROperation} from "../../core/openapi-types-normalized" +import type { + IROperation, + IRServer, + IRServerVariable, +} from "../../core/openapi-types-normalized" import {ClientOperationBuilder} from "./client-operation-builder" import {CompilationUnit, type ICompilable} from "./compilation-units" import type {ImportBuilder} from "./import-builder" @@ -7,6 +11,12 @@ import type {SchemaBuilder} from "./schema-builders/schema-builder" import type {TypeBuilder} from "./type-builder" import {quotedStringLiteral, union} from "./type-utils" +class ServersBuilder implements ICompilable { + toCompilationUnit(): CompilationUnit { + throw new Error("Method not implemented.") + } +} + export abstract class TypescriptClientBuilder implements ICompilable { private readonly operations: string[] = [] @@ -28,25 +38,61 @@ export abstract class TypescriptClientBuilder implements ICompilable { serversClass() { const servers = this.input.servers() + const toParams = (variables: { + [k: string]: IRServerVariable + }) => { + return Object.entries(variables) + .map(([name, variable]) => { + const type = union(...variable.enum) + return `${name}${type ? `:${type}` : ""} = "${variable.default}"` + }) + .join(",") + } + + const toReplacer = (server: IRServer) => { + const vars = Object.entries(server.variables) + return `"${server.url}" + ${vars.length ? vars.map(([name]) => `.replace("{${name}}", ${name})`).join("\n") : ""}` + } + + const toSpecific = () => { + const urls = servers.map((it) => quotedStringLiteral(it.url)) + return `static specific(url: ${union(urls)}){ + switch(url) { + ${servers + .map( + (server) => ` + case ${quotedStringLiteral(server.url)}: + return { + with(${toParams(server.variables)}) { + return ${toReplacer(server)} as ${this.exportName}Server + } + }`, + ) + .join("\n")} + } + }` + } + + const defaultServer = servers[0] + return ` + export type Server = string & {__server__: T} + + export type ${this.exportName}Server = Server<"${this.exportName}Server"> + export class ${this.exportName}Servers { - ${servers - .map((server) => { - const vars = Object.entries(server.variables) - - const params = vars - .map( - ([name, variable]) => - `${name}: ${union(...variable.enum, "string")} = "${variable.default}"`, - ) - .join(",") - - return `"${server.url}"(${params}){ - return "${server.url}" - ${vars.length ? vars.map(([name]) => `.replace("{${name}}", ${name})`).join("\n") : ""} - }` - }) - .join("\n")} + ${ + defaultServer + ? `static default(${toParams(defaultServer.variables)}){ + return ${toReplacer(defaultServer)} as ${this.exportName}Server + }` + : "" + } + ${servers.length ? toSpecific() : ""} + static custom(url: string){ + return url as ${this.exportName}Server + } } ` } From 77322e4af973e374b9c1e85419109ba198946750 Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Sun, 1 Dec 2024 14:18:03 +0000 Subject: [PATCH 04/24] refactor: move to separate file --- .../src/typescript/common/client-builder.ts | 88 +++-------------- .../common/client-servers-builder.ts | 98 +++++++++++++++++++ .../typescript-fetch-client-builder.ts | 2 - 3 files changed, 112 insertions(+), 76 deletions(-) create mode 100644 packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts diff --git a/packages/openapi-code-generator/src/typescript/common/client-builder.ts b/packages/openapi-code-generator/src/typescript/common/client-builder.ts index ee5b9480..3dfece4e 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-builder.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-builder.ts @@ -1,22 +1,13 @@ import type {Input} from "../../core/input" -import type { - IROperation, - IRServer, - IRServerVariable, -} from "../../core/openapi-types-normalized" +import type {IROperation} from "../../core/openapi-types-normalized" import {ClientOperationBuilder} from "./client-operation-builder" +import {ClientServersBuilder} from "./client-servers-builder" import {CompilationUnit, type ICompilable} from "./compilation-units" import type {ImportBuilder} from "./import-builder" import type {SchemaBuilder} from "./schema-builders/schema-builder" import type {TypeBuilder} from "./type-builder" import {quotedStringLiteral, union} from "./type-utils" -class ServersBuilder implements ICompilable { - toCompilationUnit(): CompilationUnit { - throw new Error("Method not implemented.") - } -} - export abstract class TypescriptClientBuilder implements ICompilable { private readonly operations: string[] = [] @@ -35,68 +26,6 @@ export abstract class TypescriptClientBuilder implements ICompilable { this.buildImports(imports) } - serversClass() { - const servers = this.input.servers() - - const toParams = (variables: { - [k: string]: IRServerVariable - }) => { - return Object.entries(variables) - .map(([name, variable]) => { - const type = union(...variable.enum) - return `${name}${type ? `:${type}` : ""} = "${variable.default}"` - }) - .join(",") - } - - const toReplacer = (server: IRServer) => { - const vars = Object.entries(server.variables) - return `"${server.url}" - ${vars.length ? vars.map(([name]) => `.replace("{${name}}", ${name})`).join("\n") : ""}` - } - - const toSpecific = () => { - const urls = servers.map((it) => quotedStringLiteral(it.url)) - return `static specific(url: ${union(urls)}){ - switch(url) { - ${servers - .map( - (server) => ` - case ${quotedStringLiteral(server.url)}: - return { - with(${toParams(server.variables)}) { - return ${toReplacer(server)} as ${this.exportName}Server - } - }`, - ) - .join("\n")} - } - }` - } - - const defaultServer = servers[0] - - return ` - export type Server = string & {__server__: T} - - export type ${this.exportName}Server = Server<"${this.exportName}Server"> - - export class ${this.exportName}Servers { - ${ - defaultServer - ? `static default(${toParams(defaultServer.variables)}){ - return ${toReplacer(defaultServer)} as ${this.exportName}Server - }` - : "" - } - ${servers.length ? toSpecific() : ""} - static custom(url: string){ - return url as ${this.exportName}Server - } - } - ` - } - basePathType() { const serverUrls = this.input .servers() @@ -129,7 +58,18 @@ export abstract class TypescriptClientBuilder implements ICompilable { ): string toString(): string { - return this.buildClient(this.exportName, this.operations) + const servers = new ClientServersBuilder( + this.filename, + this.exportName, + this.input.servers(), + this.imports, + ) + const client = this.buildClient(this.exportName, this.operations) + + return ` + ${servers} + ${client} + ` } toCompilationUnit(): CompilationUnit { diff --git a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts new file mode 100644 index 00000000..6e0466ee --- /dev/null +++ b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts @@ -0,0 +1,98 @@ +import type { + IRServer, + IRServerVariable, +} from "../../core/openapi-types-normalized" +import {CompilationUnit, type ICompilable} from "./compilation-units" +import type {ImportBuilder} from "./import-builder" +import {quotedStringLiteral, union} from "./type-utils" + +export class ClientServersBuilder implements ICompilable { + constructor( + readonly filename: string, + readonly name: string, + readonly servers: IRServer[], + readonly imports: ImportBuilder, + ) {} + + private toParams(variables: { + [k: string]: IRServerVariable + }) { + return Object.entries(variables) + .map(([name, variable]) => { + const type = union(...variable.enum) + return `${name}${type ? `:${type}` : ""} = "${variable.default}"` + }) + .join(",") + } + + private toReplacer(server: IRServer) { + const vars = Object.entries(server.variables) + return `"${server.url}" ${vars.length ? vars.map(([name]) => `.replace("{${name}}", ${name})`).join("\n") : ""}` + } + + private toDefault() { + const defaultServer = this.servers[0] + + if (!defaultServer) { + return "" + } + + return ` + static default(${this.toParams(defaultServer.variables)}): ${this.typeExportName} { + return (${this.toReplacer(defaultServer)} as ${this.typeExportName}) + } + ` + } + + private toSpecific() { + if (!this.servers.length) { + return "" + } + + const urls = this.servers.map((it) => quotedStringLiteral(it.url)) + return `static specific(url: ${union(urls)}){ + switch(url) { + ${this.servers + .map( + (server) => ` + case ${quotedStringLiteral(server.url)}: + return { + with(${this.toParams(server.variables)}): ${this.typeExportName} { + return (${this.toReplacer(server)} as ${this.typeExportName}) + } + } + `, + ) + .join("\n")} + } + }` + } + + get classExportName(): string { + return `${this.name}Servers` + } + get typeExportName(): string { + return `${this.name}Server` + } + + toString() { + return ` + export type Server = string & {__server__: T} + + export type ${this.typeExportName} = Server<"${this.typeExportName}"> + + export class ${this.classExportName} { + ${this.toDefault()} + ${this.toSpecific()} + + static custom(url: string): ${this.typeExportName} { + return (url as ${this.typeExportName}) + } + } + ` + } + + toCompilationUnit(): CompilationUnit { + return new CompilationUnit(this.filename, this.imports, this.toString()) + } +} diff --git a/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts b/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts index 81ba1301..0fa7f05b 100644 --- a/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts +++ b/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts @@ -109,8 +109,6 @@ export class TypescriptFetchClientBuilder extends TypescriptClientBuilder { const basePathType = this.basePathType() return ` - ${this.serversClass()} - export interface ${clientName}Config extends AbstractFetchClientConfig { ${basePathType ? `basePath: ${basePathType}` : ""} } From f223ea7a9657c2907b0d0808e90ee891274b44df Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Sun, 1 Dec 2024 14:18:11 +0000 Subject: [PATCH 05/24] regenerate --- .../api.github.com.yaml/client.service.ts | 26 ++++++++++++++ .../client.service.ts | 32 +++++++++++++++++ .../client.service.ts | 26 ++++++++++++++ .../generated/okta.idp.yaml/client.service.ts | 36 +++++++++++++++++++ .../okta.oauth.yaml/client.service.ts | 36 +++++++++++++++++++ .../petstore-expanded.yaml/client.service.ts | 26 ++++++++++++++ .../generated/stripe.yaml/client.service.ts | 25 +++++++++++++ .../todo-lists.yaml/client.service.ts | 11 ++++++ .../generated/api.github.com.yaml/client.ts | 25 +++++++++++++ .../client.ts | 31 ++++++++++++++++ .../azure-resource-manager.tsp/client.ts | 26 ++++++++++++++ .../src/generated/okta.idp.yaml/client.ts | 35 ++++++++++++++++++ .../src/generated/okta.oauth.yaml/client.ts | 36 +++++++++++++++++++ .../petstore-expanded.yaml/client.ts | 25 +++++++++++++ .../src/generated/stripe.yaml/client.ts | 25 +++++++++++++ .../src/generated/todo-lists.yaml/client.ts | 10 ++++++ .../generated/api.github.com.yaml/client.ts | 23 ++++++++++-- .../client.ts | 29 +++++++++++++-- .../azure-resource-manager.tsp/client.ts | 24 +++++++++++-- .../src/generated/okta.idp.yaml/client.ts | 30 ++++++++++++++-- .../src/generated/okta.oauth.yaml/client.ts | 31 ++++++++++++++-- .../petstore-expanded.yaml/client.ts | 23 ++++++++++-- .../src/generated/stripe.yaml/client.ts | 23 ++++++++++-- .../src/generated/todo-lists.yaml/client.ts | 10 +++++- 24 files changed, 609 insertions(+), 15 deletions(-) diff --git a/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts index b91ab1cb..0d110fb8 100644 --- a/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts @@ -320,6 +320,32 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" +export type Server = string & { __server__: T } + +export type GitHubV3RestApiServiceServer = + Server<"GitHubV3RestApiServiceServer"> + +export class GitHubV3RestApiServiceServers { + static default(): GitHubV3RestApiServiceServer { + return "https://api.github.com" as GitHubV3RestApiServiceServer + } + + static specific(url: "https://api.github.com") { + switch (url) { + case "https://api.github.com": + return { + with(): GitHubV3RestApiServiceServer { + return "https://api.github.com" as GitHubV3RestApiServiceServer + }, + } + } + } + + static custom(url: string): GitHubV3RestApiServiceServer { + return url as GitHubV3RestApiServiceServer + } +} + export class GitHubV3RestApiServiceConfig { basePath: "https://api.github.com" | string = "" defaultHeaders: Record = {} diff --git a/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts b/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts index 7d454273..6df51886 100644 --- a/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts @@ -24,6 +24,38 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" +export type Server = string & { __server__: T } + +export type ContosoWidgetManagerServiceServer = + Server<"ContosoWidgetManagerServiceServer"> + +export class ContosoWidgetManagerServiceServers { + static default(endpoint = ""): ContosoWidgetManagerServiceServer { + return "{endpoint}/widget".replace( + "{endpoint}", + endpoint, + ) as ContosoWidgetManagerServiceServer + } + + static specific(url: "{endpoint}/widget") { + switch (url) { + case "{endpoint}/widget": + return { + with(endpoint = ""): ContosoWidgetManagerServiceServer { + return "{endpoint}/widget".replace( + "{endpoint}", + endpoint, + ) as ContosoWidgetManagerServiceServer + }, + } + } + } + + static custom(url: string): ContosoWidgetManagerServiceServer { + return url as ContosoWidgetManagerServiceServer + } +} + export class ContosoWidgetManagerServiceConfig { basePath: "{endpoint}/widget" | string = "" defaultHeaders: Record = {} diff --git a/integration-tests/typescript-angular/src/generated/azure-resource-manager.tsp/client.service.ts b/integration-tests/typescript-angular/src/generated/azure-resource-manager.tsp/client.service.ts index e42882b6..d9162411 100644 --- a/integration-tests/typescript-angular/src/generated/azure-resource-manager.tsp/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/azure-resource-manager.tsp/client.service.ts @@ -16,6 +16,32 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" +export type Server = string & { __server__: T } + +export type ContosoProviderHubClientServiceServer = + Server<"ContosoProviderHubClientServiceServer"> + +export class ContosoProviderHubClientServiceServers { + static default(): ContosoProviderHubClientServiceServer { + return "https://management.azure.com" as ContosoProviderHubClientServiceServer + } + + static specific(url: "https://management.azure.com") { + switch (url) { + case "https://management.azure.com": + return { + with(): ContosoProviderHubClientServiceServer { + return "https://management.azure.com" as ContosoProviderHubClientServiceServer + }, + } + } + } + + static custom(url: string): ContosoProviderHubClientServiceServer { + return url as ContosoProviderHubClientServiceServer + } +} + export class ContosoProviderHubClientServiceConfig { basePath: "https://management.azure.com" | string = "" defaultHeaders: Record = {} diff --git a/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts index 92552091..aeb71377 100644 --- a/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts @@ -24,6 +24,42 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" +export type Server = string & { __server__: T } + +export type MyAccountManagementServiceServer = + Server<"MyAccountManagementServiceServer"> + +export class MyAccountManagementServiceServers { + static default( + yourOktaDomain = "subdomain.okta.com", + ): MyAccountManagementServiceServer { + return "https://{yourOktaDomain}".replace( + "{yourOktaDomain}", + yourOktaDomain, + ) as MyAccountManagementServiceServer + } + + static specific(url: "https://{yourOktaDomain}") { + switch (url) { + case "https://{yourOktaDomain}": + return { + with( + yourOktaDomain = "subdomain.okta.com", + ): MyAccountManagementServiceServer { + return "https://{yourOktaDomain}".replace( + "{yourOktaDomain}", + yourOktaDomain, + ) as MyAccountManagementServiceServer + }, + } + } + } + + static custom(url: string): MyAccountManagementServiceServer { + return url as MyAccountManagementServiceServer + } +} + export class MyAccountManagementServiceConfig { basePath: "https://{yourOktaDomain}" | string = "" defaultHeaders: Record = {} diff --git a/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts index fccd67bb..5706e558 100644 --- a/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts @@ -38,6 +38,42 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" +export type Server = string & { __server__: T } + +export type OktaOpenIdConnectOAuth20ServiceServer = + Server<"OktaOpenIdConnectOAuth20ServiceServer"> + +export class OktaOpenIdConnectOAuth20ServiceServers { + static default( + yourOktaDomain = "subdomain.okta.com", + ): OktaOpenIdConnectOAuth20ServiceServer { + return "https://{yourOktaDomain}".replace( + "{yourOktaDomain}", + yourOktaDomain, + ) as OktaOpenIdConnectOAuth20ServiceServer + } + + static specific(url: "https://{yourOktaDomain}") { + switch (url) { + case "https://{yourOktaDomain}": + return { + with( + yourOktaDomain = "subdomain.okta.com", + ): OktaOpenIdConnectOAuth20ServiceServer { + return "https://{yourOktaDomain}".replace( + "{yourOktaDomain}", + yourOktaDomain, + ) as OktaOpenIdConnectOAuth20ServiceServer + }, + } + } + } + + static custom(url: string): OktaOpenIdConnectOAuth20ServiceServer { + return url as OktaOpenIdConnectOAuth20ServiceServer + } +} + export class OktaOpenIdConnectOAuth20ServiceConfig { basePath: "https://{yourOktaDomain}" | string = "" defaultHeaders: Record = {} diff --git a/integration-tests/typescript-angular/src/generated/petstore-expanded.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/petstore-expanded.yaml/client.service.ts index c9e2e93b..7cf28ca3 100644 --- a/integration-tests/typescript-angular/src/generated/petstore-expanded.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/petstore-expanded.yaml/client.service.ts @@ -7,6 +7,32 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" +export type Server = string & { __server__: T } + +export type SwaggerPetstoreServiceServer = + Server<"SwaggerPetstoreServiceServer"> + +export class SwaggerPetstoreServiceServers { + static default(): SwaggerPetstoreServiceServer { + return "https://petstore.swagger.io/v2" as SwaggerPetstoreServiceServer + } + + static specific(url: "https://petstore.swagger.io/v2") { + switch (url) { + case "https://petstore.swagger.io/v2": + return { + with(): SwaggerPetstoreServiceServer { + return "https://petstore.swagger.io/v2" as SwaggerPetstoreServiceServer + }, + } + } + } + + static custom(url: string): SwaggerPetstoreServiceServer { + return url as SwaggerPetstoreServiceServer + } +} + export class SwaggerPetstoreServiceConfig { basePath: "https://petstore.swagger.io/v2" | string = "" defaultHeaders: Record = {} diff --git a/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts index 10a8f278..ea9a417c 100644 --- a/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts @@ -166,6 +166,31 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" +export type Server = string & { __server__: T } + +export type StripeApiServiceServer = Server<"StripeApiServiceServer"> + +export class StripeApiServiceServers { + static default(): StripeApiServiceServer { + return "https://api.stripe.com/" as StripeApiServiceServer + } + + static specific(url: "https://api.stripe.com/") { + switch (url) { + case "https://api.stripe.com/": + return { + with(): StripeApiServiceServer { + return "https://api.stripe.com/" as StripeApiServiceServer + }, + } + } + } + + static custom(url: string): StripeApiServiceServer { + return url as StripeApiServiceServer + } +} + export class StripeApiServiceConfig { basePath: "https://api.stripe.com/" | string = "" defaultHeaders: Record = {} diff --git a/integration-tests/typescript-angular/src/generated/todo-lists.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/todo-lists.yaml/client.service.ts index f843c859..9cccfa71 100644 --- a/integration-tests/typescript-angular/src/generated/todo-lists.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/todo-lists.yaml/client.service.ts @@ -12,6 +12,17 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" +export type Server = string & { __server__: T } + +export type TodoListsExampleApiServiceServer = + Server<"TodoListsExampleApiServiceServer"> + +export class TodoListsExampleApiServiceServers { + static custom(url: string): TodoListsExampleApiServiceServer { + return url as TodoListsExampleApiServiceServer + } +} + export class TodoListsExampleApiServiceConfig { basePath: string = "" defaultHeaders: Record = {} diff --git a/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts b/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts index 0b63cac3..aa882ab8 100644 --- a/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts @@ -317,6 +317,31 @@ import { } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" +export type Server = string & { __server__: T } + +export type GitHubV3RestApiServer = Server<"GitHubV3RestApiServer"> + +export class GitHubV3RestApiServers { + static default(): GitHubV3RestApiServer { + return "https://api.github.com" as GitHubV3RestApiServer + } + + static specific(url: "https://api.github.com") { + switch (url) { + case "https://api.github.com": + return { + with(): GitHubV3RestApiServer { + return "https://api.github.com" as GitHubV3RestApiServer + }, + } + } + } + + static custom(url: string): GitHubV3RestApiServer { + return url as GitHubV3RestApiServer + } +} + export interface GitHubV3RestApiConfig extends AbstractAxiosConfig { basePath: "https://api.github.com" | string } diff --git a/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts b/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts index 967ad482..48c65500 100644 --- a/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts +++ b/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts @@ -25,6 +25,37 @@ import { } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" +export type Server = string & { __server__: T } + +export type ContosoWidgetManagerServer = Server<"ContosoWidgetManagerServer"> + +export class ContosoWidgetManagerServers { + static default(endpoint = ""): ContosoWidgetManagerServer { + return "{endpoint}/widget".replace( + "{endpoint}", + endpoint, + ) as ContosoWidgetManagerServer + } + + static specific(url: "{endpoint}/widget") { + switch (url) { + case "{endpoint}/widget": + return { + with(endpoint = ""): ContosoWidgetManagerServer { + return "{endpoint}/widget".replace( + "{endpoint}", + endpoint, + ) as ContosoWidgetManagerServer + }, + } + } + } + + static custom(url: string): ContosoWidgetManagerServer { + return url as ContosoWidgetManagerServer + } +} + export interface ContosoWidgetManagerConfig extends AbstractAxiosConfig { basePath: "{endpoint}/widget" | string } diff --git a/integration-tests/typescript-axios/src/generated/azure-resource-manager.tsp/client.ts b/integration-tests/typescript-axios/src/generated/azure-resource-manager.tsp/client.ts index 42d17dd8..3b39d023 100644 --- a/integration-tests/typescript-axios/src/generated/azure-resource-manager.tsp/client.ts +++ b/integration-tests/typescript-axios/src/generated/azure-resource-manager.tsp/client.ts @@ -17,6 +17,32 @@ import { } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" +export type Server = string & { __server__: T } + +export type ContosoProviderHubClientServer = + Server<"ContosoProviderHubClientServer"> + +export class ContosoProviderHubClientServers { + static default(): ContosoProviderHubClientServer { + return "https://management.azure.com" as ContosoProviderHubClientServer + } + + static specific(url: "https://management.azure.com") { + switch (url) { + case "https://management.azure.com": + return { + with(): ContosoProviderHubClientServer { + return "https://management.azure.com" as ContosoProviderHubClientServer + }, + } + } + } + + static custom(url: string): ContosoProviderHubClientServer { + return url as ContosoProviderHubClientServer + } +} + export interface ContosoProviderHubClientConfig extends AbstractAxiosConfig { basePath: "https://management.azure.com" | string } diff --git a/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts b/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts index d26a0523..5a8a3a9d 100644 --- a/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts @@ -25,6 +25,41 @@ import { } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" +export type Server = string & { __server__: T } + +export type MyAccountManagementServer = Server<"MyAccountManagementServer"> + +export class MyAccountManagementServers { + static default( + yourOktaDomain = "subdomain.okta.com", + ): MyAccountManagementServer { + return "https://{yourOktaDomain}".replace( + "{yourOktaDomain}", + yourOktaDomain, + ) as MyAccountManagementServer + } + + static specific(url: "https://{yourOktaDomain}") { + switch (url) { + case "https://{yourOktaDomain}": + return { + with( + yourOktaDomain = "subdomain.okta.com", + ): MyAccountManagementServer { + return "https://{yourOktaDomain}".replace( + "{yourOktaDomain}", + yourOktaDomain, + ) as MyAccountManagementServer + }, + } + } + } + + static custom(url: string): MyAccountManagementServer { + return url as MyAccountManagementServer + } +} + export interface MyAccountManagementConfig extends AbstractAxiosConfig { basePath: "https://{yourOktaDomain}" | string } diff --git a/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts b/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts index 0b22db51..63767f92 100644 --- a/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts @@ -38,6 +38,42 @@ import { } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" +export type Server = string & { __server__: T } + +export type OktaOpenIdConnectOAuth20Server = + Server<"OktaOpenIdConnectOAuth20Server"> + +export class OktaOpenIdConnectOAuth20Servers { + static default( + yourOktaDomain = "subdomain.okta.com", + ): OktaOpenIdConnectOAuth20Server { + return "https://{yourOktaDomain}".replace( + "{yourOktaDomain}", + yourOktaDomain, + ) as OktaOpenIdConnectOAuth20Server + } + + static specific(url: "https://{yourOktaDomain}") { + switch (url) { + case "https://{yourOktaDomain}": + return { + with( + yourOktaDomain = "subdomain.okta.com", + ): OktaOpenIdConnectOAuth20Server { + return "https://{yourOktaDomain}".replace( + "{yourOktaDomain}", + yourOktaDomain, + ) as OktaOpenIdConnectOAuth20Server + }, + } + } + } + + static custom(url: string): OktaOpenIdConnectOAuth20Server { + return url as OktaOpenIdConnectOAuth20Server + } +} + export interface OktaOpenIdConnectOAuth20Config extends AbstractAxiosConfig { basePath: "https://{yourOktaDomain}" | string } diff --git a/integration-tests/typescript-axios/src/generated/petstore-expanded.yaml/client.ts b/integration-tests/typescript-axios/src/generated/petstore-expanded.yaml/client.ts index d95f6e8d..18171b71 100644 --- a/integration-tests/typescript-axios/src/generated/petstore-expanded.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/petstore-expanded.yaml/client.ts @@ -9,6 +9,31 @@ import { } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" +export type Server = string & { __server__: T } + +export type SwaggerPetstoreServer = Server<"SwaggerPetstoreServer"> + +export class SwaggerPetstoreServers { + static default(): SwaggerPetstoreServer { + return "https://petstore.swagger.io/v2" as SwaggerPetstoreServer + } + + static specific(url: "https://petstore.swagger.io/v2") { + switch (url) { + case "https://petstore.swagger.io/v2": + return { + with(): SwaggerPetstoreServer { + return "https://petstore.swagger.io/v2" as SwaggerPetstoreServer + }, + } + } + } + + static custom(url: string): SwaggerPetstoreServer { + return url as SwaggerPetstoreServer + } +} + export interface SwaggerPetstoreConfig extends AbstractAxiosConfig { basePath: "https://petstore.swagger.io/v2" | string } diff --git a/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts b/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts index b92ce045..c89e4a67 100644 --- a/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts @@ -167,6 +167,31 @@ import { } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" +export type Server = string & { __server__: T } + +export type StripeApiServer = Server<"StripeApiServer"> + +export class StripeApiServers { + static default(): StripeApiServer { + return "https://api.stripe.com/" as StripeApiServer + } + + static specific(url: "https://api.stripe.com/") { + switch (url) { + case "https://api.stripe.com/": + return { + with(): StripeApiServer { + return "https://api.stripe.com/" as StripeApiServer + }, + } + } + } + + static custom(url: string): StripeApiServer { + return url as StripeApiServer + } +} + export interface StripeApiConfig extends AbstractAxiosConfig { basePath: "https://api.stripe.com/" | string } diff --git a/integration-tests/typescript-axios/src/generated/todo-lists.yaml/client.ts b/integration-tests/typescript-axios/src/generated/todo-lists.yaml/client.ts index f2332ee6..da8aa15b 100644 --- a/integration-tests/typescript-axios/src/generated/todo-lists.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/todo-lists.yaml/client.ts @@ -9,6 +9,16 @@ import { } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" +export type Server = string & { __server__: T } + +export type TodoListsExampleApiServer = Server<"TodoListsExampleApiServer"> + +export class TodoListsExampleApiServers { + static custom(url: string): TodoListsExampleApiServer { + return url as TodoListsExampleApiServer + } +} + export interface TodoListsExampleApiConfig extends AbstractAxiosConfig {} export class TodoListsExampleApi extends AbstractAxiosClient { diff --git a/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts index e9d0f733..9139109d 100644 --- a/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts @@ -323,9 +323,28 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" +export type Server = string & { __server__: T } + +export type GitHubV3RestApiServer = Server<"GitHubV3RestApiServer"> + export class GitHubV3RestApiServers { - "https://api.github.com"() { - return "https://api.github.com" + static default(): GitHubV3RestApiServer { + return "https://api.github.com" as GitHubV3RestApiServer + } + + static specific(url: "https://api.github.com") { + switch (url) { + case "https://api.github.com": + return { + with(): GitHubV3RestApiServer { + return "https://api.github.com" as GitHubV3RestApiServer + }, + } + } + } + + static custom(url: string): GitHubV3RestApiServer { + return url as GitHubV3RestApiServer } } diff --git a/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts b/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts index 7e508140..dbe62ecf 100644 --- a/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts +++ b/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts @@ -28,9 +28,34 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" +export type Server = string & { __server__: T } + +export type ContosoWidgetManagerServer = Server<"ContosoWidgetManagerServer"> + export class ContosoWidgetManagerServers { - "{endpoint}/widget"(endpoint: string = "") { - return "{endpoint}/widget".replace("{endpoint}", endpoint) + static default(endpoint = ""): ContosoWidgetManagerServer { + return "{endpoint}/widget".replace( + "{endpoint}", + endpoint, + ) as ContosoWidgetManagerServer + } + + static specific(url: "{endpoint}/widget") { + switch (url) { + case "{endpoint}/widget": + return { + with(endpoint = ""): ContosoWidgetManagerServer { + return "{endpoint}/widget".replace( + "{endpoint}", + endpoint, + ) as ContosoWidgetManagerServer + }, + } + } + } + + static custom(url: string): ContosoWidgetManagerServer { + return url as ContosoWidgetManagerServer } } diff --git a/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts b/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts index ba484879..42a50631 100644 --- a/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts +++ b/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts @@ -20,9 +20,29 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" +export type Server = string & { __server__: T } + +export type ContosoProviderHubClientServer = + Server<"ContosoProviderHubClientServer"> + export class ContosoProviderHubClientServers { - "https://management.azure.com"() { - return "https://management.azure.com" + static default(): ContosoProviderHubClientServer { + return "https://management.azure.com" as ContosoProviderHubClientServer + } + + static specific(url: "https://management.azure.com") { + switch (url) { + case "https://management.azure.com": + return { + with(): ContosoProviderHubClientServer { + return "https://management.azure.com" as ContosoProviderHubClientServer + }, + } + } + } + + static custom(url: string): ContosoProviderHubClientServer { + return url as ContosoProviderHubClientServer } } diff --git a/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts index 3f22b9a3..28e25e6e 100644 --- a/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts @@ -27,12 +27,38 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" +export type Server = string & { __server__: T } + +export type MyAccountManagementServer = Server<"MyAccountManagementServer"> + export class MyAccountManagementServers { - "https://{yourOktaDomain}"(yourOktaDomain: string = "subdomain.okta.com") { + static default( + yourOktaDomain = "subdomain.okta.com", + ): MyAccountManagementServer { return "https://{yourOktaDomain}".replace( "{yourOktaDomain}", yourOktaDomain, - ) + ) as MyAccountManagementServer + } + + static specific(url: "https://{yourOktaDomain}") { + switch (url) { + case "https://{yourOktaDomain}": + return { + with( + yourOktaDomain = "subdomain.okta.com", + ): MyAccountManagementServer { + return "https://{yourOktaDomain}".replace( + "{yourOktaDomain}", + yourOktaDomain, + ) as MyAccountManagementServer + }, + } + } + } + + static custom(url: string): MyAccountManagementServer { + return url as MyAccountManagementServer } } diff --git a/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts index 128db483..53b99577 100644 --- a/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts @@ -41,12 +41,39 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" +export type Server = string & { __server__: T } + +export type OktaOpenIdConnectOAuth20Server = + Server<"OktaOpenIdConnectOAuth20Server"> + export class OktaOpenIdConnectOAuth20Servers { - "https://{yourOktaDomain}"(yourOktaDomain: string = "subdomain.okta.com") { + static default( + yourOktaDomain = "subdomain.okta.com", + ): OktaOpenIdConnectOAuth20Server { return "https://{yourOktaDomain}".replace( "{yourOktaDomain}", yourOktaDomain, - ) + ) as OktaOpenIdConnectOAuth20Server + } + + static specific(url: "https://{yourOktaDomain}") { + switch (url) { + case "https://{yourOktaDomain}": + return { + with( + yourOktaDomain = "subdomain.okta.com", + ): OktaOpenIdConnectOAuth20Server { + return "https://{yourOktaDomain}".replace( + "{yourOktaDomain}", + yourOktaDomain, + ) as OktaOpenIdConnectOAuth20Server + }, + } + } + } + + static custom(url: string): OktaOpenIdConnectOAuth20Server { + return url as OktaOpenIdConnectOAuth20Server } } diff --git a/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts index ce0a7f45..d83eefce 100644 --- a/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts @@ -11,9 +11,28 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" +export type Server = string & { __server__: T } + +export type SwaggerPetstoreServer = Server<"SwaggerPetstoreServer"> + export class SwaggerPetstoreServers { - "https://petstore.swagger.io/v2"() { - return "https://petstore.swagger.io/v2" + static default(): SwaggerPetstoreServer { + return "https://petstore.swagger.io/v2" as SwaggerPetstoreServer + } + + static specific(url: "https://petstore.swagger.io/v2") { + switch (url) { + case "https://petstore.swagger.io/v2": + return { + with(): SwaggerPetstoreServer { + return "https://petstore.swagger.io/v2" as SwaggerPetstoreServer + }, + } + } + } + + static custom(url: string): SwaggerPetstoreServer { + return url as SwaggerPetstoreServer } } diff --git a/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts index 74f94919..5431296a 100644 --- a/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts @@ -170,9 +170,28 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" +export type Server = string & { __server__: T } + +export type StripeApiServer = Server<"StripeApiServer"> + export class StripeApiServers { - "https://api.stripe.com/"() { - return "https://api.stripe.com/" + static default(): StripeApiServer { + return "https://api.stripe.com/" as StripeApiServer + } + + static specific(url: "https://api.stripe.com/") { + switch (url) { + case "https://api.stripe.com/": + return { + with(): StripeApiServer { + return "https://api.stripe.com/" as StripeApiServer + }, + } + } + } + + static custom(url: string): StripeApiServer { + return url as StripeApiServer } } diff --git a/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/client.ts index 26abcf14..c28dc7f0 100644 --- a/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/client.ts @@ -18,7 +18,15 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" -export class TodoListsExampleApiServers {} +export type Server = string & { __server__: T } + +export type TodoListsExampleApiServer = Server<"TodoListsExampleApiServer"> + +export class TodoListsExampleApiServers { + static custom(url: string): TodoListsExampleApiServer { + return url as TodoListsExampleApiServer + } +} export interface TodoListsExampleApiConfig extends AbstractFetchClientConfig {} From 4e5d83e70c73d895e24f1e3aee719636f0715869 Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Sun, 1 Dec 2024 15:27:19 +0000 Subject: [PATCH 06/24] feat: use branded types --- e2e/src/generated/client/fetch/client.ts | 2 -- .../api.github.com.yaml/client.service.ts | 7 +++-- .../client.service.ts | 7 +++-- .../client.service.ts | 7 +++-- .../generated/okta.idp.yaml/client.service.ts | 7 +++-- .../okta.oauth.yaml/client.service.ts | 7 +++-- .../petstore-expanded.yaml/client.service.ts | 7 +++-- .../generated/stripe.yaml/client.service.ts | 6 ++-- .../todo-lists.yaml/client.service.ts | 13 ++------- .../src/audit-github-repositories.ts | 7 +++-- .../generated/api.github.com.yaml/client.ts | 5 ++-- .../client.ts | 5 ++-- .../azure-resource-manager.tsp/client.ts | 5 ++-- .../src/generated/okta.idp.yaml/client.ts | 5 ++-- .../src/generated/okta.oauth.yaml/client.ts | 5 ++-- .../petstore-expanded.yaml/client.ts | 5 ++-- .../src/generated/stripe.yaml/client.ts | 5 ++-- .../src/generated/todo-lists.yaml/client.ts | 10 ------- .../src/uniform-github-repositories.ts | 7 +++-- .../generated/api.github.com.yaml/client.ts | 5 ++-- .../client.ts | 5 ++-- .../azure-resource-manager.tsp/client.ts | 5 ++-- .../src/generated/okta.idp.yaml/client.ts | 5 ++-- .../src/generated/okta.oauth.yaml/client.ts | 5 ++-- .../petstore-expanded.yaml/client.ts | 5 ++-- .../src/generated/stripe.yaml/client.ts | 5 ++-- .../src/generated/todo-lists.yaml/client.ts | 10 ------- .../src/uniform-github-repositories.ts | 7 +++-- .../src/typescript/common/client-builder.ts | 28 ++++++++++--------- .../common/client-servers-builder.ts | 12 ++++++-- .../angular-service-builder.ts | 4 ++- .../typescript-axios-client-builder.ts | 2 +- .../typescript-fetch-client-builder.ts | 1 + packages/typescript-axios-runtime/src/main.ts | 2 ++ packages/typescript-fetch-runtime/src/main.ts | 2 ++ 35 files changed, 105 insertions(+), 120 deletions(-) diff --git a/e2e/src/generated/client/fetch/client.ts b/e2e/src/generated/client/fetch/client.ts index 25705f9a..191cec0c 100644 --- a/e2e/src/generated/client/fetch/client.ts +++ b/e2e/src/generated/client/fetch/client.ts @@ -14,8 +14,6 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" -export class E2ETestClientServers {} - export interface E2ETestClientConfig extends AbstractFetchClientConfig {} export class E2ETestClient extends AbstractFetchClient { diff --git a/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts index 0d110fb8..4176fad1 100644 --- a/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts @@ -320,8 +320,6 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" -export type Server = string & { __server__: T } - export type GitHubV3RestApiServiceServer = Server<"GitHubV3RestApiServiceServer"> @@ -347,7 +345,8 @@ export class GitHubV3RestApiServiceServers { } export class GitHubV3RestApiServiceConfig { - basePath: "https://api.github.com" | string = "" + basePath: GitHubV3RestApiServiceServer = + GitHubV3RestApiServiceServers.default() defaultHeaders: Record = {} } @@ -389,6 +388,8 @@ export type QueryParams = { | QueryParams[] } +export type Server = string & { __server__: T } + @Injectable({ providedIn: "root", }) diff --git a/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts b/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts index 6df51886..3ec83291 100644 --- a/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts @@ -24,8 +24,6 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" -export type Server = string & { __server__: T } - export type ContosoWidgetManagerServiceServer = Server<"ContosoWidgetManagerServiceServer"> @@ -57,7 +55,8 @@ export class ContosoWidgetManagerServiceServers { } export class ContosoWidgetManagerServiceConfig { - basePath: "{endpoint}/widget" | string = "" + basePath: ContosoWidgetManagerServiceServer = + ContosoWidgetManagerServiceServers.default() defaultHeaders: Record = {} } @@ -99,6 +98,8 @@ export type QueryParams = { | QueryParams[] } +export type Server = string & { __server__: T } + @Injectable({ providedIn: "root", }) diff --git a/integration-tests/typescript-angular/src/generated/azure-resource-manager.tsp/client.service.ts b/integration-tests/typescript-angular/src/generated/azure-resource-manager.tsp/client.service.ts index d9162411..48b86c26 100644 --- a/integration-tests/typescript-angular/src/generated/azure-resource-manager.tsp/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/azure-resource-manager.tsp/client.service.ts @@ -16,8 +16,6 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" -export type Server = string & { __server__: T } - export type ContosoProviderHubClientServiceServer = Server<"ContosoProviderHubClientServiceServer"> @@ -43,7 +41,8 @@ export class ContosoProviderHubClientServiceServers { } export class ContosoProviderHubClientServiceConfig { - basePath: "https://management.azure.com" | string = "" + basePath: ContosoProviderHubClientServiceServer = + ContosoProviderHubClientServiceServers.default() defaultHeaders: Record = {} } @@ -85,6 +84,8 @@ export type QueryParams = { | QueryParams[] } +export type Server = string & { __server__: T } + @Injectable({ providedIn: "root", }) diff --git a/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts index aeb71377..5847f5c8 100644 --- a/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts @@ -24,8 +24,6 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" -export type Server = string & { __server__: T } - export type MyAccountManagementServiceServer = Server<"MyAccountManagementServiceServer"> @@ -61,7 +59,8 @@ export class MyAccountManagementServiceServers { } export class MyAccountManagementServiceConfig { - basePath: "https://{yourOktaDomain}" | string = "" + basePath: MyAccountManagementServiceServer = + MyAccountManagementServiceServers.default() defaultHeaders: Record = {} } @@ -103,6 +102,8 @@ export type QueryParams = { | QueryParams[] } +export type Server = string & { __server__: T } + @Injectable({ providedIn: "root", }) diff --git a/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts index 5706e558..bdd2a292 100644 --- a/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts @@ -38,8 +38,6 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" -export type Server = string & { __server__: T } - export type OktaOpenIdConnectOAuth20ServiceServer = Server<"OktaOpenIdConnectOAuth20ServiceServer"> @@ -75,7 +73,8 @@ export class OktaOpenIdConnectOAuth20ServiceServers { } export class OktaOpenIdConnectOAuth20ServiceConfig { - basePath: "https://{yourOktaDomain}" | string = "" + basePath: OktaOpenIdConnectOAuth20ServiceServer = + OktaOpenIdConnectOAuth20ServiceServers.default() defaultHeaders: Record = {} } @@ -117,6 +116,8 @@ export type QueryParams = { | QueryParams[] } +export type Server = string & { __server__: T } + @Injectable({ providedIn: "root", }) diff --git a/integration-tests/typescript-angular/src/generated/petstore-expanded.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/petstore-expanded.yaml/client.service.ts index 7cf28ca3..bbf347a9 100644 --- a/integration-tests/typescript-angular/src/generated/petstore-expanded.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/petstore-expanded.yaml/client.service.ts @@ -7,8 +7,6 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" -export type Server = string & { __server__: T } - export type SwaggerPetstoreServiceServer = Server<"SwaggerPetstoreServiceServer"> @@ -34,7 +32,8 @@ export class SwaggerPetstoreServiceServers { } export class SwaggerPetstoreServiceConfig { - basePath: "https://petstore.swagger.io/v2" | string = "" + basePath: SwaggerPetstoreServiceServer = + SwaggerPetstoreServiceServers.default() defaultHeaders: Record = {} } @@ -76,6 +75,8 @@ export type QueryParams = { | QueryParams[] } +export type Server = string & { __server__: T } + @Injectable({ providedIn: "root", }) diff --git a/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts index ea9a417c..a16cb995 100644 --- a/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts @@ -166,8 +166,6 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" -export type Server = string & { __server__: T } - export type StripeApiServiceServer = Server<"StripeApiServiceServer"> export class StripeApiServiceServers { @@ -192,7 +190,7 @@ export class StripeApiServiceServers { } export class StripeApiServiceConfig { - basePath: "https://api.stripe.com/" | string = "" + basePath: StripeApiServiceServer = StripeApiServiceServers.default() defaultHeaders: Record = {} } @@ -234,6 +232,8 @@ export type QueryParams = { | QueryParams[] } +export type Server = string & { __server__: T } + @Injectable({ providedIn: "root", }) diff --git a/integration-tests/typescript-angular/src/generated/todo-lists.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/todo-lists.yaml/client.service.ts index 9cccfa71..6681ae93 100644 --- a/integration-tests/typescript-angular/src/generated/todo-lists.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/todo-lists.yaml/client.service.ts @@ -12,17 +12,6 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" -export type Server = string & { __server__: T } - -export type TodoListsExampleApiServiceServer = - Server<"TodoListsExampleApiServiceServer"> - -export class TodoListsExampleApiServiceServers { - static custom(url: string): TodoListsExampleApiServiceServer { - return url as TodoListsExampleApiServiceServer - } -} - export class TodoListsExampleApiServiceConfig { basePath: string = "" defaultHeaders: Record = {} @@ -66,6 +55,8 @@ export type QueryParams = { | QueryParams[] } +export type Server = string & { __server__: T } + @Injectable({ providedIn: "root", }) diff --git a/integration-tests/typescript-axios/src/audit-github-repositories.ts b/integration-tests/typescript-axios/src/audit-github-repositories.ts index 5ec5ddbc..4c7bfd60 100644 --- a/integration-tests/typescript-axios/src/audit-github-repositories.ts +++ b/integration-tests/typescript-axios/src/audit-github-repositories.ts @@ -3,14 +3,17 @@ import dotenv from "dotenv" dotenv.config() import axios, {type AxiosResponse, isAxiosError} from "axios" -import {ApiClient} from "./generated/api.github.com.yaml/client" +import { + ApiClient, + GitHubV3RestApiServers, +} from "./generated/api.github.com.yaml/client" import type {t_repository} from "./generated/api.github.com.yaml/models" const client = new ApiClient({ axios: axios.create({ headers: {Authorization: `Bearer ${process.env.GITHUB_TOKEN}`}, }), - basePath: "https://api.github.com", + basePath: GitHubV3RestApiServers.default(), defaultTimeout: 5_000, defaultHeaders: {}, }) diff --git a/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts b/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts index aa882ab8..ce639809 100644 --- a/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts @@ -314,11 +314,10 @@ import { import { AbstractAxiosClient, AbstractAxiosConfig, + Server, } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" -export type Server = string & { __server__: T } - export type GitHubV3RestApiServer = Server<"GitHubV3RestApiServer"> export class GitHubV3RestApiServers { @@ -343,7 +342,7 @@ export class GitHubV3RestApiServers { } export interface GitHubV3RestApiConfig extends AbstractAxiosConfig { - basePath: "https://api.github.com" | string + basePath: GitHubV3RestApiServer } export class GitHubV3RestApi extends AbstractAxiosClient { diff --git a/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts b/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts index 48c65500..b054c116 100644 --- a/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts +++ b/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts @@ -22,11 +22,10 @@ import { import { AbstractAxiosClient, AbstractAxiosConfig, + Server, } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" -export type Server = string & { __server__: T } - export type ContosoWidgetManagerServer = Server<"ContosoWidgetManagerServer"> export class ContosoWidgetManagerServers { @@ -57,7 +56,7 @@ export class ContosoWidgetManagerServers { } export interface ContosoWidgetManagerConfig extends AbstractAxiosConfig { - basePath: "{endpoint}/widget" | string + basePath: ContosoWidgetManagerServer } export class ContosoWidgetManager extends AbstractAxiosClient { diff --git a/integration-tests/typescript-axios/src/generated/azure-resource-manager.tsp/client.ts b/integration-tests/typescript-axios/src/generated/azure-resource-manager.tsp/client.ts index 3b39d023..b2d445fd 100644 --- a/integration-tests/typescript-axios/src/generated/azure-resource-manager.tsp/client.ts +++ b/integration-tests/typescript-axios/src/generated/azure-resource-manager.tsp/client.ts @@ -14,11 +14,10 @@ import { import { AbstractAxiosClient, AbstractAxiosConfig, + Server, } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" -export type Server = string & { __server__: T } - export type ContosoProviderHubClientServer = Server<"ContosoProviderHubClientServer"> @@ -44,7 +43,7 @@ export class ContosoProviderHubClientServers { } export interface ContosoProviderHubClientConfig extends AbstractAxiosConfig { - basePath: "https://management.azure.com" | string + basePath: ContosoProviderHubClientServer } export class ContosoProviderHubClient extends AbstractAxiosClient { diff --git a/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts b/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts index 5a8a3a9d..31edb6d6 100644 --- a/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts @@ -22,11 +22,10 @@ import { import { AbstractAxiosClient, AbstractAxiosConfig, + Server, } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" -export type Server = string & { __server__: T } - export type MyAccountManagementServer = Server<"MyAccountManagementServer"> export class MyAccountManagementServers { @@ -61,7 +60,7 @@ export class MyAccountManagementServers { } export interface MyAccountManagementConfig extends AbstractAxiosConfig { - basePath: "https://{yourOktaDomain}" | string + basePath: MyAccountManagementServer } export class MyAccountManagement extends AbstractAxiosClient { diff --git a/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts b/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts index 63767f92..a9bacf9d 100644 --- a/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts @@ -35,11 +35,10 @@ import { import { AbstractAxiosClient, AbstractAxiosConfig, + Server, } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" -export type Server = string & { __server__: T } - export type OktaOpenIdConnectOAuth20Server = Server<"OktaOpenIdConnectOAuth20Server"> @@ -75,7 +74,7 @@ export class OktaOpenIdConnectOAuth20Servers { } export interface OktaOpenIdConnectOAuth20Config extends AbstractAxiosConfig { - basePath: "https://{yourOktaDomain}" | string + basePath: OktaOpenIdConnectOAuth20Server } export class OktaOpenIdConnectOAuth20 extends AbstractAxiosClient { diff --git a/integration-tests/typescript-axios/src/generated/petstore-expanded.yaml/client.ts b/integration-tests/typescript-axios/src/generated/petstore-expanded.yaml/client.ts index 18171b71..82a43277 100644 --- a/integration-tests/typescript-axios/src/generated/petstore-expanded.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/petstore-expanded.yaml/client.ts @@ -6,11 +6,10 @@ import { t_NewPet, t_Pet } from "./models" import { AbstractAxiosClient, AbstractAxiosConfig, + Server, } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" -export type Server = string & { __server__: T } - export type SwaggerPetstoreServer = Server<"SwaggerPetstoreServer"> export class SwaggerPetstoreServers { @@ -35,7 +34,7 @@ export class SwaggerPetstoreServers { } export interface SwaggerPetstoreConfig extends AbstractAxiosConfig { - basePath: "https://petstore.swagger.io/v2" | string + basePath: SwaggerPetstoreServer } export class SwaggerPetstore extends AbstractAxiosClient { diff --git a/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts b/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts index c89e4a67..ef030810 100644 --- a/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts @@ -164,11 +164,10 @@ import { import { AbstractAxiosClient, AbstractAxiosConfig, + Server, } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" -export type Server = string & { __server__: T } - export type StripeApiServer = Server<"StripeApiServer"> export class StripeApiServers { @@ -193,7 +192,7 @@ export class StripeApiServers { } export interface StripeApiConfig extends AbstractAxiosConfig { - basePath: "https://api.stripe.com/" | string + basePath: StripeApiServer } export class StripeApi extends AbstractAxiosClient { diff --git a/integration-tests/typescript-axios/src/generated/todo-lists.yaml/client.ts b/integration-tests/typescript-axios/src/generated/todo-lists.yaml/client.ts index da8aa15b..f2332ee6 100644 --- a/integration-tests/typescript-axios/src/generated/todo-lists.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/todo-lists.yaml/client.ts @@ -9,16 +9,6 @@ import { } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" -export type Server = string & { __server__: T } - -export type TodoListsExampleApiServer = Server<"TodoListsExampleApiServer"> - -export class TodoListsExampleApiServers { - static custom(url: string): TodoListsExampleApiServer { - return url as TodoListsExampleApiServer - } -} - export interface TodoListsExampleApiConfig extends AbstractAxiosConfig {} export class TodoListsExampleApi extends AbstractAxiosClient { diff --git a/integration-tests/typescript-axios/src/uniform-github-repositories.ts b/integration-tests/typescript-axios/src/uniform-github-repositories.ts index aba416f3..11295ca7 100644 --- a/integration-tests/typescript-axios/src/uniform-github-repositories.ts +++ b/integration-tests/typescript-axios/src/uniform-github-repositories.ts @@ -3,14 +3,17 @@ import dotenv from "dotenv" dotenv.config() import axios from "axios" -import {ApiClient} from "./generated/api.github.com.yaml/client" +import { + ApiClient, + GitHubV3RestApiServers, +} from "./generated/api.github.com.yaml/client" import type {t_repository} from "./generated/api.github.com.yaml/models" const client = new ApiClient({ axios: axios.create({ headers: {Authorization: `Bearer ${process.env.GITHUB_TOKEN}`}, }), - basePath: "https://api.github.com", + basePath: GitHubV3RestApiServers.default(), defaultTimeout: 5_000, defaultHeaders: {}, }) diff --git a/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts index 9139109d..f01abb1a 100644 --- a/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts @@ -320,11 +320,10 @@ import { AbstractFetchClient, AbstractFetchClientConfig, Res, + Server, TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" -export type Server = string & { __server__: T } - export type GitHubV3RestApiServer = Server<"GitHubV3RestApiServer"> export class GitHubV3RestApiServers { @@ -349,7 +348,7 @@ export class GitHubV3RestApiServers { } export interface GitHubV3RestApiConfig extends AbstractFetchClientConfig { - basePath: "https://api.github.com" | string + basePath: GitHubV3RestApiServer } export class GitHubV3RestApi extends AbstractFetchClient { diff --git a/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts b/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts index dbe62ecf..a924b675 100644 --- a/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts +++ b/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts @@ -24,12 +24,11 @@ import { AbstractFetchClient, AbstractFetchClientConfig, Res, + Server, StatusCode, TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" -export type Server = string & { __server__: T } - export type ContosoWidgetManagerServer = Server<"ContosoWidgetManagerServer"> export class ContosoWidgetManagerServers { @@ -60,7 +59,7 @@ export class ContosoWidgetManagerServers { } export interface ContosoWidgetManagerConfig extends AbstractFetchClientConfig { - basePath: "{endpoint}/widget" | string + basePath: ContosoWidgetManagerServer } export class ContosoWidgetManager extends AbstractFetchClient { diff --git a/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts b/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts index 42a50631..e249899a 100644 --- a/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts +++ b/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts @@ -16,12 +16,11 @@ import { AbstractFetchClient, AbstractFetchClientConfig, Res, + Server, StatusCode, TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" -export type Server = string & { __server__: T } - export type ContosoProviderHubClientServer = Server<"ContosoProviderHubClientServer"> @@ -48,7 +47,7 @@ export class ContosoProviderHubClientServers { export interface ContosoProviderHubClientConfig extends AbstractFetchClientConfig { - basePath: "https://management.azure.com" | string + basePath: ContosoProviderHubClientServer } export class ContosoProviderHubClient extends AbstractFetchClient { diff --git a/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts index 28e25e6e..72d0d1e1 100644 --- a/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts @@ -24,11 +24,10 @@ import { AbstractFetchClient, AbstractFetchClientConfig, Res, + Server, TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" -export type Server = string & { __server__: T } - export type MyAccountManagementServer = Server<"MyAccountManagementServer"> export class MyAccountManagementServers { @@ -63,7 +62,7 @@ export class MyAccountManagementServers { } export interface MyAccountManagementConfig extends AbstractFetchClientConfig { - basePath: "https://{yourOktaDomain}" | string + basePath: MyAccountManagementServer } export class MyAccountManagement extends AbstractFetchClient { diff --git a/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts index 53b99577..410e8e18 100644 --- a/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts @@ -38,11 +38,10 @@ import { AbstractFetchClient, AbstractFetchClientConfig, Res, + Server, TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" -export type Server = string & { __server__: T } - export type OktaOpenIdConnectOAuth20Server = Server<"OktaOpenIdConnectOAuth20Server"> @@ -79,7 +78,7 @@ export class OktaOpenIdConnectOAuth20Servers { export interface OktaOpenIdConnectOAuth20Config extends AbstractFetchClientConfig { - basePath: "https://{yourOktaDomain}" | string + basePath: OktaOpenIdConnectOAuth20Server } export class OktaOpenIdConnectOAuth20 extends AbstractFetchClient { diff --git a/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts index d83eefce..d2ed0bfb 100644 --- a/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts @@ -7,12 +7,11 @@ import { AbstractFetchClient, AbstractFetchClientConfig, Res, + Server, StatusCode, TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" -export type Server = string & { __server__: T } - export type SwaggerPetstoreServer = Server<"SwaggerPetstoreServer"> export class SwaggerPetstoreServers { @@ -37,7 +36,7 @@ export class SwaggerPetstoreServers { } export interface SwaggerPetstoreConfig extends AbstractFetchClientConfig { - basePath: "https://petstore.swagger.io/v2" | string + basePath: SwaggerPetstoreServer } export class SwaggerPetstore extends AbstractFetchClient { diff --git a/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts index 5431296a..a18f5876 100644 --- a/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts @@ -166,12 +166,11 @@ import { AbstractFetchClient, AbstractFetchClientConfig, Res, + Server, StatusCode, TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" -export type Server = string & { __server__: T } - export type StripeApiServer = Server<"StripeApiServer"> export class StripeApiServers { @@ -196,7 +195,7 @@ export class StripeApiServers { } export interface StripeApiConfig extends AbstractFetchClientConfig { - basePath: "https://api.stripe.com/" | string + basePath: StripeApiServer } export class StripeApi extends AbstractFetchClient { diff --git a/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/client.ts index c28dc7f0..e074e141 100644 --- a/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/client.ts @@ -18,16 +18,6 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" -export type Server = string & { __server__: T } - -export type TodoListsExampleApiServer = Server<"TodoListsExampleApiServer"> - -export class TodoListsExampleApiServers { - static custom(url: string): TodoListsExampleApiServer { - return url as TodoListsExampleApiServer - } -} - export interface TodoListsExampleApiConfig extends AbstractFetchClientConfig {} export class TodoListsExampleApi extends AbstractFetchClient { diff --git a/integration-tests/typescript-fetch/src/uniform-github-repositories.ts b/integration-tests/typescript-fetch/src/uniform-github-repositories.ts index 2b9bef8b..a80aea74 100644 --- a/integration-tests/typescript-fetch/src/uniform-github-repositories.ts +++ b/integration-tests/typescript-fetch/src/uniform-github-repositories.ts @@ -2,13 +2,16 @@ import dotenv from "dotenv" dotenv.config() -import {ApiClient} from "./generated/api.github.com.yaml/client" +import { + ApiClient, + GitHubV3RestApiServers, +} from "./generated/api.github.com.yaml/client" import type {t_repository} from "./generated/api.github.com.yaml/models" const {writeHeapSnapshot} = require("node:v8") const client = new ApiClient({ - basePath: "https://api.github.com", + basePath: GitHubV3RestApiServers.default(), defaultHeaders: {Authorization: `Bearer ${process.env.GITHUB_TOKEN}`}, defaultTimeout: 5_000, }) diff --git a/packages/openapi-code-generator/src/typescript/common/client-builder.ts b/packages/openapi-code-generator/src/typescript/common/client-builder.ts index 3dfece4e..426b4565 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-builder.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-builder.ts @@ -11,6 +11,8 @@ import {quotedStringLiteral, union} from "./type-utils" export abstract class TypescriptClientBuilder implements ICompilable { private readonly operations: string[] = [] + protected readonly clientServersBuilder: ClientServersBuilder + constructor( public readonly filename: string, public readonly exportName: string, @@ -24,15 +26,21 @@ export abstract class TypescriptClientBuilder implements ICompilable { } = {enableRuntimeResponseValidation: false, enableTypedBasePaths: true}, ) { this.buildImports(imports) + + this.clientServersBuilder = new ClientServersBuilder( + this.filename, + this.exportName, + this.input.servers(), + this.imports, + ) } basePathType() { - const serverUrls = this.input - .servers() - .map((it) => quotedStringLiteral(it.url)) - - if (this.config.enableTypedBasePaths && serverUrls.length > 0) { - return union(...serverUrls, "string") + if ( + this.config.enableTypedBasePaths && + this.clientServersBuilder.hasServers + ) { + return this.clientServersBuilder.typeExportName } return "" @@ -58,16 +66,10 @@ export abstract class TypescriptClientBuilder implements ICompilable { ): string toString(): string { - const servers = new ClientServersBuilder( - this.filename, - this.exportName, - this.input.servers(), - this.imports, - ) const client = this.buildClient(this.exportName, this.operations) return ` - ${servers} + ${this.clientServersBuilder} ${client} ` } diff --git a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts index 6e0466ee..7461188d 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts @@ -45,7 +45,7 @@ export class ClientServersBuilder implements ICompilable { } private toSpecific() { - if (!this.servers.length) { + if (!this.hasServers) { return "" } @@ -68,6 +68,10 @@ export class ClientServersBuilder implements ICompilable { }` } + get hasServers() { + return this.servers.length > 0 + } + get classExportName(): string { return `${this.name}Servers` } @@ -76,9 +80,11 @@ export class ClientServersBuilder implements ICompilable { } toString() { - return ` - export type Server = string & {__server__: T} + if (!this.hasServers) { + return "" + } + return ` export type ${this.typeExportName} = Server<"${this.typeExportName}"> export class ${this.classExportName} { diff --git a/packages/openapi-code-generator/src/typescript/typescript-angular/angular-service-builder.ts b/packages/openapi-code-generator/src/typescript/typescript-angular/angular-service-builder.ts index be188d1b..dcc43371 100644 --- a/packages/openapi-code-generator/src/typescript/typescript-angular/angular-service-builder.ts +++ b/packages/openapi-code-generator/src/typescript/typescript-angular/angular-service-builder.ts @@ -72,7 +72,7 @@ return this.httpClient.request( return ` export class ${clientName}Config { - basePath: ${basePathType ? basePathType : "string"} = '' + basePath: ${basePathType ? basePathType : "string"} = ${this.clientServersBuilder.hasServers ? `${this.clientServersBuilder.classExportName}.default()` : "''"} defaultHeaders: Record = {} } @@ -114,6 +114,8 @@ export type QueryParams = { | QueryParams[] } +export type Server = string & {__server__: T} + @Injectable({ providedIn: 'root' }) diff --git a/packages/openapi-code-generator/src/typescript/typescript-axios/typescript-axios-client-builder.ts b/packages/openapi-code-generator/src/typescript/typescript-axios/typescript-axios-client-builder.ts index 004257f8..ac44fbe5 100644 --- a/packages/openapi-code-generator/src/typescript/typescript-axios/typescript-axios-client-builder.ts +++ b/packages/openapi-code-generator/src/typescript/typescript-axios/typescript-axios-client-builder.ts @@ -8,7 +8,7 @@ export class TypescriptAxiosClientBuilder extends TypescriptClientBuilder { protected buildImports(imports: ImportBuilder): void { imports .from("@nahkies/typescript-axios-runtime/main") - .add("AbstractAxiosConfig", "AbstractAxiosClient") + .add("AbstractAxiosConfig", "AbstractAxiosClient", "Server") imports.from("axios").all("axios") diff --git a/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts b/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts index 0fa7f05b..3a2a243e 100644 --- a/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts +++ b/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts @@ -13,6 +13,7 @@ export class TypescriptFetchClientBuilder extends TypescriptClientBuilder { .add( "AbstractFetchClientConfig", "AbstractFetchClient", + "Server", "Res", "TypedFetchResponse", "StatusCode2xx", diff --git a/packages/typescript-axios-runtime/src/main.ts b/packages/typescript-axios-runtime/src/main.ts index 574e0284..1a6163cd 100644 --- a/packages/typescript-axios-runtime/src/main.ts +++ b/packages/typescript-axios-runtime/src/main.ts @@ -55,6 +55,8 @@ export type HeaderParams = | [string, string | number | undefined | null][] | Headers +export type Server = string & {__server__: T} + export interface AbstractAxiosConfig { axios?: AxiosInstance basePath: string diff --git a/packages/typescript-fetch-runtime/src/main.ts b/packages/typescript-fetch-runtime/src/main.ts index 1638b05c..03abddd7 100644 --- a/packages/typescript-fetch-runtime/src/main.ts +++ b/packages/typescript-fetch-runtime/src/main.ts @@ -35,6 +35,8 @@ export type TypedFetchResponse> = Promise< Omit & R > +export type Server = string & {__server__: T} + export interface AbstractFetchClientConfig { basePath: string defaultHeaders: Record From 4ee2da783beecb5f11b44e63b500a5755f184b65 Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Sun, 1 Dec 2024 17:41:12 +0000 Subject: [PATCH 07/24] feat: overridden servers --- .../api.github.com.yaml/client.service.ts | 11 +++++++ .../generated/stripe.yaml/client.service.ts | 21 +++++++++++++ .../generated/api.github.com.yaml/client.ts | 11 +++++++ .../src/generated/stripe.yaml/client.ts | 21 +++++++++++++ .../generated/api.github.com.yaml/client.ts | 11 +++++++ .../src/generated/stripe.yaml/client.ts | 21 +++++++++++++ .../openapi-code-generator/src/core/input.ts | 7 +---- .../src/typescript/common/client-builder.ts | 1 + .../common/client-servers-builder.ts | 31 ++++++++++++++----- 9 files changed, 121 insertions(+), 14 deletions(-) diff --git a/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts index 4176fad1..d667d5b6 100644 --- a/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts @@ -342,6 +342,17 @@ export class GitHubV3RestApiServiceServers { static custom(url: string): GitHubV3RestApiServiceServer { return url as GitHubV3RestApiServiceServer } + + static reposUploadReleaseAsset(url: "https://uploads.github.com") { + switch (url) { + case "https://uploads.github.com": + return { + with(): Server<"reposUploadReleaseAsset"> { + return "https://uploads.github.com" as Server<"reposUploadReleaseAsset"> + }, + } + } + } } export class GitHubV3RestApiServiceConfig { diff --git a/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts index a16cb995..a4201a8f 100644 --- a/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts @@ -187,6 +187,27 @@ export class StripeApiServiceServers { static custom(url: string): StripeApiServiceServer { return url as StripeApiServiceServer } + + static postFiles(url: "https://files.stripe.com/") { + switch (url) { + case "https://files.stripe.com/": + return { + with(): Server<"postFiles"> { + return "https://files.stripe.com/" as Server<"postFiles"> + }, + } + } + } + static getQuotesQuotePdf(url: "https://files.stripe.com/") { + switch (url) { + case "https://files.stripe.com/": + return { + with(): Server<"getQuotesQuotePdf"> { + return "https://files.stripe.com/" as Server<"getQuotesQuotePdf"> + }, + } + } + } } export class StripeApiServiceConfig { diff --git a/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts b/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts index ce639809..75f7417f 100644 --- a/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts @@ -339,6 +339,17 @@ export class GitHubV3RestApiServers { static custom(url: string): GitHubV3RestApiServer { return url as GitHubV3RestApiServer } + + static reposUploadReleaseAsset(url: "https://uploads.github.com") { + switch (url) { + case "https://uploads.github.com": + return { + with(): Server<"reposUploadReleaseAsset"> { + return "https://uploads.github.com" as Server<"reposUploadReleaseAsset"> + }, + } + } + } } export interface GitHubV3RestApiConfig extends AbstractAxiosConfig { diff --git a/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts b/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts index ef030810..b7c7b75b 100644 --- a/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts @@ -189,6 +189,27 @@ export class StripeApiServers { static custom(url: string): StripeApiServer { return url as StripeApiServer } + + static postFiles(url: "https://files.stripe.com/") { + switch (url) { + case "https://files.stripe.com/": + return { + with(): Server<"postFiles"> { + return "https://files.stripe.com/" as Server<"postFiles"> + }, + } + } + } + static getQuotesQuotePdf(url: "https://files.stripe.com/") { + switch (url) { + case "https://files.stripe.com/": + return { + with(): Server<"getQuotesQuotePdf"> { + return "https://files.stripe.com/" as Server<"getQuotesQuotePdf"> + }, + } + } + } } export interface StripeApiConfig extends AbstractAxiosConfig { diff --git a/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts index f01abb1a..b420cc6b 100644 --- a/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts @@ -345,6 +345,17 @@ export class GitHubV3RestApiServers { static custom(url: string): GitHubV3RestApiServer { return url as GitHubV3RestApiServer } + + static reposUploadReleaseAsset(url: "https://uploads.github.com") { + switch (url) { + case "https://uploads.github.com": + return { + with(): Server<"reposUploadReleaseAsset"> { + return "https://uploads.github.com" as Server<"reposUploadReleaseAsset"> + }, + } + } + } } export interface GitHubV3RestApiConfig extends AbstractFetchClientConfig { diff --git a/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts index a18f5876..73a4dbff 100644 --- a/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts @@ -192,6 +192,27 @@ export class StripeApiServers { static custom(url: string): StripeApiServer { return url as StripeApiServer } + + static postFiles(url: "https://files.stripe.com/") { + switch (url) { + case "https://files.stripe.com/": + return { + with(): Server<"postFiles"> { + return "https://files.stripe.com/" as Server<"postFiles"> + }, + } + } + } + static getQuotesQuotePdf(url: "https://files.stripe.com/") { + switch (url) { + case "https://files.stripe.com/": + return { + with(): Server<"getQuotesQuotePdf"> { + return "https://files.stripe.com/" as Server<"getQuotesQuotePdf"> + }, + } + } + } } export interface StripeApiConfig extends AbstractFetchClientConfig { diff --git a/packages/openapi-code-generator/src/core/input.ts b/packages/openapi-code-generator/src/core/input.ts index 81d788d4..a5e11d57 100644 --- a/packages/openapi-code-generator/src/core/input.ts +++ b/packages/openapi-code-generator/src/core/input.ts @@ -135,12 +135,7 @@ export class Input { route, method, servers: this.normalizeServers( - coalesce( - definition.servers, - paths.servers, - this.loader.entryPoint.servers, - [], - ), + coalesce(definition.servers, paths.servers, []), ), parameters: params.concat( this.normalizeParameters(definition.parameters), diff --git a/packages/openapi-code-generator/src/typescript/common/client-builder.ts b/packages/openapi-code-generator/src/typescript/common/client-builder.ts index 426b4565..ab8feaf1 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-builder.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-builder.ts @@ -54,6 +54,7 @@ export abstract class TypescriptClientBuilder implements ICompilable { ) const result = this.buildOperation(builder) this.operations.push(result) + this.clientServersBuilder.addOperation(operation) } protected abstract buildImports(imports: ImportBuilder): void diff --git a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts index 7461188d..70d9fc4c 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts @@ -1,4 +1,5 @@ import type { + IROperation, IRServer, IRServerVariable, } from "../../core/openapi-types-normalized" @@ -7,6 +8,8 @@ import type {ImportBuilder} from "./import-builder" import {quotedStringLiteral, union} from "./type-utils" export class ClientServersBuilder implements ICompilable { + readonly operations: IROperation[] = [] + constructor( readonly filename: string, readonly name: string, @@ -14,6 +17,12 @@ export class ClientServersBuilder implements ICompilable { readonly imports: ImportBuilder, ) {} + addOperation(operation: IROperation) { + if (operation.servers.length) { + this.operations.push(operation) + } + } + private toParams(variables: { [k: string]: IRServerVariable }) { @@ -44,21 +53,21 @@ export class ClientServersBuilder implements ICompilable { ` } - private toSpecific() { + private toSpecific(name: string, servers: IRServer[], typeName: string) { if (!this.hasServers) { return "" } - const urls = this.servers.map((it) => quotedStringLiteral(it.url)) - return `static specific(url: ${union(urls)}){ + const urls = servers.map((it) => quotedStringLiteral(it.url)) + return `static ${name}(url: ${union(urls)}){ switch(url) { - ${this.servers + ${servers .map( (server) => ` case ${quotedStringLiteral(server.url)}: return { - with(${this.toParams(server.variables)}): ${this.typeExportName} { - return (${this.toReplacer(server)} as ${this.typeExportName}) + with(${this.toParams(server.variables)}): ${typeName} { + return (${this.toReplacer(server)} as ${typeName}) } } `, @@ -72,6 +81,10 @@ export class ClientServersBuilder implements ICompilable { return this.servers.length > 0 } + get hasOperationServers() { + return this.operations.length > 0 + } + get classExportName(): string { return `${this.name}Servers` } @@ -80,7 +93,7 @@ export class ClientServersBuilder implements ICompilable { } toString() { - if (!this.hasServers) { + if (!this.hasServers && !this.hasOperationServers) { return "" } @@ -89,11 +102,13 @@ export class ClientServersBuilder implements ICompilable { export class ${this.classExportName} { ${this.toDefault()} - ${this.toSpecific()} + ${this.toSpecific("specific", this.servers, this.typeExportName)} static custom(url: string): ${this.typeExportName} { return (url as ${this.typeExportName}) } + + ${this.operations.map((it) => this.toSpecific(it.operationId, it.servers, `Server<"${it.operationId}">`)).join("\n")} } ` } From 50ebd6e3d7e5468121274ec2b7b38eaff52eca7e Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Mon, 2 Dec 2024 08:44:08 +0000 Subject: [PATCH 08/24] feat: adding new param --- .../api.github.com.yaml/client.service.ts | 27 +++++--------- .../client.service.ts | 19 +++++----- .../client.service.ts | 21 +++++------ .../generated/okta.idp.yaml/client.service.ts | 19 +++++----- .../okta.oauth.yaml/client.service.ts | 19 +++++----- .../petstore-expanded.yaml/client.service.ts | 21 +++++------ .../generated/stripe.yaml/client.service.ts | 31 +++++----------- .../generated/api.github.com.yaml/client.ts | 24 ++++--------- .../client.ts | 18 +++++----- .../azure-resource-manager.tsp/client.ts | 21 +++++------ .../src/generated/okta.idp.yaml/client.ts | 16 ++++----- .../src/generated/okta.oauth.yaml/client.ts | 19 +++++----- .../petstore-expanded.yaml/client.ts | 18 ++++------ .../src/generated/stripe.yaml/client.ts | 30 +++++----------- .../generated/api.github.com.yaml/client.ts | 27 ++++++-------- .../client.ts | 18 +++++----- .../azure-resource-manager.tsp/client.ts | 21 +++++------ .../src/generated/okta.idp.yaml/client.ts | 16 ++++----- .../src/generated/okta.oauth.yaml/client.ts | 19 +++++----- .../petstore-expanded.yaml/client.ts | 18 ++++------ .../src/generated/stripe.yaml/client.ts | 32 ++++++----------- .../src/typescript/common/client-builder.ts | 5 ++- .../common/client-operation-builder.ts | 4 +++ .../common/client-servers-builder.ts | 35 ++++++++++++------- .../typescript-fetch-client-builder.ts | 12 ++++++- 25 files changed, 216 insertions(+), 294 deletions(-) diff --git a/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts index d667d5b6..43b881a7 100644 --- a/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts @@ -320,43 +320,34 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" -export type GitHubV3RestApiServiceServer = - Server<"GitHubV3RestApiServiceServer"> - export class GitHubV3RestApiServiceServers { - static default(): GitHubV3RestApiServiceServer { - return "https://api.github.com" as GitHubV3RestApiServiceServer + static default(): Server<"GitHubV3RestApiService"> { + return "https://api.github.com" as Server<"GitHubV3RestApiService"> } static specific(url: "https://api.github.com") { switch (url) { case "https://api.github.com": - return { - with(): GitHubV3RestApiServiceServer { - return "https://api.github.com" as GitHubV3RestApiServiceServer - }, - } + return "https://api.github.com" as Server<"GitHubV3RestApiService"> } } - static custom(url: string): GitHubV3RestApiServiceServer { - return url as GitHubV3RestApiServiceServer + static custom(url: string): Server<"GitHubV3RestApiServiceCustom"> { + return url as Server<"GitHubV3RestApiServiceCustom"> } static reposUploadReleaseAsset(url: "https://uploads.github.com") { switch (url) { case "https://uploads.github.com": - return { - with(): Server<"reposUploadReleaseAsset"> { - return "https://uploads.github.com" as Server<"reposUploadReleaseAsset"> - }, - } + return "https://uploads.github.com" as Server<"reposUploadReleaseAsset"> } } } export class GitHubV3RestApiServiceConfig { - basePath: GitHubV3RestApiServiceServer = + basePath: + | Server<"GitHubV3RestApiService"> + | Server<"GitHubV3RestApiServiceCustom"> = GitHubV3RestApiServiceServers.default() defaultHeaders: Record = {} } diff --git a/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts b/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts index 3ec83291..e449b1ff 100644 --- a/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts @@ -24,38 +24,37 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" -export type ContosoWidgetManagerServiceServer = - Server<"ContosoWidgetManagerServiceServer"> - export class ContosoWidgetManagerServiceServers { - static default(endpoint = ""): ContosoWidgetManagerServiceServer { + static default(endpoint = ""): Server<"ContosoWidgetManagerService"> { return "{endpoint}/widget".replace( "{endpoint}", endpoint, - ) as ContosoWidgetManagerServiceServer + ) as Server<"ContosoWidgetManagerService"> } static specific(url: "{endpoint}/widget") { switch (url) { case "{endpoint}/widget": return { - with(endpoint = ""): ContosoWidgetManagerServiceServer { + with(endpoint = ""): Server<"ContosoWidgetManagerService"> { return "{endpoint}/widget".replace( "{endpoint}", endpoint, - ) as ContosoWidgetManagerServiceServer + ) as Server<"ContosoWidgetManagerService"> }, } } } - static custom(url: string): ContosoWidgetManagerServiceServer { - return url as ContosoWidgetManagerServiceServer + static custom(url: string): Server<"ContosoWidgetManagerServiceCustom"> { + return url as Server<"ContosoWidgetManagerServiceCustom"> } } export class ContosoWidgetManagerServiceConfig { - basePath: ContosoWidgetManagerServiceServer = + basePath: + | Server<"ContosoWidgetManagerService"> + | Server<"ContosoWidgetManagerServiceCustom"> = ContosoWidgetManagerServiceServers.default() defaultHeaders: Record = {} } diff --git a/integration-tests/typescript-angular/src/generated/azure-resource-manager.tsp/client.service.ts b/integration-tests/typescript-angular/src/generated/azure-resource-manager.tsp/client.service.ts index 48b86c26..a55954a1 100644 --- a/integration-tests/typescript-angular/src/generated/azure-resource-manager.tsp/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/azure-resource-manager.tsp/client.service.ts @@ -16,32 +16,27 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" -export type ContosoProviderHubClientServiceServer = - Server<"ContosoProviderHubClientServiceServer"> - export class ContosoProviderHubClientServiceServers { - static default(): ContosoProviderHubClientServiceServer { - return "https://management.azure.com" as ContosoProviderHubClientServiceServer + static default(): Server<"ContosoProviderHubClientService"> { + return "https://management.azure.com" as Server<"ContosoProviderHubClientService"> } static specific(url: "https://management.azure.com") { switch (url) { case "https://management.azure.com": - return { - with(): ContosoProviderHubClientServiceServer { - return "https://management.azure.com" as ContosoProviderHubClientServiceServer - }, - } + return "https://management.azure.com" as Server<"ContosoProviderHubClientService"> } } - static custom(url: string): ContosoProviderHubClientServiceServer { - return url as ContosoProviderHubClientServiceServer + static custom(url: string): Server<"ContosoProviderHubClientServiceCustom"> { + return url as Server<"ContosoProviderHubClientServiceCustom"> } } export class ContosoProviderHubClientServiceConfig { - basePath: ContosoProviderHubClientServiceServer = + basePath: + | Server<"ContosoProviderHubClientService"> + | Server<"ContosoProviderHubClientServiceCustom"> = ContosoProviderHubClientServiceServers.default() defaultHeaders: Record = {} } diff --git a/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts index 5847f5c8..794c48f3 100644 --- a/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts @@ -24,17 +24,14 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" -export type MyAccountManagementServiceServer = - Server<"MyAccountManagementServiceServer"> - export class MyAccountManagementServiceServers { static default( yourOktaDomain = "subdomain.okta.com", - ): MyAccountManagementServiceServer { + ): Server<"MyAccountManagementService"> { return "https://{yourOktaDomain}".replace( "{yourOktaDomain}", yourOktaDomain, - ) as MyAccountManagementServiceServer + ) as Server<"MyAccountManagementService"> } static specific(url: "https://{yourOktaDomain}") { @@ -43,23 +40,25 @@ export class MyAccountManagementServiceServers { return { with( yourOktaDomain = "subdomain.okta.com", - ): MyAccountManagementServiceServer { + ): Server<"MyAccountManagementService"> { return "https://{yourOktaDomain}".replace( "{yourOktaDomain}", yourOktaDomain, - ) as MyAccountManagementServiceServer + ) as Server<"MyAccountManagementService"> }, } } } - static custom(url: string): MyAccountManagementServiceServer { - return url as MyAccountManagementServiceServer + static custom(url: string): Server<"MyAccountManagementServiceCustom"> { + return url as Server<"MyAccountManagementServiceCustom"> } } export class MyAccountManagementServiceConfig { - basePath: MyAccountManagementServiceServer = + basePath: + | Server<"MyAccountManagementService"> + | Server<"MyAccountManagementServiceCustom"> = MyAccountManagementServiceServers.default() defaultHeaders: Record = {} } diff --git a/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts index bdd2a292..26f6417a 100644 --- a/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts @@ -38,17 +38,14 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" -export type OktaOpenIdConnectOAuth20ServiceServer = - Server<"OktaOpenIdConnectOAuth20ServiceServer"> - export class OktaOpenIdConnectOAuth20ServiceServers { static default( yourOktaDomain = "subdomain.okta.com", - ): OktaOpenIdConnectOAuth20ServiceServer { + ): Server<"OktaOpenIdConnectOAuth20Service"> { return "https://{yourOktaDomain}".replace( "{yourOktaDomain}", yourOktaDomain, - ) as OktaOpenIdConnectOAuth20ServiceServer + ) as Server<"OktaOpenIdConnectOAuth20Service"> } static specific(url: "https://{yourOktaDomain}") { @@ -57,23 +54,25 @@ export class OktaOpenIdConnectOAuth20ServiceServers { return { with( yourOktaDomain = "subdomain.okta.com", - ): OktaOpenIdConnectOAuth20ServiceServer { + ): Server<"OktaOpenIdConnectOAuth20Service"> { return "https://{yourOktaDomain}".replace( "{yourOktaDomain}", yourOktaDomain, - ) as OktaOpenIdConnectOAuth20ServiceServer + ) as Server<"OktaOpenIdConnectOAuth20Service"> }, } } } - static custom(url: string): OktaOpenIdConnectOAuth20ServiceServer { - return url as OktaOpenIdConnectOAuth20ServiceServer + static custom(url: string): Server<"OktaOpenIdConnectOAuth20ServiceCustom"> { + return url as Server<"OktaOpenIdConnectOAuth20ServiceCustom"> } } export class OktaOpenIdConnectOAuth20ServiceConfig { - basePath: OktaOpenIdConnectOAuth20ServiceServer = + basePath: + | Server<"OktaOpenIdConnectOAuth20Service"> + | Server<"OktaOpenIdConnectOAuth20ServiceCustom"> = OktaOpenIdConnectOAuth20ServiceServers.default() defaultHeaders: Record = {} } diff --git a/integration-tests/typescript-angular/src/generated/petstore-expanded.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/petstore-expanded.yaml/client.service.ts index bbf347a9..6cd37e16 100644 --- a/integration-tests/typescript-angular/src/generated/petstore-expanded.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/petstore-expanded.yaml/client.service.ts @@ -7,32 +7,27 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" -export type SwaggerPetstoreServiceServer = - Server<"SwaggerPetstoreServiceServer"> - export class SwaggerPetstoreServiceServers { - static default(): SwaggerPetstoreServiceServer { - return "https://petstore.swagger.io/v2" as SwaggerPetstoreServiceServer + static default(): Server<"SwaggerPetstoreService"> { + return "https://petstore.swagger.io/v2" as Server<"SwaggerPetstoreService"> } static specific(url: "https://petstore.swagger.io/v2") { switch (url) { case "https://petstore.swagger.io/v2": - return { - with(): SwaggerPetstoreServiceServer { - return "https://petstore.swagger.io/v2" as SwaggerPetstoreServiceServer - }, - } + return "https://petstore.swagger.io/v2" as Server<"SwaggerPetstoreService"> } } - static custom(url: string): SwaggerPetstoreServiceServer { - return url as SwaggerPetstoreServiceServer + static custom(url: string): Server<"SwaggerPetstoreServiceCustom"> { + return url as Server<"SwaggerPetstoreServiceCustom"> } } export class SwaggerPetstoreServiceConfig { - basePath: SwaggerPetstoreServiceServer = + basePath: + | Server<"SwaggerPetstoreService"> + | Server<"SwaggerPetstoreServiceCustom"> = SwaggerPetstoreServiceServers.default() defaultHeaders: Record = {} } diff --git a/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts index a4201a8f..6181d13c 100644 --- a/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts @@ -166,52 +166,39 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" -export type StripeApiServiceServer = Server<"StripeApiServiceServer"> - export class StripeApiServiceServers { - static default(): StripeApiServiceServer { - return "https://api.stripe.com/" as StripeApiServiceServer + static default(): Server<"StripeApiService"> { + return "https://api.stripe.com/" as Server<"StripeApiService"> } static specific(url: "https://api.stripe.com/") { switch (url) { case "https://api.stripe.com/": - return { - with(): StripeApiServiceServer { - return "https://api.stripe.com/" as StripeApiServiceServer - }, - } + return "https://api.stripe.com/" as Server<"StripeApiService"> } } - static custom(url: string): StripeApiServiceServer { - return url as StripeApiServiceServer + static custom(url: string): Server<"StripeApiServiceCustom"> { + return url as Server<"StripeApiServiceCustom"> } static postFiles(url: "https://files.stripe.com/") { switch (url) { case "https://files.stripe.com/": - return { - with(): Server<"postFiles"> { - return "https://files.stripe.com/" as Server<"postFiles"> - }, - } + return "https://files.stripe.com/" as Server<"postFiles"> } } static getQuotesQuotePdf(url: "https://files.stripe.com/") { switch (url) { case "https://files.stripe.com/": - return { - with(): Server<"getQuotesQuotePdf"> { - return "https://files.stripe.com/" as Server<"getQuotesQuotePdf"> - }, - } + return "https://files.stripe.com/" as Server<"getQuotesQuotePdf"> } } } export class StripeApiServiceConfig { - basePath: StripeApiServiceServer = StripeApiServiceServers.default() + basePath: Server<"StripeApiService"> | Server<"StripeApiServiceCustom"> = + StripeApiServiceServers.default() defaultHeaders: Record = {} } diff --git a/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts b/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts index 75f7417f..86fa36f8 100644 --- a/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts @@ -318,42 +318,32 @@ import { } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" -export type GitHubV3RestApiServer = Server<"GitHubV3RestApiServer"> - export class GitHubV3RestApiServers { - static default(): GitHubV3RestApiServer { - return "https://api.github.com" as GitHubV3RestApiServer + static default(): Server<"GitHubV3RestApi"> { + return "https://api.github.com" as Server<"GitHubV3RestApi"> } static specific(url: "https://api.github.com") { switch (url) { case "https://api.github.com": - return { - with(): GitHubV3RestApiServer { - return "https://api.github.com" as GitHubV3RestApiServer - }, - } + return "https://api.github.com" as Server<"GitHubV3RestApi"> } } - static custom(url: string): GitHubV3RestApiServer { - return url as GitHubV3RestApiServer + static custom(url: string): Server<"GitHubV3RestApiCustom"> { + return url as Server<"GitHubV3RestApiCustom"> } static reposUploadReleaseAsset(url: "https://uploads.github.com") { switch (url) { case "https://uploads.github.com": - return { - with(): Server<"reposUploadReleaseAsset"> { - return "https://uploads.github.com" as Server<"reposUploadReleaseAsset"> - }, - } + return "https://uploads.github.com" as Server<"reposUploadReleaseAsset"> } } } export interface GitHubV3RestApiConfig extends AbstractAxiosConfig { - basePath: GitHubV3RestApiServer + basePath: Server<"GitHubV3RestApi"> | Server<"GitHubV3RestApiCustom"> } export class GitHubV3RestApi extends AbstractAxiosClient { diff --git a/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts b/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts index b054c116..d9cd0bc9 100644 --- a/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts +++ b/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts @@ -26,37 +26,37 @@ import { } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" -export type ContosoWidgetManagerServer = Server<"ContosoWidgetManagerServer"> - export class ContosoWidgetManagerServers { - static default(endpoint = ""): ContosoWidgetManagerServer { + static default(endpoint = ""): Server<"ContosoWidgetManager"> { return "{endpoint}/widget".replace( "{endpoint}", endpoint, - ) as ContosoWidgetManagerServer + ) as Server<"ContosoWidgetManager"> } static specific(url: "{endpoint}/widget") { switch (url) { case "{endpoint}/widget": return { - with(endpoint = ""): ContosoWidgetManagerServer { + with(endpoint = ""): Server<"ContosoWidgetManager"> { return "{endpoint}/widget".replace( "{endpoint}", endpoint, - ) as ContosoWidgetManagerServer + ) as Server<"ContosoWidgetManager"> }, } } } - static custom(url: string): ContosoWidgetManagerServer { - return url as ContosoWidgetManagerServer + static custom(url: string): Server<"ContosoWidgetManagerCustom"> { + return url as Server<"ContosoWidgetManagerCustom"> } } export interface ContosoWidgetManagerConfig extends AbstractAxiosConfig { - basePath: ContosoWidgetManagerServer + basePath: + | Server<"ContosoWidgetManager"> + | Server<"ContosoWidgetManagerCustom"> } export class ContosoWidgetManager extends AbstractAxiosClient { diff --git a/integration-tests/typescript-axios/src/generated/azure-resource-manager.tsp/client.ts b/integration-tests/typescript-axios/src/generated/azure-resource-manager.tsp/client.ts index b2d445fd..c5566fcd 100644 --- a/integration-tests/typescript-axios/src/generated/azure-resource-manager.tsp/client.ts +++ b/integration-tests/typescript-axios/src/generated/azure-resource-manager.tsp/client.ts @@ -18,32 +18,27 @@ import { } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" -export type ContosoProviderHubClientServer = - Server<"ContosoProviderHubClientServer"> - export class ContosoProviderHubClientServers { - static default(): ContosoProviderHubClientServer { - return "https://management.azure.com" as ContosoProviderHubClientServer + static default(): Server<"ContosoProviderHubClient"> { + return "https://management.azure.com" as Server<"ContosoProviderHubClient"> } static specific(url: "https://management.azure.com") { switch (url) { case "https://management.azure.com": - return { - with(): ContosoProviderHubClientServer { - return "https://management.azure.com" as ContosoProviderHubClientServer - }, - } + return "https://management.azure.com" as Server<"ContosoProviderHubClient"> } } - static custom(url: string): ContosoProviderHubClientServer { - return url as ContosoProviderHubClientServer + static custom(url: string): Server<"ContosoProviderHubClientCustom"> { + return url as Server<"ContosoProviderHubClientCustom"> } } export interface ContosoProviderHubClientConfig extends AbstractAxiosConfig { - basePath: ContosoProviderHubClientServer + basePath: + | Server<"ContosoProviderHubClient"> + | Server<"ContosoProviderHubClientCustom"> } export class ContosoProviderHubClient extends AbstractAxiosClient { diff --git a/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts b/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts index 31edb6d6..000e89d0 100644 --- a/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts @@ -26,16 +26,14 @@ import { } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" -export type MyAccountManagementServer = Server<"MyAccountManagementServer"> - export class MyAccountManagementServers { static default( yourOktaDomain = "subdomain.okta.com", - ): MyAccountManagementServer { + ): Server<"MyAccountManagement"> { return "https://{yourOktaDomain}".replace( "{yourOktaDomain}", yourOktaDomain, - ) as MyAccountManagementServer + ) as Server<"MyAccountManagement"> } static specific(url: "https://{yourOktaDomain}") { @@ -44,23 +42,23 @@ export class MyAccountManagementServers { return { with( yourOktaDomain = "subdomain.okta.com", - ): MyAccountManagementServer { + ): Server<"MyAccountManagement"> { return "https://{yourOktaDomain}".replace( "{yourOktaDomain}", yourOktaDomain, - ) as MyAccountManagementServer + ) as Server<"MyAccountManagement"> }, } } } - static custom(url: string): MyAccountManagementServer { - return url as MyAccountManagementServer + static custom(url: string): Server<"MyAccountManagementCustom"> { + return url as Server<"MyAccountManagementCustom"> } } export interface MyAccountManagementConfig extends AbstractAxiosConfig { - basePath: MyAccountManagementServer + basePath: Server<"MyAccountManagement"> | Server<"MyAccountManagementCustom"> } export class MyAccountManagement extends AbstractAxiosClient { diff --git a/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts b/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts index a9bacf9d..c96c437f 100644 --- a/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts @@ -39,17 +39,14 @@ import { } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" -export type OktaOpenIdConnectOAuth20Server = - Server<"OktaOpenIdConnectOAuth20Server"> - export class OktaOpenIdConnectOAuth20Servers { static default( yourOktaDomain = "subdomain.okta.com", - ): OktaOpenIdConnectOAuth20Server { + ): Server<"OktaOpenIdConnectOAuth20"> { return "https://{yourOktaDomain}".replace( "{yourOktaDomain}", yourOktaDomain, - ) as OktaOpenIdConnectOAuth20Server + ) as Server<"OktaOpenIdConnectOAuth20"> } static specific(url: "https://{yourOktaDomain}") { @@ -58,23 +55,25 @@ export class OktaOpenIdConnectOAuth20Servers { return { with( yourOktaDomain = "subdomain.okta.com", - ): OktaOpenIdConnectOAuth20Server { + ): Server<"OktaOpenIdConnectOAuth20"> { return "https://{yourOktaDomain}".replace( "{yourOktaDomain}", yourOktaDomain, - ) as OktaOpenIdConnectOAuth20Server + ) as Server<"OktaOpenIdConnectOAuth20"> }, } } } - static custom(url: string): OktaOpenIdConnectOAuth20Server { - return url as OktaOpenIdConnectOAuth20Server + static custom(url: string): Server<"OktaOpenIdConnectOAuth20Custom"> { + return url as Server<"OktaOpenIdConnectOAuth20Custom"> } } export interface OktaOpenIdConnectOAuth20Config extends AbstractAxiosConfig { - basePath: OktaOpenIdConnectOAuth20Server + basePath: + | Server<"OktaOpenIdConnectOAuth20"> + | Server<"OktaOpenIdConnectOAuth20Custom"> } export class OktaOpenIdConnectOAuth20 extends AbstractAxiosClient { diff --git a/integration-tests/typescript-axios/src/generated/petstore-expanded.yaml/client.ts b/integration-tests/typescript-axios/src/generated/petstore-expanded.yaml/client.ts index 82a43277..f3f40bce 100644 --- a/integration-tests/typescript-axios/src/generated/petstore-expanded.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/petstore-expanded.yaml/client.ts @@ -10,31 +10,25 @@ import { } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" -export type SwaggerPetstoreServer = Server<"SwaggerPetstoreServer"> - export class SwaggerPetstoreServers { - static default(): SwaggerPetstoreServer { - return "https://petstore.swagger.io/v2" as SwaggerPetstoreServer + static default(): Server<"SwaggerPetstore"> { + return "https://petstore.swagger.io/v2" as Server<"SwaggerPetstore"> } static specific(url: "https://petstore.swagger.io/v2") { switch (url) { case "https://petstore.swagger.io/v2": - return { - with(): SwaggerPetstoreServer { - return "https://petstore.swagger.io/v2" as SwaggerPetstoreServer - }, - } + return "https://petstore.swagger.io/v2" as Server<"SwaggerPetstore"> } } - static custom(url: string): SwaggerPetstoreServer { - return url as SwaggerPetstoreServer + static custom(url: string): Server<"SwaggerPetstoreCustom"> { + return url as Server<"SwaggerPetstoreCustom"> } } export interface SwaggerPetstoreConfig extends AbstractAxiosConfig { - basePath: SwaggerPetstoreServer + basePath: Server<"SwaggerPetstore"> | Server<"SwaggerPetstoreCustom"> } export class SwaggerPetstore extends AbstractAxiosClient { diff --git a/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts b/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts index b7c7b75b..b6ad6dfb 100644 --- a/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts @@ -168,52 +168,38 @@ import { } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" -export type StripeApiServer = Server<"StripeApiServer"> - export class StripeApiServers { - static default(): StripeApiServer { - return "https://api.stripe.com/" as StripeApiServer + static default(): Server<"StripeApi"> { + return "https://api.stripe.com/" as Server<"StripeApi"> } static specific(url: "https://api.stripe.com/") { switch (url) { case "https://api.stripe.com/": - return { - with(): StripeApiServer { - return "https://api.stripe.com/" as StripeApiServer - }, - } + return "https://api.stripe.com/" as Server<"StripeApi"> } } - static custom(url: string): StripeApiServer { - return url as StripeApiServer + static custom(url: string): Server<"StripeApiCustom"> { + return url as Server<"StripeApiCustom"> } static postFiles(url: "https://files.stripe.com/") { switch (url) { case "https://files.stripe.com/": - return { - with(): Server<"postFiles"> { - return "https://files.stripe.com/" as Server<"postFiles"> - }, - } + return "https://files.stripe.com/" as Server<"postFiles"> } } static getQuotesQuotePdf(url: "https://files.stripe.com/") { switch (url) { case "https://files.stripe.com/": - return { - with(): Server<"getQuotesQuotePdf"> { - return "https://files.stripe.com/" as Server<"getQuotesQuotePdf"> - }, - } + return "https://files.stripe.com/" as Server<"getQuotesQuotePdf"> } } } export interface StripeApiConfig extends AbstractAxiosConfig { - basePath: StripeApiServer + basePath: Server<"StripeApi"> | Server<"StripeApiCustom"> } export class StripeApi extends AbstractAxiosClient { diff --git a/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts index b420cc6b..4fcab70d 100644 --- a/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts @@ -324,42 +324,32 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" -export type GitHubV3RestApiServer = Server<"GitHubV3RestApiServer"> - export class GitHubV3RestApiServers { - static default(): GitHubV3RestApiServer { - return "https://api.github.com" as GitHubV3RestApiServer + static default(): Server<"GitHubV3RestApi"> { + return "https://api.github.com" as Server<"GitHubV3RestApi"> } static specific(url: "https://api.github.com") { switch (url) { case "https://api.github.com": - return { - with(): GitHubV3RestApiServer { - return "https://api.github.com" as GitHubV3RestApiServer - }, - } + return "https://api.github.com" as Server<"GitHubV3RestApi"> } } - static custom(url: string): GitHubV3RestApiServer { - return url as GitHubV3RestApiServer + static custom(url: string): Server<"GitHubV3RestApiCustom"> { + return url as Server<"GitHubV3RestApiCustom"> } static reposUploadReleaseAsset(url: "https://uploads.github.com") { switch (url) { case "https://uploads.github.com": - return { - with(): Server<"reposUploadReleaseAsset"> { - return "https://uploads.github.com" as Server<"reposUploadReleaseAsset"> - }, - } + return "https://uploads.github.com" as Server<"reposUploadReleaseAsset"> } } } export interface GitHubV3RestApiConfig extends AbstractFetchClientConfig { - basePath: GitHubV3RestApiServer + basePath: Server<"GitHubV3RestApi"> | Server<"GitHubV3RestApiCustom"> } export class GitHubV3RestApi extends AbstractFetchClient { @@ -20358,6 +20348,9 @@ export class GitHubV3RestApi extends AbstractFetchClient { label?: string requestBody?: string }, + baseUrl?: + | Server<"reposUploadReleaseAsset"> + | Server<"GitHubV3RestApiCustom">, timeout?: number, opts: RequestInit = {}, ): Promise | Res<422, void>>> { diff --git a/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts b/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts index a924b675..8beef19e 100644 --- a/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts +++ b/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts @@ -29,37 +29,37 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" -export type ContosoWidgetManagerServer = Server<"ContosoWidgetManagerServer"> - export class ContosoWidgetManagerServers { - static default(endpoint = ""): ContosoWidgetManagerServer { + static default(endpoint = ""): Server<"ContosoWidgetManager"> { return "{endpoint}/widget".replace( "{endpoint}", endpoint, - ) as ContosoWidgetManagerServer + ) as Server<"ContosoWidgetManager"> } static specific(url: "{endpoint}/widget") { switch (url) { case "{endpoint}/widget": return { - with(endpoint = ""): ContosoWidgetManagerServer { + with(endpoint = ""): Server<"ContosoWidgetManager"> { return "{endpoint}/widget".replace( "{endpoint}", endpoint, - ) as ContosoWidgetManagerServer + ) as Server<"ContosoWidgetManager"> }, } } } - static custom(url: string): ContosoWidgetManagerServer { - return url as ContosoWidgetManagerServer + static custom(url: string): Server<"ContosoWidgetManagerCustom"> { + return url as Server<"ContosoWidgetManagerCustom"> } } export interface ContosoWidgetManagerConfig extends AbstractFetchClientConfig { - basePath: ContosoWidgetManagerServer + basePath: + | Server<"ContosoWidgetManager"> + | Server<"ContosoWidgetManagerCustom"> } export class ContosoWidgetManager extends AbstractFetchClient { diff --git a/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts b/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts index e249899a..5bbcbc5f 100644 --- a/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts +++ b/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts @@ -21,33 +21,28 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" -export type ContosoProviderHubClientServer = - Server<"ContosoProviderHubClientServer"> - export class ContosoProviderHubClientServers { - static default(): ContosoProviderHubClientServer { - return "https://management.azure.com" as ContosoProviderHubClientServer + static default(): Server<"ContosoProviderHubClient"> { + return "https://management.azure.com" as Server<"ContosoProviderHubClient"> } static specific(url: "https://management.azure.com") { switch (url) { case "https://management.azure.com": - return { - with(): ContosoProviderHubClientServer { - return "https://management.azure.com" as ContosoProviderHubClientServer - }, - } + return "https://management.azure.com" as Server<"ContosoProviderHubClient"> } } - static custom(url: string): ContosoProviderHubClientServer { - return url as ContosoProviderHubClientServer + static custom(url: string): Server<"ContosoProviderHubClientCustom"> { + return url as Server<"ContosoProviderHubClientCustom"> } } export interface ContosoProviderHubClientConfig extends AbstractFetchClientConfig { - basePath: ContosoProviderHubClientServer + basePath: + | Server<"ContosoProviderHubClient"> + | Server<"ContosoProviderHubClientCustom"> } export class ContosoProviderHubClient extends AbstractFetchClient { diff --git a/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts index 72d0d1e1..5ac4dbe6 100644 --- a/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts @@ -28,16 +28,14 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" -export type MyAccountManagementServer = Server<"MyAccountManagementServer"> - export class MyAccountManagementServers { static default( yourOktaDomain = "subdomain.okta.com", - ): MyAccountManagementServer { + ): Server<"MyAccountManagement"> { return "https://{yourOktaDomain}".replace( "{yourOktaDomain}", yourOktaDomain, - ) as MyAccountManagementServer + ) as Server<"MyAccountManagement"> } static specific(url: "https://{yourOktaDomain}") { @@ -46,23 +44,23 @@ export class MyAccountManagementServers { return { with( yourOktaDomain = "subdomain.okta.com", - ): MyAccountManagementServer { + ): Server<"MyAccountManagement"> { return "https://{yourOktaDomain}".replace( "{yourOktaDomain}", yourOktaDomain, - ) as MyAccountManagementServer + ) as Server<"MyAccountManagement"> }, } } } - static custom(url: string): MyAccountManagementServer { - return url as MyAccountManagementServer + static custom(url: string): Server<"MyAccountManagementCustom"> { + return url as Server<"MyAccountManagementCustom"> } } export interface MyAccountManagementConfig extends AbstractFetchClientConfig { - basePath: MyAccountManagementServer + basePath: Server<"MyAccountManagement"> | Server<"MyAccountManagementCustom"> } export class MyAccountManagement extends AbstractFetchClient { diff --git a/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts index 410e8e18..6530466f 100644 --- a/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts @@ -42,17 +42,14 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" -export type OktaOpenIdConnectOAuth20Server = - Server<"OktaOpenIdConnectOAuth20Server"> - export class OktaOpenIdConnectOAuth20Servers { static default( yourOktaDomain = "subdomain.okta.com", - ): OktaOpenIdConnectOAuth20Server { + ): Server<"OktaOpenIdConnectOAuth20"> { return "https://{yourOktaDomain}".replace( "{yourOktaDomain}", yourOktaDomain, - ) as OktaOpenIdConnectOAuth20Server + ) as Server<"OktaOpenIdConnectOAuth20"> } static specific(url: "https://{yourOktaDomain}") { @@ -61,24 +58,26 @@ export class OktaOpenIdConnectOAuth20Servers { return { with( yourOktaDomain = "subdomain.okta.com", - ): OktaOpenIdConnectOAuth20Server { + ): Server<"OktaOpenIdConnectOAuth20"> { return "https://{yourOktaDomain}".replace( "{yourOktaDomain}", yourOktaDomain, - ) as OktaOpenIdConnectOAuth20Server + ) as Server<"OktaOpenIdConnectOAuth20"> }, } } } - static custom(url: string): OktaOpenIdConnectOAuth20Server { - return url as OktaOpenIdConnectOAuth20Server + static custom(url: string): Server<"OktaOpenIdConnectOAuth20Custom"> { + return url as Server<"OktaOpenIdConnectOAuth20Custom"> } } export interface OktaOpenIdConnectOAuth20Config extends AbstractFetchClientConfig { - basePath: OktaOpenIdConnectOAuth20Server + basePath: + | Server<"OktaOpenIdConnectOAuth20"> + | Server<"OktaOpenIdConnectOAuth20Custom"> } export class OktaOpenIdConnectOAuth20 extends AbstractFetchClient { diff --git a/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts index d2ed0bfb..72f309f5 100644 --- a/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts @@ -12,31 +12,25 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" -export type SwaggerPetstoreServer = Server<"SwaggerPetstoreServer"> - export class SwaggerPetstoreServers { - static default(): SwaggerPetstoreServer { - return "https://petstore.swagger.io/v2" as SwaggerPetstoreServer + static default(): Server<"SwaggerPetstore"> { + return "https://petstore.swagger.io/v2" as Server<"SwaggerPetstore"> } static specific(url: "https://petstore.swagger.io/v2") { switch (url) { case "https://petstore.swagger.io/v2": - return { - with(): SwaggerPetstoreServer { - return "https://petstore.swagger.io/v2" as SwaggerPetstoreServer - }, - } + return "https://petstore.swagger.io/v2" as Server<"SwaggerPetstore"> } } - static custom(url: string): SwaggerPetstoreServer { - return url as SwaggerPetstoreServer + static custom(url: string): Server<"SwaggerPetstoreCustom"> { + return url as Server<"SwaggerPetstoreCustom"> } } export interface SwaggerPetstoreConfig extends AbstractFetchClientConfig { - basePath: SwaggerPetstoreServer + basePath: Server<"SwaggerPetstore"> | Server<"SwaggerPetstoreCustom"> } export class SwaggerPetstore extends AbstractFetchClient { diff --git a/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts index 73a4dbff..0dc2e149 100644 --- a/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts @@ -171,52 +171,38 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" -export type StripeApiServer = Server<"StripeApiServer"> - export class StripeApiServers { - static default(): StripeApiServer { - return "https://api.stripe.com/" as StripeApiServer + static default(): Server<"StripeApi"> { + return "https://api.stripe.com/" as Server<"StripeApi"> } static specific(url: "https://api.stripe.com/") { switch (url) { case "https://api.stripe.com/": - return { - with(): StripeApiServer { - return "https://api.stripe.com/" as StripeApiServer - }, - } + return "https://api.stripe.com/" as Server<"StripeApi"> } } - static custom(url: string): StripeApiServer { - return url as StripeApiServer + static custom(url: string): Server<"StripeApiCustom"> { + return url as Server<"StripeApiCustom"> } static postFiles(url: "https://files.stripe.com/") { switch (url) { case "https://files.stripe.com/": - return { - with(): Server<"postFiles"> { - return "https://files.stripe.com/" as Server<"postFiles"> - }, - } + return "https://files.stripe.com/" as Server<"postFiles"> } } static getQuotesQuotePdf(url: "https://files.stripe.com/") { switch (url) { case "https://files.stripe.com/": - return { - with(): Server<"getQuotesQuotePdf"> { - return "https://files.stripe.com/" as Server<"getQuotesQuotePdf"> - }, - } + return "https://files.stripe.com/" as Server<"getQuotesQuotePdf"> } } } export interface StripeApiConfig extends AbstractFetchClientConfig { - basePath: StripeApiServer + basePath: Server<"StripeApi"> | Server<"StripeApiCustom"> } export class StripeApi extends AbstractFetchClient { @@ -10538,6 +10524,7 @@ export class StripeApi extends AbstractFetchClient { | "terminal_reader_splashscreen" } }, + baseUrl?: Server<"postFiles"> | Server<"StripeApiCustom">, timeout?: number, opts: RequestInit = {}, ): Promise | Res>> { @@ -25967,6 +25954,7 @@ export class StripeApi extends AbstractFetchClient { quote: string requestBody?: EmptyObject }, + baseUrl?: Server<"getQuotesQuotePdf"> | Server<"StripeApiCustom">, timeout?: number, opts: RequestInit = {}, ): Promise | Res>> { diff --git a/packages/openapi-code-generator/src/typescript/common/client-builder.ts b/packages/openapi-code-generator/src/typescript/common/client-builder.ts index ab8feaf1..46a032f8 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-builder.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-builder.ts @@ -40,7 +40,10 @@ export abstract class TypescriptClientBuilder implements ICompilable { this.config.enableTypedBasePaths && this.clientServersBuilder.hasServers ) { - return this.clientServersBuilder.typeExportName + return union( + this.clientServersBuilder.typeForDefault(), + this.clientServersBuilder.typeForCustom(), + ) } return "" diff --git a/packages/openapi-code-generator/src/typescript/common/client-operation-builder.ts b/packages/openapi-code-generator/src/typescript/common/client-operation-builder.ts index 7025d1d6..239bb618 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-operation-builder.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-operation-builder.ts @@ -48,6 +48,10 @@ export class ClientOperationBuilder { return this.operation.method } + get hasServers(): boolean { + return this.operation.servers.length > 0 + } + methodParameter(): MethodParameterDefinition | undefined { const {parameters} = this.operation const {requestBodyParameter} = this.requestBodyAsParameter() diff --git a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts index 70d9fc4c..bc365fef 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts @@ -47,8 +47,8 @@ export class ClientServersBuilder implements ICompilable { } return ` - static default(${this.toParams(defaultServer.variables)}): ${this.typeExportName} { - return (${this.toReplacer(defaultServer)} as ${this.typeExportName}) + static default(${this.toParams(defaultServer.variables)}): ${this.typeForDefault()} { + return (${this.toReplacer(defaultServer)} as ${this.typeForDefault()}) } ` } @@ -65,10 +65,14 @@ export class ClientServersBuilder implements ICompilable { .map( (server) => ` case ${quotedStringLiteral(server.url)}: - return { - with(${this.toParams(server.variables)}): ${typeName} { + return ${ + Object.keys(server.variables).length > 0 + ? `{ + with(${this.toParams(server.variables)}): ${typeName} { return (${this.toReplacer(server)} as ${typeName}) } + }` + : `(${quotedStringLiteral(server.url)} as ${typeName})` } `, ) @@ -88,8 +92,17 @@ export class ClientServersBuilder implements ICompilable { get classExportName(): string { return `${this.name}Servers` } - get typeExportName(): string { - return `${this.name}Server` + + typeForDefault() { + return `Server<"${this.name}">` + } + + typeForCustom() { + return `Server<"${this.name}Custom">` + } + + typeForOperationId(operationId: string) { + return `Server<"${operationId}">` } toString() { @@ -98,17 +111,15 @@ export class ClientServersBuilder implements ICompilable { } return ` - export type ${this.typeExportName} = Server<"${this.typeExportName}"> - export class ${this.classExportName} { ${this.toDefault()} - ${this.toSpecific("specific", this.servers, this.typeExportName)} + ${this.toSpecific("specific", this.servers, this.typeForDefault())} - static custom(url: string): ${this.typeExportName} { - return (url as ${this.typeExportName}) + static custom(url: string): ${this.typeForCustom()} { + return (url as ${this.typeForCustom()}) } - ${this.operations.map((it) => this.toSpecific(it.operationId, it.servers, `Server<"${it.operationId}">`)).join("\n")} + ${this.operations.map((it) => this.toSpecific(it.operationId, it.servers, this.typeForOperationId(it.operationId))).join("\n")} } ` } diff --git a/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts b/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts index 3a2a243e..12aa1d56 100644 --- a/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts +++ b/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts @@ -37,7 +37,7 @@ export class TypescriptFetchClientBuilder extends TypescriptClientBuilder { } protected buildOperation(builder: ClientOperationBuilder): string { - const {operationId, route, method} = builder + const {operationId, route, method, hasServers} = builder const {requestBodyParameter} = builder.requestBodyAsParameter() const operationParameter = builder.methodParameter() @@ -93,6 +93,16 @@ export class TypescriptFetchClientBuilder extends TypescriptClientBuilder { name: operationId, parameters: [ operationParameter, + hasServers + ? { + name: "baseUrl", + type: union( + this.clientServersBuilder.typeForOperationId(operationId), + this.clientServersBuilder.typeForCustom(), + ), + required: false, + } + : undefined, {name: "timeout", type: "number", required: false}, { name: "opts", From 5b16ed7c88e967d719bf5370943ccb7cbe56fad5 Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Tue, 3 Dec 2024 06:45:32 +0000 Subject: [PATCH 09/24] feat: use overridden basePath --- .../src/generated/api.github.com.yaml/client.ts | 4 ++-- .../typescript-fetch/src/generated/stripe.yaml/client.ts | 8 ++++---- .../typescript-fetch/typescript-fetch-client-builder.ts | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts index 4fcab70d..1cb6d081 100644 --- a/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts @@ -20348,14 +20348,14 @@ export class GitHubV3RestApi extends AbstractFetchClient { label?: string requestBody?: string }, - baseUrl?: + basePath?: | Server<"reposUploadReleaseAsset"> | Server<"GitHubV3RestApiCustom">, timeout?: number, opts: RequestInit = {}, ): Promise | Res<422, void>>> { const url = - this.basePath + + basePath + `/repos/${p["owner"]}/${p["repo"]}/releases/${p["releaseId"]}/assets` const headers = this._headers( { "Content-Type": "application/octet-stream" }, diff --git a/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts index 0dc2e149..cc9f29c0 100644 --- a/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts @@ -10524,11 +10524,11 @@ export class StripeApi extends AbstractFetchClient { | "terminal_reader_splashscreen" } }, - baseUrl?: Server<"postFiles"> | Server<"StripeApiCustom">, + basePath?: Server<"postFiles"> | Server<"StripeApiCustom">, timeout?: number, opts: RequestInit = {}, ): Promise | Res>> { - const url = this.basePath + `/v1/files` + const url = basePath + `/v1/files` const headers = this._headers( { "Content-Type": "multipart/form-data" }, opts.headers, @@ -25954,11 +25954,11 @@ export class StripeApi extends AbstractFetchClient { quote: string requestBody?: EmptyObject }, - baseUrl?: Server<"getQuotesQuotePdf"> | Server<"StripeApiCustom">, + basePath?: Server<"getQuotesQuotePdf"> | Server<"StripeApiCustom">, timeout?: number, opts: RequestInit = {}, ): Promise | Res>> { - const url = this.basePath + `/v1/quotes/${p["quote"]}/pdf` + const url = basePath + `/v1/quotes/${p["quote"]}/pdf` const headers = this._headers( { "Content-Type": "application/x-www-form-urlencoded" }, opts.headers, diff --git a/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts b/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts index 12aa1d56..212ca48b 100644 --- a/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts +++ b/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts @@ -67,7 +67,7 @@ export class TypescriptFetchClientBuilder extends TypescriptClientBuilder { .join("\n")}}, timeout)` const body = ` - const url = this.basePath + \`${routeToTemplateString(route)}\` + const url = ${hasServers ? "basePath" : "this.basePath"} + \`${routeToTemplateString(route)}\` ${[ headers ? `const headers = this._headers(${headers}, opts.headers)` @@ -95,7 +95,7 @@ export class TypescriptFetchClientBuilder extends TypescriptClientBuilder { operationParameter, hasServers ? { - name: "baseUrl", + name: "basePath", type: union( this.clientServersBuilder.typeForOperationId(operationId), this.clientServersBuilder.typeForCustom(), From 713d4c811549f51b606ab7d136dcbdc04bbdc36b Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Tue, 3 Dec 2024 06:49:20 +0000 Subject: [PATCH 10/24] feat: use overridden basePath --- .../src/generated/api.github.com.yaml/client.ts | 4 ++++ .../src/generated/stripe.yaml/client.ts | 4 ++++ .../typescript-axios-client-builder.ts | 13 ++++++++++++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts b/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts index 86fa36f8..ee5e5a65 100644 --- a/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts @@ -20168,6 +20168,9 @@ export class GitHubV3RestApi extends AbstractAxiosClient { label?: string requestBody?: string }, + basePath?: + | Server<"reposUploadReleaseAsset"> + | Server<"GitHubV3RestApiCustom">, timeout?: number, opts: AxiosRequestConfig = {}, ): Promise> { @@ -20183,6 +20186,7 @@ export class GitHubV3RestApi extends AbstractAxiosClient { url: url + query, method: "POST", data: body, + ...(basePath ? { baseURL: basePath } : {}), ...(timeout ? { timeout } : {}), ...opts, headers, diff --git a/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts b/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts index b6ad6dfb..2f17e805 100644 --- a/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts @@ -10722,6 +10722,7 @@ export class StripeApi extends AbstractAxiosClient { | "terminal_reader_splashscreen" } }, + basePath?: Server<"postFiles"> | Server<"StripeApiCustom">, timeout?: number, opts: AxiosRequestConfig = {}, ): Promise> { @@ -10736,6 +10737,7 @@ export class StripeApi extends AbstractAxiosClient { url: url, method: "POST", data: body, + ...(basePath ? { baseURL: basePath } : {}), ...(timeout ? { timeout } : {}), ...opts, headers, @@ -26360,6 +26362,7 @@ export class StripeApi extends AbstractAxiosClient { quote: string requestBody?: EmptyObject }, + basePath?: Server<"getQuotesQuotePdf"> | Server<"StripeApiCustom">, timeout?: number, opts: AxiosRequestConfig = {}, ): Promise> { @@ -26375,6 +26378,7 @@ export class StripeApi extends AbstractAxiosClient { url: url + query, method: "GET", data: body, + ...(basePath ? { baseURL: basePath } : {}), ...(timeout ? { timeout } : {}), ...opts, headers, diff --git a/packages/openapi-code-generator/src/typescript/typescript-axios/typescript-axios-client-builder.ts b/packages/openapi-code-generator/src/typescript/typescript-axios/typescript-axios-client-builder.ts index ac44fbe5..c5400b1c 100644 --- a/packages/openapi-code-generator/src/typescript/typescript-axios/typescript-axios-client-builder.ts +++ b/packages/openapi-code-generator/src/typescript/typescript-axios/typescript-axios-client-builder.ts @@ -16,7 +16,7 @@ export class TypescriptAxiosClientBuilder extends TypescriptClientBuilder { } protected buildOperation(builder: ClientOperationBuilder): string { - const {operationId, route, method} = builder + const {operationId, route, method, hasServers} = builder const {requestBodyParameter} = builder.requestBodyAsParameter() const operationParameter = builder.methodParameter() @@ -52,6 +52,7 @@ export class TypescriptAxiosClientBuilder extends TypescriptClientBuilder { requestBodyParameter ? "data: body" : "", // ensure compatibility with `exactOptionalPropertyTypes` compiler option // https://www.typescriptlang.org/tsconfig#exactOptionalPropertyTypes + hasServers ? "...(basePath? {baseURL: basePath} : {})" : undefined, "...(timeout ? {timeout} : {})", "...opts", "headers", @@ -87,6 +88,16 @@ export class TypescriptAxiosClientBuilder extends TypescriptClientBuilder { name: operationId, parameters: [ operationParameter, + hasServers + ? { + name: "basePath", + type: union( + this.clientServersBuilder.typeForOperationId(operationId), + this.clientServersBuilder.typeForCustom(), + ), + required: false, + } + : undefined, {name: "timeout", type: "number", required: false}, { name: "opts", From 8bb8477e986e27d081b5b06b4041f846882df344 Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Fri, 6 Dec 2024 08:53:49 +0000 Subject: [PATCH 11/24] test: add unit tests, fix enum types --- .../common/client-servers-builder.spec.ts | 253 ++++++++++++++++++ .../common/client-servers-builder.ts | 6 +- 2 files changed, 256 insertions(+), 3 deletions(-) create mode 100644 packages/openapi-code-generator/src/typescript/common/client-servers-builder.spec.ts diff --git a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.spec.ts b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.spec.ts new file mode 100644 index 00000000..73f734c7 --- /dev/null +++ b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.spec.ts @@ -0,0 +1,253 @@ +import {describe, expect, it} from "@jest/globals" +import type {IROperation, IRServer} from "../../core/openapi-types-normalized" +import {ClientServersBuilder} from "./client-servers-builder" +import {ImportBuilder} from "./import-builder" +import {TypescriptFormatterBiome} from "./typescript-formatter.biome" + +type TestResult = { + output: string + hasServers: boolean + hasOperationServers: boolean +} + +async function runTest( + servers: IRServer[], + operations: Pick[] = [], +) { + const builder = new ClientServersBuilder( + "unit-test.ts", + "UnitTest", + servers, + new ImportBuilder(), + ) + + for (const it of operations) { + builder.addOperation(it) + } + + const formatter = await TypescriptFormatterBiome.createNodeFormatter() + + return { + output: await formatter.format("unit-test.ts", builder.toString()), + hasServers: builder.hasServers, + hasOperationServers: builder.hasOperationServers, + } +} + +describe("typescript/common/client-servers-builder", () => { + describe("no servers", () => { + let result: TestResult + + beforeAll(async () => { + result = await runTest([]) + }) + + it("produces nothing", () => { + expect(result.output).toMatchInlineSnapshot(`""`) + }) + + it("hasServers is false", () => { + expect(result.hasServers).toBe(false) + }) + + it("hasOperationServers is false", () => { + expect(result.hasOperationServers).toBe(false) + }) + }) + + describe("root servers, no operation servers", () => { + let result: TestResult + + beforeAll(async () => { + result = await runTest([ + { + url: "{schema}://unit-tests-default.{tenant}.example.com", + variables: { + schema: { + default: "https", + enum: ["https", "http"], + description: "The protocol to use", + }, + tenant: { + default: "example", + enum: [], + description: "Your tenant slug", + }, + }, + description: "Default Unit test server", + }, + { + url: "https://unit-tests-other.example.com", + variables: {}, + description: "Secondary Unit test server", + }, + ]) + }) + + it("produces a default, specific, and custom", () => { + expect(result.output).toMatchInlineSnapshot(` + "export class UnitTestServers { + static default( + schema: "https" | "http" = "https", + tenant = "example", + ): Server<"UnitTest"> { + return "{schema}://unit-tests-default.{tenant}.example.com" + .replace("{schema}", schema) + .replace("{tenant}", tenant) as Server<"UnitTest"> + } + + static specific( + url: + | "{schema}://unit-tests-default.{tenant}.example.com" + | "https://unit-tests-other.example.com", + ) { + switch (url) { + case "{schema}://unit-tests-default.{tenant}.example.com": + return { + with( + schema: "https" | "http" = "https", + tenant = "example", + ): Server<"UnitTest"> { + return "{schema}://unit-tests-default.{tenant}.example.com" + .replace("{schema}", schema) + .replace("{tenant}", tenant) as Server<"UnitTest"> + }, + } + + case "https://unit-tests-other.example.com": + return "https://unit-tests-other.example.com" as Server<"UnitTest"> + } + } + + static custom(url: string): Server<"UnitTestCustom"> { + return url as Server<"UnitTestCustom"> + } + } + " + `) + }) + + it("hasServers is true", () => { + expect(result.hasServers).toBe(true) + }) + + it("hasOperationServers is false", () => { + expect(result.hasOperationServers).toBe(false) + }) + }) + + describe("root servers, operation servers", () => { + let result: TestResult + + beforeAll(async () => { + result = await runTest( + [ + { + url: "https://unit-tests-default.example.com", + variables: {}, + description: "Default Unit test server", + }, + { + url: "https://unit-tests-other.example.com", + variables: {}, + description: "Secondary Unit test server", + }, + ], + [ + { + operationId: "testOperation", + servers: [ + { + url: "{schema}}://test-operation.{tenant}.example.com", + variables: { + schema: { + default: "https", + enum: ["https", "http"], + description: "The protocol to use", + }, + tenant: { + default: "example", + enum: [], + description: "Your tenant slug", + }, + }, + description: "Test operation server", + }, + ], + }, + { + operationId: "anotherTestOperation", + servers: [ + { + url: "https://another-test-operation.example.com", + variables: {}, + description: "Another test operation server", + }, + ], + }, + ], + ) + }) + + it("produces a default, specific, custom, and operations", () => { + expect(result.output).toMatchInlineSnapshot(` + "export class UnitTestServers { + static default(): Server<"UnitTest"> { + return "https://unit-tests-default.example.com" as Server<"UnitTest"> + } + + static specific( + url: + | "https://unit-tests-default.example.com" + | "https://unit-tests-other.example.com", + ) { + switch (url) { + case "https://unit-tests-default.example.com": + return "https://unit-tests-default.example.com" as Server<"UnitTest"> + + case "https://unit-tests-other.example.com": + return "https://unit-tests-other.example.com" as Server<"UnitTest"> + } + } + + static custom(url: string): Server<"UnitTestCustom"> { + return url as Server<"UnitTestCustom"> + } + + static testOperation(url: "{schema}}://test-operation.{tenant}.example.com") { + switch (url) { + case "{schema}}://test-operation.{tenant}.example.com": + return { + with( + schema: "https" | "http" = "https", + tenant = "example", + ): Server<"testOperation"> { + return "{schema}}://test-operation.{tenant}.example.com" + .replace("{schema}", schema) + .replace("{tenant}", tenant) as Server<"testOperation"> + }, + } + } + } + static anotherTestOperation( + url: "https://another-test-operation.example.com", + ) { + switch (url) { + case "https://another-test-operation.example.com": + return "https://another-test-operation.example.com" as Server<"anotherTestOperation"> + } + } + } + " + `) + }) + + it("hasServers is true", () => { + expect(result.hasServers).toBe(true) + }) + + it("hasOperationServers is true", () => { + expect(result.hasOperationServers).toBe(true) + }) + }) +}) diff --git a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts index bc365fef..4a8e69db 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts @@ -8,7 +8,7 @@ import type {ImportBuilder} from "./import-builder" import {quotedStringLiteral, union} from "./type-utils" export class ClientServersBuilder implements ICompilable { - readonly operations: IROperation[] = [] + readonly operations: Pick[] = [] constructor( readonly filename: string, @@ -17,7 +17,7 @@ export class ClientServersBuilder implements ICompilable { readonly imports: ImportBuilder, ) {} - addOperation(operation: IROperation) { + addOperation(operation: Pick) { if (operation.servers.length) { this.operations.push(operation) } @@ -28,7 +28,7 @@ export class ClientServersBuilder implements ICompilable { }) { return Object.entries(variables) .map(([name, variable]) => { - const type = union(...variable.enum) + const type = union(...variable.enum.map(quotedStringLiteral)) return `${name}${type ? `:${type}` : ""} = "${variable.default}"` }) .join(",") From 545a07a826edfdc56d644d3a0e7a4154c295f37d Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Fri, 6 Dec 2024 08:59:59 +0000 Subject: [PATCH 12/24] fix: nest operations to avoid naming collisions --- .../api.github.com.yaml/client.service.ts | 12 ++--- .../generated/stripe.yaml/client.service.ts | 24 +++++----- .../generated/api.github.com.yaml/client.ts | 12 ++--- .../src/generated/stripe.yaml/client.ts | 24 +++++----- .../generated/api.github.com.yaml/client.ts | 12 ++--- .../src/generated/stripe.yaml/client.ts | 24 +++++----- .../common/client-servers-builder.spec.ts | 44 +++++++++---------- .../common/client-servers-builder.ts | 28 ++++++++++-- 8 files changed, 107 insertions(+), 73 deletions(-) diff --git a/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts index 43b881a7..1721e584 100644 --- a/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts @@ -336,11 +336,13 @@ export class GitHubV3RestApiServiceServers { return url as Server<"GitHubV3RestApiServiceCustom"> } - static reposUploadReleaseAsset(url: "https://uploads.github.com") { - switch (url) { - case "https://uploads.github.com": - return "https://uploads.github.com" as Server<"reposUploadReleaseAsset"> - } + static readonly operations = { + reposUploadReleaseAsset(url: "https://uploads.github.com") { + switch (url) { + case "https://uploads.github.com": + return "https://uploads.github.com" as Server<"reposUploadReleaseAsset"> + } + }, } } diff --git a/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts index 6181d13c..3b9e22be 100644 --- a/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts @@ -182,17 +182,19 @@ export class StripeApiServiceServers { return url as Server<"StripeApiServiceCustom"> } - static postFiles(url: "https://files.stripe.com/") { - switch (url) { - case "https://files.stripe.com/": - return "https://files.stripe.com/" as Server<"postFiles"> - } - } - static getQuotesQuotePdf(url: "https://files.stripe.com/") { - switch (url) { - case "https://files.stripe.com/": - return "https://files.stripe.com/" as Server<"getQuotesQuotePdf"> - } + static readonly operations = { + postFiles(url: "https://files.stripe.com/") { + switch (url) { + case "https://files.stripe.com/": + return "https://files.stripe.com/" as Server<"postFiles"> + } + }, + getQuotesQuotePdf(url: "https://files.stripe.com/") { + switch (url) { + case "https://files.stripe.com/": + return "https://files.stripe.com/" as Server<"getQuotesQuotePdf"> + } + }, } } diff --git a/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts b/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts index ee5e5a65..b481f1e6 100644 --- a/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts @@ -334,11 +334,13 @@ export class GitHubV3RestApiServers { return url as Server<"GitHubV3RestApiCustom"> } - static reposUploadReleaseAsset(url: "https://uploads.github.com") { - switch (url) { - case "https://uploads.github.com": - return "https://uploads.github.com" as Server<"reposUploadReleaseAsset"> - } + static readonly operations = { + reposUploadReleaseAsset(url: "https://uploads.github.com") { + switch (url) { + case "https://uploads.github.com": + return "https://uploads.github.com" as Server<"reposUploadReleaseAsset"> + } + }, } } diff --git a/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts b/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts index 2f17e805..7bb951f0 100644 --- a/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts @@ -184,17 +184,19 @@ export class StripeApiServers { return url as Server<"StripeApiCustom"> } - static postFiles(url: "https://files.stripe.com/") { - switch (url) { - case "https://files.stripe.com/": - return "https://files.stripe.com/" as Server<"postFiles"> - } - } - static getQuotesQuotePdf(url: "https://files.stripe.com/") { - switch (url) { - case "https://files.stripe.com/": - return "https://files.stripe.com/" as Server<"getQuotesQuotePdf"> - } + static readonly operations = { + postFiles(url: "https://files.stripe.com/") { + switch (url) { + case "https://files.stripe.com/": + return "https://files.stripe.com/" as Server<"postFiles"> + } + }, + getQuotesQuotePdf(url: "https://files.stripe.com/") { + switch (url) { + case "https://files.stripe.com/": + return "https://files.stripe.com/" as Server<"getQuotesQuotePdf"> + } + }, } } diff --git a/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts index 1cb6d081..882beb2a 100644 --- a/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts @@ -340,11 +340,13 @@ export class GitHubV3RestApiServers { return url as Server<"GitHubV3RestApiCustom"> } - static reposUploadReleaseAsset(url: "https://uploads.github.com") { - switch (url) { - case "https://uploads.github.com": - return "https://uploads.github.com" as Server<"reposUploadReleaseAsset"> - } + static readonly operations = { + reposUploadReleaseAsset(url: "https://uploads.github.com") { + switch (url) { + case "https://uploads.github.com": + return "https://uploads.github.com" as Server<"reposUploadReleaseAsset"> + } + }, } } diff --git a/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts index cc9f29c0..0792d751 100644 --- a/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts @@ -187,17 +187,19 @@ export class StripeApiServers { return url as Server<"StripeApiCustom"> } - static postFiles(url: "https://files.stripe.com/") { - switch (url) { - case "https://files.stripe.com/": - return "https://files.stripe.com/" as Server<"postFiles"> - } - } - static getQuotesQuotePdf(url: "https://files.stripe.com/") { - switch (url) { - case "https://files.stripe.com/": - return "https://files.stripe.com/" as Server<"getQuotesQuotePdf"> - } + static readonly operations = { + postFiles(url: "https://files.stripe.com/") { + switch (url) { + case "https://files.stripe.com/": + return "https://files.stripe.com/" as Server<"postFiles"> + } + }, + getQuotesQuotePdf(url: "https://files.stripe.com/") { + switch (url) { + case "https://files.stripe.com/": + return "https://files.stripe.com/" as Server<"getQuotesQuotePdf"> + } + }, } } diff --git a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.spec.ts b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.spec.ts index 73f734c7..329eb31c 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.spec.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.spec.ts @@ -214,28 +214,28 @@ describe("typescript/common/client-servers-builder", () => { return url as Server<"UnitTestCustom"> } - static testOperation(url: "{schema}}://test-operation.{tenant}.example.com") { - switch (url) { - case "{schema}}://test-operation.{tenant}.example.com": - return { - with( - schema: "https" | "http" = "https", - tenant = "example", - ): Server<"testOperation"> { - return "{schema}}://test-operation.{tenant}.example.com" - .replace("{schema}", schema) - .replace("{tenant}", tenant) as Server<"testOperation"> - }, - } - } - } - static anotherTestOperation( - url: "https://another-test-operation.example.com", - ) { - switch (url) { - case "https://another-test-operation.example.com": - return "https://another-test-operation.example.com" as Server<"anotherTestOperation"> - } + static readonly operations = { + testOperation(url: "{schema}}://test-operation.{tenant}.example.com") { + switch (url) { + case "{schema}}://test-operation.{tenant}.example.com": + return { + with( + schema: "https" | "http" = "https", + tenant = "example", + ): Server<"testOperation"> { + return "{schema}}://test-operation.{tenant}.example.com" + .replace("{schema}", schema) + .replace("{tenant}", tenant) as Server<"testOperation"> + }, + } + } + }, + anotherTestOperation(url: "https://another-test-operation.example.com") { + switch (url) { + case "https://another-test-operation.example.com": + return "https://another-test-operation.example.com" as Server<"anotherTestOperation"> + } + }, } } " diff --git a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts index 4a8e69db..805439ac 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts @@ -53,13 +53,18 @@ export class ClientServersBuilder implements ICompilable { ` } - private toSpecific(name: string, servers: IRServer[], typeName: string) { + private toSpecific( + name: string, + servers: IRServer[], + typeName: string, + isStatic = true, + ) { if (!this.hasServers) { return "" } const urls = servers.map((it) => quotedStringLiteral(it.url)) - return `static ${name}(url: ${union(urls)}){ + return `${isStatic ? "static " : ""}${name}(url: ${union(urls)}){ switch(url) { ${servers .map( @@ -110,6 +115,17 @@ export class ClientServersBuilder implements ICompilable { return "" } + const operations = this.operations + .map((it) => + this.toSpecific( + it.operationId, + it.servers, + this.typeForOperationId(it.operationId), + false, + ), + ) + .join(",\n") + return ` export class ${this.classExportName} { ${this.toDefault()} @@ -119,7 +135,13 @@ export class ClientServersBuilder implements ICompilable { return (url as ${this.typeForCustom()}) } - ${this.operations.map((it) => this.toSpecific(it.operationId, it.servers, this.typeForOperationId(it.operationId))).join("\n")} + ${ + operations.length + ? `static readonly operations = { + ${operations} + }` + : "" + } } ` } From 3b4b8522558341796508d5e3475a86fd2fc6b713 Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Fri, 6 Dec 2024 09:13:10 +0000 Subject: [PATCH 13/24] feat: default server for operations --- .../client.service.ts | 2 +- .../generated/okta.idp.yaml/client.service.ts | 2 +- .../okta.oauth.yaml/client.service.ts | 2 +- .../client.ts | 2 +- .../src/generated/okta.idp.yaml/client.ts | 2 +- .../src/generated/okta.oauth.yaml/client.ts | 2 +- .../generated/api.github.com.yaml/client.ts | 6 ++-- .../client.ts | 2 +- .../src/generated/okta.idp.yaml/client.ts | 2 +- .../src/generated/okta.oauth.yaml/client.ts | 2 +- .../src/generated/stripe.yaml/client.ts | 12 +++++-- .../src/typescript/common/client-builder.ts | 2 +- .../common/client-servers-builder.spec.ts | 34 +++++++++++++++++-- .../common/client-servers-builder.ts | 22 +++++++++++- .../typescript-fetch-client-builder.ts | 3 +- 15 files changed, 79 insertions(+), 18 deletions(-) diff --git a/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts b/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts index e449b1ff..6f759038 100644 --- a/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts @@ -36,7 +36,7 @@ export class ContosoWidgetManagerServiceServers { switch (url) { case "{endpoint}/widget": return { - with(endpoint = ""): Server<"ContosoWidgetManagerService"> { + build(endpoint = ""): Server<"ContosoWidgetManagerService"> { return "{endpoint}/widget".replace( "{endpoint}", endpoint, diff --git a/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts index 794c48f3..fbae0b3f 100644 --- a/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts @@ -38,7 +38,7 @@ export class MyAccountManagementServiceServers { switch (url) { case "https://{yourOktaDomain}": return { - with( + build( yourOktaDomain = "subdomain.okta.com", ): Server<"MyAccountManagementService"> { return "https://{yourOktaDomain}".replace( diff --git a/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts index 26f6417a..e6d7b785 100644 --- a/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts @@ -52,7 +52,7 @@ export class OktaOpenIdConnectOAuth20ServiceServers { switch (url) { case "https://{yourOktaDomain}": return { - with( + build( yourOktaDomain = "subdomain.okta.com", ): Server<"OktaOpenIdConnectOAuth20Service"> { return "https://{yourOktaDomain}".replace( diff --git a/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts b/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts index d9cd0bc9..a00ae62b 100644 --- a/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts +++ b/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts @@ -38,7 +38,7 @@ export class ContosoWidgetManagerServers { switch (url) { case "{endpoint}/widget": return { - with(endpoint = ""): Server<"ContosoWidgetManager"> { + build(endpoint = ""): Server<"ContosoWidgetManager"> { return "{endpoint}/widget".replace( "{endpoint}", endpoint, diff --git a/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts b/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts index 000e89d0..80872c3d 100644 --- a/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts @@ -40,7 +40,7 @@ export class MyAccountManagementServers { switch (url) { case "https://{yourOktaDomain}": return { - with( + build( yourOktaDomain = "subdomain.okta.com", ): Server<"MyAccountManagement"> { return "https://{yourOktaDomain}".replace( diff --git a/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts b/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts index c96c437f..2147361e 100644 --- a/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts @@ -53,7 +53,7 @@ export class OktaOpenIdConnectOAuth20Servers { switch (url) { case "https://{yourOktaDomain}": return { - with( + build( yourOktaDomain = "subdomain.okta.com", ): Server<"OktaOpenIdConnectOAuth20"> { return "https://{yourOktaDomain}".replace( diff --git a/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts index 882beb2a..d2dc22aa 100644 --- a/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts @@ -20350,9 +20350,11 @@ export class GitHubV3RestApi extends AbstractFetchClient { label?: string requestBody?: string }, - basePath?: + basePath: | Server<"reposUploadReleaseAsset"> - | Server<"GitHubV3RestApiCustom">, + | Server<"GitHubV3RestApiCustom"> = GitHubV3RestApiServers.operations.reposUploadReleaseAsset( + "https://uploads.github.com", + ), timeout?: number, opts: RequestInit = {}, ): Promise | Res<422, void>>> { diff --git a/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts b/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts index 8beef19e..d65bef75 100644 --- a/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts +++ b/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts @@ -41,7 +41,7 @@ export class ContosoWidgetManagerServers { switch (url) { case "{endpoint}/widget": return { - with(endpoint = ""): Server<"ContosoWidgetManager"> { + build(endpoint = ""): Server<"ContosoWidgetManager"> { return "{endpoint}/widget".replace( "{endpoint}", endpoint, diff --git a/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts index 5ac4dbe6..901ab6b5 100644 --- a/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts @@ -42,7 +42,7 @@ export class MyAccountManagementServers { switch (url) { case "https://{yourOktaDomain}": return { - with( + build( yourOktaDomain = "subdomain.okta.com", ): Server<"MyAccountManagement"> { return "https://{yourOktaDomain}".replace( diff --git a/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts index 6530466f..9e7f3312 100644 --- a/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts @@ -56,7 +56,7 @@ export class OktaOpenIdConnectOAuth20Servers { switch (url) { case "https://{yourOktaDomain}": return { - with( + build( yourOktaDomain = "subdomain.okta.com", ): Server<"OktaOpenIdConnectOAuth20"> { return "https://{yourOktaDomain}".replace( diff --git a/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts index 0792d751..ea9daa0d 100644 --- a/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts @@ -10526,7 +10526,11 @@ export class StripeApi extends AbstractFetchClient { | "terminal_reader_splashscreen" } }, - basePath?: Server<"postFiles"> | Server<"StripeApiCustom">, + basePath: + | Server<"postFiles"> + | Server<"StripeApiCustom"> = StripeApiServers.operations.postFiles( + "https://files.stripe.com/", + ), timeout?: number, opts: RequestInit = {}, ): Promise | Res>> { @@ -25956,7 +25960,11 @@ export class StripeApi extends AbstractFetchClient { quote: string requestBody?: EmptyObject }, - basePath?: Server<"getQuotesQuotePdf"> | Server<"StripeApiCustom">, + basePath: + | Server<"getQuotesQuotePdf"> + | Server<"StripeApiCustom"> = StripeApiServers.operations.getQuotesQuotePdf( + "https://files.stripe.com/", + ), timeout?: number, opts: RequestInit = {}, ): Promise | Res>> { diff --git a/packages/openapi-code-generator/src/typescript/common/client-builder.ts b/packages/openapi-code-generator/src/typescript/common/client-builder.ts index 46a032f8..872a1d8a 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-builder.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-builder.ts @@ -55,9 +55,9 @@ export abstract class TypescriptClientBuilder implements ICompilable { this.models, this.schemaBuilder, ) + this.clientServersBuilder.addOperation(operation) const result = this.buildOperation(builder) this.operations.push(result) - this.clientServersBuilder.addOperation(operation) } protected abstract buildImports(imports: ImportBuilder): void diff --git a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.spec.ts b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.spec.ts index 329eb31c..ada1cae4 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.spec.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.spec.ts @@ -8,6 +8,7 @@ type TestResult = { output: string hasServers: boolean hasOperationServers: boolean + builder: ClientServersBuilder } async function runTest( @@ -31,6 +32,7 @@ async function runTest( output: await formatter.format("unit-test.ts", builder.toString()), hasServers: builder.hasServers, hasOperationServers: builder.hasOperationServers, + builder, } } @@ -104,7 +106,7 @@ describe("typescript/common/client-servers-builder", () => { switch (url) { case "{schema}://unit-tests-default.{tenant}.example.com": return { - with( + build( schema: "https" | "http" = "https", tenant = "example", ): Server<"UnitTest"> { @@ -219,7 +221,7 @@ describe("typescript/common/client-servers-builder", () => { switch (url) { case "{schema}}://test-operation.{tenant}.example.com": return { - with( + build( schema: "https" | "http" = "https", tenant = "example", ): Server<"testOperation"> { @@ -249,5 +251,33 @@ describe("typescript/common/client-servers-builder", () => { it("hasOperationServers is true", () => { expect(result.hasOperationServers).toBe(true) }) + + it("typeForOperationId returns the correct type when there are variables", () => { + expect( + result.builder.typeForOperationId("testOperation"), + ).toMatchInlineSnapshot(`"Server<"testOperation">"`) + }) + + it("typeForOperationId returns the correct type when there are no variables", () => { + expect( + result.builder.typeForOperationId("anotherTestOperation"), + ).toMatchInlineSnapshot(`"Server<"anotherTestOperation">"`) + }) + + it("defaultForOperationId returns the correct value when there are variables", () => { + expect( + result.builder.defaultForOperationId("testOperation"), + ).toMatchInlineSnapshot( + `"UnitTestServers.operations.testOperation("{schema}}://test-operation.{tenant}.example.com").build()"`, + ) + }) + + it("defaultForOperationId returns the correct value when there are no variables", () => { + expect( + result.builder.defaultForOperationId("anotherTestOperation"), + ).toMatchInlineSnapshot( + `"UnitTestServers.operations.anotherTestOperation("https://another-test-operation.example.com")"`, + ) + }) }) }) diff --git a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts index 805439ac..c5e321eb 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts @@ -73,7 +73,7 @@ export class ClientServersBuilder implements ICompilable { return ${ Object.keys(server.variables).length > 0 ? `{ - with(${this.toParams(server.variables)}): ${typeName} { + build(${this.toParams(server.variables)}): ${typeName} { return (${this.toReplacer(server)} as ${typeName}) } }` @@ -106,6 +106,26 @@ export class ClientServersBuilder implements ICompilable { return `Server<"${this.name}Custom">` } + defaultForOperationId(operationId: string) { + const operation = this.operations.find( + (it) => it.operationId === operationId, + ) + + if (!operation) { + throw new Error(`no operation with id '${operationId}'`) + } + + const defaultServer = operation.servers[0] + + if (!defaultServer) { + throw new Error(`no default server for operation '${operationId}'`) + } + + const hasVariables = Object.keys(defaultServer.variables).length > 0 + + return `${this.classExportName}.operations.${operationId}(${quotedStringLiteral(defaultServer.url)})${hasVariables ? ".build()" : ""}` + } + typeForOperationId(operationId: string) { return `Server<"${operationId}">` } diff --git a/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts b/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts index 212ca48b..b1b4a7f2 100644 --- a/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts +++ b/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts @@ -100,7 +100,8 @@ export class TypescriptFetchClientBuilder extends TypescriptClientBuilder { this.clientServersBuilder.typeForOperationId(operationId), this.clientServersBuilder.typeForCustom(), ), - required: false, + default: + this.clientServersBuilder.defaultForOperationId(operationId), } : undefined, {name: "timeout", type: "number", required: false}, From ae40e95761861989ad467b0d80f40e167edc6a9f Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Fri, 6 Dec 2024 10:08:44 +0000 Subject: [PATCH 14/24] fix: use overloads --- integration-tests-definitions/todo-lists.yaml | 44 ++++ .../api.github.com.yaml/client.service.ts | 52 +++-- .../client.service.ts | 21 +- .../client.service.ts | 22 +- .../generated/okta.idp.yaml/client.service.ts | 23 ++- .../okta.oauth.yaml/client.service.ts | 25 ++- .../petstore-expanded.yaml/client.service.ts | 22 +- .../generated/stripe.yaml/client.service.ts | 73 +++++-- .../todo-lists.yaml/client.service.ts | 177 +++++++++++++++- .../src/generated/todo-lists.yaml/models.ts | 4 + .../generated/api.github.com.yaml/client.ts | 56 +++-- .../client.ts | 21 +- .../azure-resource-manager.tsp/client.ts | 22 +- .../src/generated/okta.idp.yaml/client.ts | 23 ++- .../src/generated/okta.oauth.yaml/client.ts | 23 ++- .../petstore-expanded.yaml/client.ts | 22 +- .../src/generated/stripe.yaml/client.ts | 79 +++++-- .../src/generated/todo-lists.yaml/client.ts | 195 +++++++++++++++++- .../src/generated/todo-lists.yaml/models.ts | 4 + .../generated/api.github.com.yaml/client.ts | 60 ++++-- .../client.ts | 21 +- .../azure-resource-manager.tsp/client.ts | 22 +- .../src/generated/okta.idp.yaml/client.ts | 23 ++- .../src/generated/okta.oauth.yaml/client.ts | 23 ++- .../petstore-expanded.yaml/client.ts | 22 +- .../src/generated/stripe.yaml/client.ts | 89 +++++--- .../src/generated/todo-lists.yaml/client.ts | 178 +++++++++++++++- .../src/generated/todo-lists.yaml/models.ts | 4 + .../src/todo-lists.type-tests.ts | 40 ++++ .../generated/todo-lists.yaml/generated.ts | 103 +++++++++ .../src/generated/todo-lists.yaml/models.ts | 8 + .../src/generated/todo-lists.yaml/schemas.ts | 2 + .../typescript-koa/src/todo-lists.yaml.ts | 2 + .../common/client-servers-builder.spec.ts | 159 +++++++++----- .../common/client-servers-builder.ts | 84 +++++--- 35 files changed, 1408 insertions(+), 340 deletions(-) create mode 100644 integration-tests/typescript-fetch/src/todo-lists.type-tests.ts diff --git a/integration-tests-definitions/todo-lists.yaml b/integration-tests-definitions/todo-lists.yaml index b629c73d..1a77111d 100644 --- a/integration-tests-definitions/todo-lists.yaml +++ b/integration-tests-definitions/todo-lists.yaml @@ -2,6 +2,17 @@ openapi: "3.0.0" info: title: Todo Lists Example API version: '0.0.1' +servers: + - url: '{schema}://{tenant}.todo-lists.example.com' + variables: + schema: + enum: + - http + - https + default: https + tenant: + default: your-slug + - url: 'https://todo-lists.example.com' paths: /list: parameters: @@ -146,6 +157,39 @@ paths: responses: 204: description: success + /attachments: + servers: + - url: '{schema}://{tenant}.attachments.example.com' + variables: + schema: + enum: + - http + - https + default: https + tenant: + default: your-slug + - url: 'https://attachments.example.com' + get: + operationId: listAttachments + responses: + 200: + description: success + content: + application/json: + schema: + type: array + post: + operationId: uploadAttachment + requestBody: + content: + multipart/form-data: + schema: + additionalProperties: false + properties: + file: {} + responses: + 202: + description: accepted components: schemas: CreateUpdateTodoList: diff --git a/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts index 1721e584..8578f503 100644 --- a/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts @@ -320,36 +320,60 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" +export class GitHubV3RestApiServiceServersOperations { + static reposUploadReleaseAsset(url?: "https://uploads.github.com"): { + build: () => Server<"reposUploadReleaseAsset_GitHubV3RestApiService"> + } + static reposUploadReleaseAsset( + url: string = "https://uploads.github.com", + ): unknown { + switch (url) { + case "https://uploads.github.com": + return { + build(): Server<"reposUploadReleaseAsset_GitHubV3RestApiService"> { + return "https://uploads.github.com" as Server<"reposUploadReleaseAsset_GitHubV3RestApiService"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) + } + } +} + export class GitHubV3RestApiServiceServers { static default(): Server<"GitHubV3RestApiService"> { - return "https://api.github.com" as Server<"GitHubV3RestApiService"> + return GitHubV3RestApiServiceServers.server().build() } - static specific(url: "https://api.github.com") { + static server(url?: "https://api.github.com"): { + build: () => Server<"GitHubV3RestApiService"> + } + static server(url: string = "https://api.github.com"): unknown { switch (url) { case "https://api.github.com": - return "https://api.github.com" as Server<"GitHubV3RestApiService"> + return { + build(): Server<"GitHubV3RestApiService"> { + return "https://api.github.com" as Server<"GitHubV3RestApiService"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) } } - static custom(url: string): Server<"GitHubV3RestApiServiceCustom"> { - return url as Server<"GitHubV3RestApiServiceCustom"> - } + static readonly operations = GitHubV3RestApiServiceServersOperations - static readonly operations = { - reposUploadReleaseAsset(url: "https://uploads.github.com") { - switch (url) { - case "https://uploads.github.com": - return "https://uploads.github.com" as Server<"reposUploadReleaseAsset"> - } - }, + static custom(url: string): Server<"custom_GitHubV3RestApiService"> { + return url as Server<"custom_GitHubV3RestApiService"> } } export class GitHubV3RestApiServiceConfig { basePath: | Server<"GitHubV3RestApiService"> - | Server<"GitHubV3RestApiServiceCustom"> = + | Server<"custom_GitHubV3RestApiService"> = GitHubV3RestApiServiceServers.default() defaultHeaders: Record = {} } diff --git a/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts b/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts index 6f759038..25997b46 100644 --- a/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts @@ -25,14 +25,14 @@ import { Injectable } from "@angular/core" import { Observable } from "rxjs" export class ContosoWidgetManagerServiceServers { - static default(endpoint = ""): Server<"ContosoWidgetManagerService"> { - return "{endpoint}/widget".replace( - "{endpoint}", - endpoint, - ) as Server<"ContosoWidgetManagerService"> + static default(): Server<"ContosoWidgetManagerService"> { + return ContosoWidgetManagerServiceServers.server().build() } - static specific(url: "{endpoint}/widget") { + static server(url?: "{endpoint}/widget"): { + build: (endpoint?: string) => Server<"ContosoWidgetManagerService"> + } + static server(url: string = "{endpoint}/widget"): unknown { switch (url) { case "{endpoint}/widget": return { @@ -43,18 +43,21 @@ export class ContosoWidgetManagerServiceServers { ) as Server<"ContosoWidgetManagerService"> }, } + + default: + throw new Error(`no matching server for url '${url}'`) } } - static custom(url: string): Server<"ContosoWidgetManagerServiceCustom"> { - return url as Server<"ContosoWidgetManagerServiceCustom"> + static custom(url: string): Server<"custom_ContosoWidgetManagerService"> { + return url as Server<"custom_ContosoWidgetManagerService"> } } export class ContosoWidgetManagerServiceConfig { basePath: | Server<"ContosoWidgetManagerService"> - | Server<"ContosoWidgetManagerServiceCustom"> = + | Server<"custom_ContosoWidgetManagerService"> = ContosoWidgetManagerServiceServers.default() defaultHeaders: Record = {} } diff --git a/integration-tests/typescript-angular/src/generated/azure-resource-manager.tsp/client.service.ts b/integration-tests/typescript-angular/src/generated/azure-resource-manager.tsp/client.service.ts index a55954a1..e965830b 100644 --- a/integration-tests/typescript-angular/src/generated/azure-resource-manager.tsp/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/azure-resource-manager.tsp/client.service.ts @@ -18,25 +18,35 @@ import { Observable } from "rxjs" export class ContosoProviderHubClientServiceServers { static default(): Server<"ContosoProviderHubClientService"> { - return "https://management.azure.com" as Server<"ContosoProviderHubClientService"> + return ContosoProviderHubClientServiceServers.server().build() } - static specific(url: "https://management.azure.com") { + static server(url?: "https://management.azure.com"): { + build: () => Server<"ContosoProviderHubClientService"> + } + static server(url: string = "https://management.azure.com"): unknown { switch (url) { case "https://management.azure.com": - return "https://management.azure.com" as Server<"ContosoProviderHubClientService"> + return { + build(): Server<"ContosoProviderHubClientService"> { + return "https://management.azure.com" as Server<"ContosoProviderHubClientService"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) } } - static custom(url: string): Server<"ContosoProviderHubClientServiceCustom"> { - return url as Server<"ContosoProviderHubClientServiceCustom"> + static custom(url: string): Server<"custom_ContosoProviderHubClientService"> { + return url as Server<"custom_ContosoProviderHubClientService"> } } export class ContosoProviderHubClientServiceConfig { basePath: | Server<"ContosoProviderHubClientService"> - | Server<"ContosoProviderHubClientServiceCustom"> = + | Server<"custom_ContosoProviderHubClientService"> = ContosoProviderHubClientServiceServers.default() defaultHeaders: Record = {} } diff --git a/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts index fbae0b3f..9ffd8321 100644 --- a/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts @@ -25,16 +25,14 @@ import { Injectable } from "@angular/core" import { Observable } from "rxjs" export class MyAccountManagementServiceServers { - static default( - yourOktaDomain = "subdomain.okta.com", - ): Server<"MyAccountManagementService"> { - return "https://{yourOktaDomain}".replace( - "{yourOktaDomain}", - yourOktaDomain, - ) as Server<"MyAccountManagementService"> + static default(): Server<"MyAccountManagementService"> { + return MyAccountManagementServiceServers.server().build() } - static specific(url: "https://{yourOktaDomain}") { + static server(url?: "https://{yourOktaDomain}"): { + build: (yourOktaDomain?: string) => Server<"MyAccountManagementService"> + } + static server(url: string = "https://{yourOktaDomain}"): unknown { switch (url) { case "https://{yourOktaDomain}": return { @@ -47,18 +45,21 @@ export class MyAccountManagementServiceServers { ) as Server<"MyAccountManagementService"> }, } + + default: + throw new Error(`no matching server for url '${url}'`) } } - static custom(url: string): Server<"MyAccountManagementServiceCustom"> { - return url as Server<"MyAccountManagementServiceCustom"> + static custom(url: string): Server<"custom_MyAccountManagementService"> { + return url as Server<"custom_MyAccountManagementService"> } } export class MyAccountManagementServiceConfig { basePath: | Server<"MyAccountManagementService"> - | Server<"MyAccountManagementServiceCustom"> = + | Server<"custom_MyAccountManagementService"> = MyAccountManagementServiceServers.default() defaultHeaders: Record = {} } diff --git a/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts index e6d7b785..0d2543ae 100644 --- a/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts @@ -39,16 +39,16 @@ import { Injectable } from "@angular/core" import { Observable } from "rxjs" export class OktaOpenIdConnectOAuth20ServiceServers { - static default( - yourOktaDomain = "subdomain.okta.com", - ): Server<"OktaOpenIdConnectOAuth20Service"> { - return "https://{yourOktaDomain}".replace( - "{yourOktaDomain}", - yourOktaDomain, - ) as Server<"OktaOpenIdConnectOAuth20Service"> + static default(): Server<"OktaOpenIdConnectOAuth20Service"> { + return OktaOpenIdConnectOAuth20ServiceServers.server().build() } - static specific(url: "https://{yourOktaDomain}") { + static server(url?: "https://{yourOktaDomain}"): { + build: ( + yourOktaDomain?: string, + ) => Server<"OktaOpenIdConnectOAuth20Service"> + } + static server(url: string = "https://{yourOktaDomain}"): unknown { switch (url) { case "https://{yourOktaDomain}": return { @@ -61,18 +61,21 @@ export class OktaOpenIdConnectOAuth20ServiceServers { ) as Server<"OktaOpenIdConnectOAuth20Service"> }, } + + default: + throw new Error(`no matching server for url '${url}'`) } } - static custom(url: string): Server<"OktaOpenIdConnectOAuth20ServiceCustom"> { - return url as Server<"OktaOpenIdConnectOAuth20ServiceCustom"> + static custom(url: string): Server<"custom_OktaOpenIdConnectOAuth20Service"> { + return url as Server<"custom_OktaOpenIdConnectOAuth20Service"> } } export class OktaOpenIdConnectOAuth20ServiceConfig { basePath: | Server<"OktaOpenIdConnectOAuth20Service"> - | Server<"OktaOpenIdConnectOAuth20ServiceCustom"> = + | Server<"custom_OktaOpenIdConnectOAuth20Service"> = OktaOpenIdConnectOAuth20ServiceServers.default() defaultHeaders: Record = {} } diff --git a/integration-tests/typescript-angular/src/generated/petstore-expanded.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/petstore-expanded.yaml/client.service.ts index 6cd37e16..94c1cb6f 100644 --- a/integration-tests/typescript-angular/src/generated/petstore-expanded.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/petstore-expanded.yaml/client.service.ts @@ -9,25 +9,35 @@ import { Observable } from "rxjs" export class SwaggerPetstoreServiceServers { static default(): Server<"SwaggerPetstoreService"> { - return "https://petstore.swagger.io/v2" as Server<"SwaggerPetstoreService"> + return SwaggerPetstoreServiceServers.server().build() } - static specific(url: "https://petstore.swagger.io/v2") { + static server(url?: "https://petstore.swagger.io/v2"): { + build: () => Server<"SwaggerPetstoreService"> + } + static server(url: string = "https://petstore.swagger.io/v2"): unknown { switch (url) { case "https://petstore.swagger.io/v2": - return "https://petstore.swagger.io/v2" as Server<"SwaggerPetstoreService"> + return { + build(): Server<"SwaggerPetstoreService"> { + return "https://petstore.swagger.io/v2" as Server<"SwaggerPetstoreService"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) } } - static custom(url: string): Server<"SwaggerPetstoreServiceCustom"> { - return url as Server<"SwaggerPetstoreServiceCustom"> + static custom(url: string): Server<"custom_SwaggerPetstoreService"> { + return url as Server<"custom_SwaggerPetstoreService"> } } export class SwaggerPetstoreServiceConfig { basePath: | Server<"SwaggerPetstoreService"> - | Server<"SwaggerPetstoreServiceCustom"> = + | Server<"custom_SwaggerPetstoreService"> = SwaggerPetstoreServiceServers.default() defaultHeaders: Record = {} } diff --git a/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts index 3b9e22be..8667ed68 100644 --- a/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts @@ -166,40 +166,73 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" +export class StripeApiServiceServersOperations { + static postFiles(url?: "https://files.stripe.com/"): { + build: () => Server<"postFiles_StripeApiService"> + } + static postFiles(url: string = "https://files.stripe.com/"): unknown { + switch (url) { + case "https://files.stripe.com/": + return { + build(): Server<"postFiles_StripeApiService"> { + return "https://files.stripe.com/" as Server<"postFiles_StripeApiService"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) + } + } + + static getQuotesQuotePdf(url?: "https://files.stripe.com/"): { + build: () => Server<"getQuotesQuotePdf_StripeApiService"> + } + static getQuotesQuotePdf(url: string = "https://files.stripe.com/"): unknown { + switch (url) { + case "https://files.stripe.com/": + return { + build(): Server<"getQuotesQuotePdf_StripeApiService"> { + return "https://files.stripe.com/" as Server<"getQuotesQuotePdf_StripeApiService"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) + } + } +} + export class StripeApiServiceServers { static default(): Server<"StripeApiService"> { - return "https://api.stripe.com/" as Server<"StripeApiService"> + return StripeApiServiceServers.server().build() } - static specific(url: "https://api.stripe.com/") { + static server(url?: "https://api.stripe.com/"): { + build: () => Server<"StripeApiService"> + } + static server(url: string = "https://api.stripe.com/"): unknown { switch (url) { case "https://api.stripe.com/": - return "https://api.stripe.com/" as Server<"StripeApiService"> + return { + build(): Server<"StripeApiService"> { + return "https://api.stripe.com/" as Server<"StripeApiService"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) } } - static custom(url: string): Server<"StripeApiServiceCustom"> { - return url as Server<"StripeApiServiceCustom"> - } + static readonly operations = StripeApiServiceServersOperations - static readonly operations = { - postFiles(url: "https://files.stripe.com/") { - switch (url) { - case "https://files.stripe.com/": - return "https://files.stripe.com/" as Server<"postFiles"> - } - }, - getQuotesQuotePdf(url: "https://files.stripe.com/") { - switch (url) { - case "https://files.stripe.com/": - return "https://files.stripe.com/" as Server<"getQuotesQuotePdf"> - } - }, + static custom(url: string): Server<"custom_StripeApiService"> { + return url as Server<"custom_StripeApiService"> } } export class StripeApiServiceConfig { - basePath: Server<"StripeApiService"> | Server<"StripeApiServiceCustom"> = + basePath: Server<"StripeApiService"> | Server<"custom_StripeApiService"> = StripeApiServiceServers.default() defaultHeaders: Record = {} } diff --git a/integration-tests/typescript-angular/src/generated/todo-lists.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/todo-lists.yaml/client.service.ts index 6681ae93..55fac03d 100644 --- a/integration-tests/typescript-angular/src/generated/todo-lists.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/todo-lists.yaml/client.service.ts @@ -7,13 +7,153 @@ import { t_Error, t_Statuses, t_TodoList, + t_UnknownObject, } from "./models" import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { Observable } from "rxjs" +export class TodoListsExampleApiServiceServersOperations { + static listAttachments(url?: "{schema}://{tenant}.attachments.example.com"): { + build: ( + schema?: "http" | "https", + tenant?: string, + ) => Server<"listAttachments_TodoListsExampleApiService"> + } + static listAttachments(url?: "https://attachments.example.com"): { + build: () => Server<"listAttachments_TodoListsExampleApiService"> + } + static listAttachments( + url: string = "{schema}://{tenant}.attachments.example.com", + ): unknown { + switch (url) { + case "{schema}://{tenant}.attachments.example.com": + return { + build( + schema: "http" | "https" = "https", + tenant = "your-slug", + ): Server<"listAttachments_TodoListsExampleApiService"> { + return "{schema}://{tenant}.attachments.example.com" + .replace("{schema}", schema) + .replace( + "{tenant}", + tenant, + ) as Server<"listAttachments_TodoListsExampleApiService"> + }, + } + + case "https://attachments.example.com": + return { + build(): Server<"listAttachments_TodoListsExampleApiService"> { + return "https://attachments.example.com" as Server<"listAttachments_TodoListsExampleApiService"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) + } + } + + static uploadAttachment( + url?: "{schema}://{tenant}.attachments.example.com", + ): { + build: ( + schema?: "http" | "https", + tenant?: string, + ) => Server<"uploadAttachment_TodoListsExampleApiService"> + } + static uploadAttachment(url?: "https://attachments.example.com"): { + build: () => Server<"uploadAttachment_TodoListsExampleApiService"> + } + static uploadAttachment( + url: string = "{schema}://{tenant}.attachments.example.com", + ): unknown { + switch (url) { + case "{schema}://{tenant}.attachments.example.com": + return { + build( + schema: "http" | "https" = "https", + tenant = "your-slug", + ): Server<"uploadAttachment_TodoListsExampleApiService"> { + return "{schema}://{tenant}.attachments.example.com" + .replace("{schema}", schema) + .replace( + "{tenant}", + tenant, + ) as Server<"uploadAttachment_TodoListsExampleApiService"> + }, + } + + case "https://attachments.example.com": + return { + build(): Server<"uploadAttachment_TodoListsExampleApiService"> { + return "https://attachments.example.com" as Server<"uploadAttachment_TodoListsExampleApiService"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) + } + } +} + +export class TodoListsExampleApiServiceServers { + static default(): Server<"TodoListsExampleApiService"> { + return TodoListsExampleApiServiceServers.server().build() + } + + static server(url?: "{schema}://{tenant}.todo-lists.example.com"): { + build: ( + schema?: "http" | "https", + tenant?: string, + ) => Server<"TodoListsExampleApiService"> + } + static server(url?: "https://todo-lists.example.com"): { + build: () => Server<"TodoListsExampleApiService"> + } + static server( + url: string = "{schema}://{tenant}.todo-lists.example.com", + ): unknown { + switch (url) { + case "{schema}://{tenant}.todo-lists.example.com": + return { + build( + schema: "http" | "https" = "https", + tenant = "your-slug", + ): Server<"TodoListsExampleApiService"> { + return "{schema}://{tenant}.todo-lists.example.com" + .replace("{schema}", schema) + .replace( + "{tenant}", + tenant, + ) as Server<"TodoListsExampleApiService"> + }, + } + + case "https://todo-lists.example.com": + return { + build(): Server<"TodoListsExampleApiService"> { + return "https://todo-lists.example.com" as Server<"TodoListsExampleApiService"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) + } + } + + static readonly operations = TodoListsExampleApiServiceServersOperations + + static custom(url: string): Server<"custom_TodoListsExampleApiService"> { + return url as Server<"custom_TodoListsExampleApiService"> + } +} + export class TodoListsExampleApiServiceConfig { - basePath: string = "" + basePath: + | Server<"TodoListsExampleApiService"> + | Server<"custom_TodoListsExampleApiService"> = + TodoListsExampleApiServiceServers.default() defaultHeaders: Record = {} } @@ -224,6 +364,41 @@ export class TodoListsExampleApiService { }, ) } + + listAttachments(): Observable< + (HttpResponse & { status: 200 }) | HttpResponse + > { + return this.httpClient.request( + "GET", + this.config.basePath + `/attachments`, + { + observe: "response", + reportProgress: false, + }, + ) + } + + uploadAttachment(p: { + requestBody: { + file?: unknown + } + }): Observable< + (HttpResponse & { status: 202 }) | HttpResponse + > { + const headers = this._headers({ "Content-Type": "multipart/form-data" }) + const body = p["requestBody"] + + return this.httpClient.request( + "POST", + this.config.basePath + `/attachments`, + { + headers, + body, + observe: "response", + reportProgress: false, + }, + ) + } } export { TodoListsExampleApiService as ApiClient } diff --git a/integration-tests/typescript-angular/src/generated/todo-lists.yaml/models.ts b/integration-tests/typescript-angular/src/generated/todo-lists.yaml/models.ts index ba480145..46c34696 100644 --- a/integration-tests/typescript-angular/src/generated/todo-lists.yaml/models.ts +++ b/integration-tests/typescript-angular/src/generated/todo-lists.yaml/models.ts @@ -21,3 +21,7 @@ export type t_TodoList = { totalItemCount: number updated: string } + +export type t_UnknownObject = { + [key: string]: unknown | undefined +} diff --git a/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts b/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts index b481f1e6..2191cf31 100644 --- a/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts @@ -318,34 +318,58 @@ import { } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" +export class GitHubV3RestApiServersOperations { + static reposUploadReleaseAsset(url?: "https://uploads.github.com"): { + build: () => Server<"reposUploadReleaseAsset_GitHubV3RestApi"> + } + static reposUploadReleaseAsset( + url: string = "https://uploads.github.com", + ): unknown { + switch (url) { + case "https://uploads.github.com": + return { + build(): Server<"reposUploadReleaseAsset_GitHubV3RestApi"> { + return "https://uploads.github.com" as Server<"reposUploadReleaseAsset_GitHubV3RestApi"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) + } + } +} + export class GitHubV3RestApiServers { static default(): Server<"GitHubV3RestApi"> { - return "https://api.github.com" as Server<"GitHubV3RestApi"> + return GitHubV3RestApiServers.server().build() } - static specific(url: "https://api.github.com") { + static server(url?: "https://api.github.com"): { + build: () => Server<"GitHubV3RestApi"> + } + static server(url: string = "https://api.github.com"): unknown { switch (url) { case "https://api.github.com": - return "https://api.github.com" as Server<"GitHubV3RestApi"> + return { + build(): Server<"GitHubV3RestApi"> { + return "https://api.github.com" as Server<"GitHubV3RestApi"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) } } - static custom(url: string): Server<"GitHubV3RestApiCustom"> { - return url as Server<"GitHubV3RestApiCustom"> - } + static readonly operations = GitHubV3RestApiServersOperations - static readonly operations = { - reposUploadReleaseAsset(url: "https://uploads.github.com") { - switch (url) { - case "https://uploads.github.com": - return "https://uploads.github.com" as Server<"reposUploadReleaseAsset"> - } - }, + static custom(url: string): Server<"custom_GitHubV3RestApi"> { + return url as Server<"custom_GitHubV3RestApi"> } } export interface GitHubV3RestApiConfig extends AbstractAxiosConfig { - basePath: Server<"GitHubV3RestApi"> | Server<"GitHubV3RestApiCustom"> + basePath: Server<"GitHubV3RestApi"> | Server<"custom_GitHubV3RestApi"> } export class GitHubV3RestApi extends AbstractAxiosClient { @@ -20171,8 +20195,8 @@ export class GitHubV3RestApi extends AbstractAxiosClient { requestBody?: string }, basePath?: - | Server<"reposUploadReleaseAsset"> - | Server<"GitHubV3RestApiCustom">, + | Server<"reposUploadReleaseAsset_GitHubV3RestApi"> + | Server<"custom_GitHubV3RestApi">, timeout?: number, opts: AxiosRequestConfig = {}, ): Promise> { diff --git a/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts b/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts index a00ae62b..fe5ea1d1 100644 --- a/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts +++ b/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts @@ -27,14 +27,14 @@ import { import { AxiosRequestConfig, AxiosResponse } from "axios" export class ContosoWidgetManagerServers { - static default(endpoint = ""): Server<"ContosoWidgetManager"> { - return "{endpoint}/widget".replace( - "{endpoint}", - endpoint, - ) as Server<"ContosoWidgetManager"> + static default(): Server<"ContosoWidgetManager"> { + return ContosoWidgetManagerServers.server().build() } - static specific(url: "{endpoint}/widget") { + static server(url?: "{endpoint}/widget"): { + build: (endpoint?: string) => Server<"ContosoWidgetManager"> + } + static server(url: string = "{endpoint}/widget"): unknown { switch (url) { case "{endpoint}/widget": return { @@ -45,18 +45,21 @@ export class ContosoWidgetManagerServers { ) as Server<"ContosoWidgetManager"> }, } + + default: + throw new Error(`no matching server for url '${url}'`) } } - static custom(url: string): Server<"ContosoWidgetManagerCustom"> { - return url as Server<"ContosoWidgetManagerCustom"> + static custom(url: string): Server<"custom_ContosoWidgetManager"> { + return url as Server<"custom_ContosoWidgetManager"> } } export interface ContosoWidgetManagerConfig extends AbstractAxiosConfig { basePath: | Server<"ContosoWidgetManager"> - | Server<"ContosoWidgetManagerCustom"> + | Server<"custom_ContosoWidgetManager"> } export class ContosoWidgetManager extends AbstractAxiosClient { diff --git a/integration-tests/typescript-axios/src/generated/azure-resource-manager.tsp/client.ts b/integration-tests/typescript-axios/src/generated/azure-resource-manager.tsp/client.ts index c5566fcd..8b802fd0 100644 --- a/integration-tests/typescript-axios/src/generated/azure-resource-manager.tsp/client.ts +++ b/integration-tests/typescript-axios/src/generated/azure-resource-manager.tsp/client.ts @@ -20,25 +20,35 @@ import { AxiosRequestConfig, AxiosResponse } from "axios" export class ContosoProviderHubClientServers { static default(): Server<"ContosoProviderHubClient"> { - return "https://management.azure.com" as Server<"ContosoProviderHubClient"> + return ContosoProviderHubClientServers.server().build() } - static specific(url: "https://management.azure.com") { + static server(url?: "https://management.azure.com"): { + build: () => Server<"ContosoProviderHubClient"> + } + static server(url: string = "https://management.azure.com"): unknown { switch (url) { case "https://management.azure.com": - return "https://management.azure.com" as Server<"ContosoProviderHubClient"> + return { + build(): Server<"ContosoProviderHubClient"> { + return "https://management.azure.com" as Server<"ContosoProviderHubClient"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) } } - static custom(url: string): Server<"ContosoProviderHubClientCustom"> { - return url as Server<"ContosoProviderHubClientCustom"> + static custom(url: string): Server<"custom_ContosoProviderHubClient"> { + return url as Server<"custom_ContosoProviderHubClient"> } } export interface ContosoProviderHubClientConfig extends AbstractAxiosConfig { basePath: | Server<"ContosoProviderHubClient"> - | Server<"ContosoProviderHubClientCustom"> + | Server<"custom_ContosoProviderHubClient"> } export class ContosoProviderHubClient extends AbstractAxiosClient { diff --git a/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts b/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts index 80872c3d..37056129 100644 --- a/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts @@ -27,16 +27,14 @@ import { import { AxiosRequestConfig, AxiosResponse } from "axios" export class MyAccountManagementServers { - static default( - yourOktaDomain = "subdomain.okta.com", - ): Server<"MyAccountManagement"> { - return "https://{yourOktaDomain}".replace( - "{yourOktaDomain}", - yourOktaDomain, - ) as Server<"MyAccountManagement"> + static default(): Server<"MyAccountManagement"> { + return MyAccountManagementServers.server().build() } - static specific(url: "https://{yourOktaDomain}") { + static server(url?: "https://{yourOktaDomain}"): { + build: (yourOktaDomain?: string) => Server<"MyAccountManagement"> + } + static server(url: string = "https://{yourOktaDomain}"): unknown { switch (url) { case "https://{yourOktaDomain}": return { @@ -49,16 +47,19 @@ export class MyAccountManagementServers { ) as Server<"MyAccountManagement"> }, } + + default: + throw new Error(`no matching server for url '${url}'`) } } - static custom(url: string): Server<"MyAccountManagementCustom"> { - return url as Server<"MyAccountManagementCustom"> + static custom(url: string): Server<"custom_MyAccountManagement"> { + return url as Server<"custom_MyAccountManagement"> } } export interface MyAccountManagementConfig extends AbstractAxiosConfig { - basePath: Server<"MyAccountManagement"> | Server<"MyAccountManagementCustom"> + basePath: Server<"MyAccountManagement"> | Server<"custom_MyAccountManagement"> } export class MyAccountManagement extends AbstractAxiosClient { diff --git a/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts b/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts index 2147361e..850d6aca 100644 --- a/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts @@ -40,16 +40,14 @@ import { import { AxiosRequestConfig, AxiosResponse } from "axios" export class OktaOpenIdConnectOAuth20Servers { - static default( - yourOktaDomain = "subdomain.okta.com", - ): Server<"OktaOpenIdConnectOAuth20"> { - return "https://{yourOktaDomain}".replace( - "{yourOktaDomain}", - yourOktaDomain, - ) as Server<"OktaOpenIdConnectOAuth20"> + static default(): Server<"OktaOpenIdConnectOAuth20"> { + return OktaOpenIdConnectOAuth20Servers.server().build() } - static specific(url: "https://{yourOktaDomain}") { + static server(url?: "https://{yourOktaDomain}"): { + build: (yourOktaDomain?: string) => Server<"OktaOpenIdConnectOAuth20"> + } + static server(url: string = "https://{yourOktaDomain}"): unknown { switch (url) { case "https://{yourOktaDomain}": return { @@ -62,18 +60,21 @@ export class OktaOpenIdConnectOAuth20Servers { ) as Server<"OktaOpenIdConnectOAuth20"> }, } + + default: + throw new Error(`no matching server for url '${url}'`) } } - static custom(url: string): Server<"OktaOpenIdConnectOAuth20Custom"> { - return url as Server<"OktaOpenIdConnectOAuth20Custom"> + static custom(url: string): Server<"custom_OktaOpenIdConnectOAuth20"> { + return url as Server<"custom_OktaOpenIdConnectOAuth20"> } } export interface OktaOpenIdConnectOAuth20Config extends AbstractAxiosConfig { basePath: | Server<"OktaOpenIdConnectOAuth20"> - | Server<"OktaOpenIdConnectOAuth20Custom"> + | Server<"custom_OktaOpenIdConnectOAuth20"> } export class OktaOpenIdConnectOAuth20 extends AbstractAxiosClient { diff --git a/integration-tests/typescript-axios/src/generated/petstore-expanded.yaml/client.ts b/integration-tests/typescript-axios/src/generated/petstore-expanded.yaml/client.ts index f3f40bce..5e8ebdee 100644 --- a/integration-tests/typescript-axios/src/generated/petstore-expanded.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/petstore-expanded.yaml/client.ts @@ -12,23 +12,33 @@ import { AxiosRequestConfig, AxiosResponse } from "axios" export class SwaggerPetstoreServers { static default(): Server<"SwaggerPetstore"> { - return "https://petstore.swagger.io/v2" as Server<"SwaggerPetstore"> + return SwaggerPetstoreServers.server().build() } - static specific(url: "https://petstore.swagger.io/v2") { + static server(url?: "https://petstore.swagger.io/v2"): { + build: () => Server<"SwaggerPetstore"> + } + static server(url: string = "https://petstore.swagger.io/v2"): unknown { switch (url) { case "https://petstore.swagger.io/v2": - return "https://petstore.swagger.io/v2" as Server<"SwaggerPetstore"> + return { + build(): Server<"SwaggerPetstore"> { + return "https://petstore.swagger.io/v2" as Server<"SwaggerPetstore"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) } } - static custom(url: string): Server<"SwaggerPetstoreCustom"> { - return url as Server<"SwaggerPetstoreCustom"> + static custom(url: string): Server<"custom_SwaggerPetstore"> { + return url as Server<"custom_SwaggerPetstore"> } } export interface SwaggerPetstoreConfig extends AbstractAxiosConfig { - basePath: Server<"SwaggerPetstore"> | Server<"SwaggerPetstoreCustom"> + basePath: Server<"SwaggerPetstore"> | Server<"custom_SwaggerPetstore"> } export class SwaggerPetstore extends AbstractAxiosClient { diff --git a/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts b/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts index 7bb951f0..b73c3b5c 100644 --- a/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts @@ -168,40 +168,73 @@ import { } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" +export class StripeApiServersOperations { + static postFiles(url?: "https://files.stripe.com/"): { + build: () => Server<"postFiles_StripeApi"> + } + static postFiles(url: string = "https://files.stripe.com/"): unknown { + switch (url) { + case "https://files.stripe.com/": + return { + build(): Server<"postFiles_StripeApi"> { + return "https://files.stripe.com/" as Server<"postFiles_StripeApi"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) + } + } + + static getQuotesQuotePdf(url?: "https://files.stripe.com/"): { + build: () => Server<"getQuotesQuotePdf_StripeApi"> + } + static getQuotesQuotePdf(url: string = "https://files.stripe.com/"): unknown { + switch (url) { + case "https://files.stripe.com/": + return { + build(): Server<"getQuotesQuotePdf_StripeApi"> { + return "https://files.stripe.com/" as Server<"getQuotesQuotePdf_StripeApi"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) + } + } +} + export class StripeApiServers { static default(): Server<"StripeApi"> { - return "https://api.stripe.com/" as Server<"StripeApi"> + return StripeApiServers.server().build() } - static specific(url: "https://api.stripe.com/") { + static server(url?: "https://api.stripe.com/"): { + build: () => Server<"StripeApi"> + } + static server(url: string = "https://api.stripe.com/"): unknown { switch (url) { case "https://api.stripe.com/": - return "https://api.stripe.com/" as Server<"StripeApi"> + return { + build(): Server<"StripeApi"> { + return "https://api.stripe.com/" as Server<"StripeApi"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) } } - static custom(url: string): Server<"StripeApiCustom"> { - return url as Server<"StripeApiCustom"> - } + static readonly operations = StripeApiServersOperations - static readonly operations = { - postFiles(url: "https://files.stripe.com/") { - switch (url) { - case "https://files.stripe.com/": - return "https://files.stripe.com/" as Server<"postFiles"> - } - }, - getQuotesQuotePdf(url: "https://files.stripe.com/") { - switch (url) { - case "https://files.stripe.com/": - return "https://files.stripe.com/" as Server<"getQuotesQuotePdf"> - } - }, + static custom(url: string): Server<"custom_StripeApi"> { + return url as Server<"custom_StripeApi"> } } export interface StripeApiConfig extends AbstractAxiosConfig { - basePath: Server<"StripeApi"> | Server<"StripeApiCustom"> + basePath: Server<"StripeApi"> | Server<"custom_StripeApi"> } export class StripeApi extends AbstractAxiosClient { @@ -10724,7 +10757,7 @@ export class StripeApi extends AbstractAxiosClient { | "terminal_reader_splashscreen" } }, - basePath?: Server<"postFiles"> | Server<"StripeApiCustom">, + basePath?: Server<"postFiles_StripeApi"> | Server<"custom_StripeApi">, timeout?: number, opts: AxiosRequestConfig = {}, ): Promise> { @@ -26364,7 +26397,9 @@ export class StripeApi extends AbstractAxiosClient { quote: string requestBody?: EmptyObject }, - basePath?: Server<"getQuotesQuotePdf"> | Server<"StripeApiCustom">, + basePath?: + | Server<"getQuotesQuotePdf_StripeApi"> + | Server<"custom_StripeApi">, timeout?: number, opts: AxiosRequestConfig = {}, ): Promise> { diff --git a/integration-tests/typescript-axios/src/generated/todo-lists.yaml/client.ts b/integration-tests/typescript-axios/src/generated/todo-lists.yaml/client.ts index f2332ee6..4719c9b9 100644 --- a/integration-tests/typescript-axios/src/generated/todo-lists.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/todo-lists.yaml/client.ts @@ -2,14 +2,155 @@ /* tslint:disable */ /* eslint-disable */ -import { t_CreateUpdateTodoList, t_Statuses, t_TodoList } from "./models" +import { + t_CreateUpdateTodoList, + t_Statuses, + t_TodoList, + t_UnknownObject, +} from "./models" import { AbstractAxiosClient, AbstractAxiosConfig, + Server, } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" -export interface TodoListsExampleApiConfig extends AbstractAxiosConfig {} +export class TodoListsExampleApiServersOperations { + static listAttachments(url?: "{schema}://{tenant}.attachments.example.com"): { + build: ( + schema?: "http" | "https", + tenant?: string, + ) => Server<"listAttachments_TodoListsExampleApi"> + } + static listAttachments(url?: "https://attachments.example.com"): { + build: () => Server<"listAttachments_TodoListsExampleApi"> + } + static listAttachments( + url: string = "{schema}://{tenant}.attachments.example.com", + ): unknown { + switch (url) { + case "{schema}://{tenant}.attachments.example.com": + return { + build( + schema: "http" | "https" = "https", + tenant = "your-slug", + ): Server<"listAttachments_TodoListsExampleApi"> { + return "{schema}://{tenant}.attachments.example.com" + .replace("{schema}", schema) + .replace( + "{tenant}", + tenant, + ) as Server<"listAttachments_TodoListsExampleApi"> + }, + } + + case "https://attachments.example.com": + return { + build(): Server<"listAttachments_TodoListsExampleApi"> { + return "https://attachments.example.com" as Server<"listAttachments_TodoListsExampleApi"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) + } + } + + static uploadAttachment( + url?: "{schema}://{tenant}.attachments.example.com", + ): { + build: ( + schema?: "http" | "https", + tenant?: string, + ) => Server<"uploadAttachment_TodoListsExampleApi"> + } + static uploadAttachment(url?: "https://attachments.example.com"): { + build: () => Server<"uploadAttachment_TodoListsExampleApi"> + } + static uploadAttachment( + url: string = "{schema}://{tenant}.attachments.example.com", + ): unknown { + switch (url) { + case "{schema}://{tenant}.attachments.example.com": + return { + build( + schema: "http" | "https" = "https", + tenant = "your-slug", + ): Server<"uploadAttachment_TodoListsExampleApi"> { + return "{schema}://{tenant}.attachments.example.com" + .replace("{schema}", schema) + .replace( + "{tenant}", + tenant, + ) as Server<"uploadAttachment_TodoListsExampleApi"> + }, + } + + case "https://attachments.example.com": + return { + build(): Server<"uploadAttachment_TodoListsExampleApi"> { + return "https://attachments.example.com" as Server<"uploadAttachment_TodoListsExampleApi"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) + } + } +} + +export class TodoListsExampleApiServers { + static default(): Server<"TodoListsExampleApi"> { + return TodoListsExampleApiServers.server().build() + } + + static server(url?: "{schema}://{tenant}.todo-lists.example.com"): { + build: ( + schema?: "http" | "https", + tenant?: string, + ) => Server<"TodoListsExampleApi"> + } + static server(url?: "https://todo-lists.example.com"): { + build: () => Server<"TodoListsExampleApi"> + } + static server( + url: string = "{schema}://{tenant}.todo-lists.example.com", + ): unknown { + switch (url) { + case "{schema}://{tenant}.todo-lists.example.com": + return { + build( + schema: "http" | "https" = "https", + tenant = "your-slug", + ): Server<"TodoListsExampleApi"> { + return "{schema}://{tenant}.todo-lists.example.com" + .replace("{schema}", schema) + .replace("{tenant}", tenant) as Server<"TodoListsExampleApi"> + }, + } + + case "https://todo-lists.example.com": + return { + build(): Server<"TodoListsExampleApi"> { + return "https://todo-lists.example.com" as Server<"TodoListsExampleApi"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) + } + } + + static readonly operations = TodoListsExampleApiServersOperations + + static custom(url: string): Server<"custom_TodoListsExampleApi"> { + return url as Server<"custom_TodoListsExampleApi"> + } +} + +export interface TodoListsExampleApiConfig extends AbstractAxiosConfig { + basePath: Server<"TodoListsExampleApi"> | Server<"custom_TodoListsExampleApi"> +} export class TodoListsExampleApi extends AbstractAxiosClient { constructor(config: TodoListsExampleApiConfig) { @@ -159,6 +300,56 @@ export class TodoListsExampleApi extends AbstractAxiosClient { headers, }) } + + async listAttachments( + basePath?: + | Server<"listAttachments_TodoListsExampleApi"> + | Server<"custom_TodoListsExampleApi">, + timeout?: number, + opts: AxiosRequestConfig = {}, + ): Promise> { + const url = `/attachments` + const headers = this._headers({}, opts.headers) + + return this._request({ + url: url, + method: "GET", + ...(basePath ? { baseURL: basePath } : {}), + ...(timeout ? { timeout } : {}), + ...opts, + headers, + }) + } + + async uploadAttachment( + p: { + requestBody: { + file?: unknown + } + }, + basePath?: + | Server<"uploadAttachment_TodoListsExampleApi"> + | Server<"custom_TodoListsExampleApi">, + timeout?: number, + opts: AxiosRequestConfig = {}, + ): Promise> { + const url = `/attachments` + const headers = this._headers( + { "Content-Type": "multipart/form-data" }, + opts.headers, + ) + const body = JSON.stringify(p.requestBody) + + return this._request({ + url: url, + method: "POST", + data: body, + ...(basePath ? { baseURL: basePath } : {}), + ...(timeout ? { timeout } : {}), + ...opts, + headers, + }) + } } export { TodoListsExampleApi as ApiClient } diff --git a/integration-tests/typescript-axios/src/generated/todo-lists.yaml/models.ts b/integration-tests/typescript-axios/src/generated/todo-lists.yaml/models.ts index ba480145..46c34696 100644 --- a/integration-tests/typescript-axios/src/generated/todo-lists.yaml/models.ts +++ b/integration-tests/typescript-axios/src/generated/todo-lists.yaml/models.ts @@ -21,3 +21,7 @@ export type t_TodoList = { totalItemCount: number updated: string } + +export type t_UnknownObject = { + [key: string]: unknown | undefined +} diff --git a/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts index d2dc22aa..9077697f 100644 --- a/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts @@ -324,34 +324,58 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" +export class GitHubV3RestApiServersOperations { + static reposUploadReleaseAsset(url?: "https://uploads.github.com"): { + build: () => Server<"reposUploadReleaseAsset_GitHubV3RestApi"> + } + static reposUploadReleaseAsset( + url: string = "https://uploads.github.com", + ): unknown { + switch (url) { + case "https://uploads.github.com": + return { + build(): Server<"reposUploadReleaseAsset_GitHubV3RestApi"> { + return "https://uploads.github.com" as Server<"reposUploadReleaseAsset_GitHubV3RestApi"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) + } + } +} + export class GitHubV3RestApiServers { static default(): Server<"GitHubV3RestApi"> { - return "https://api.github.com" as Server<"GitHubV3RestApi"> + return GitHubV3RestApiServers.server().build() } - static specific(url: "https://api.github.com") { + static server(url?: "https://api.github.com"): { + build: () => Server<"GitHubV3RestApi"> + } + static server(url: string = "https://api.github.com"): unknown { switch (url) { case "https://api.github.com": - return "https://api.github.com" as Server<"GitHubV3RestApi"> + return { + build(): Server<"GitHubV3RestApi"> { + return "https://api.github.com" as Server<"GitHubV3RestApi"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) } } - static custom(url: string): Server<"GitHubV3RestApiCustom"> { - return url as Server<"GitHubV3RestApiCustom"> - } + static readonly operations = GitHubV3RestApiServersOperations - static readonly operations = { - reposUploadReleaseAsset(url: "https://uploads.github.com") { - switch (url) { - case "https://uploads.github.com": - return "https://uploads.github.com" as Server<"reposUploadReleaseAsset"> - } - }, + static custom(url: string): Server<"custom_GitHubV3RestApi"> { + return url as Server<"custom_GitHubV3RestApi"> } } export interface GitHubV3RestApiConfig extends AbstractFetchClientConfig { - basePath: Server<"GitHubV3RestApi"> | Server<"GitHubV3RestApiCustom"> + basePath: Server<"GitHubV3RestApi"> | Server<"custom_GitHubV3RestApi"> } export class GitHubV3RestApi extends AbstractFetchClient { @@ -20351,10 +20375,10 @@ export class GitHubV3RestApi extends AbstractFetchClient { requestBody?: string }, basePath: - | Server<"reposUploadReleaseAsset"> - | Server<"GitHubV3RestApiCustom"> = GitHubV3RestApiServers.operations.reposUploadReleaseAsset( - "https://uploads.github.com", - ), + | Server<"reposUploadReleaseAsset_GitHubV3RestApi"> + | Server<"custom_GitHubV3RestApi"> = GitHubV3RestApiServers.operations + .reposUploadReleaseAsset() + .build(), timeout?: number, opts: RequestInit = {}, ): Promise | Res<422, void>>> { diff --git a/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts b/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts index d65bef75..43033e64 100644 --- a/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts +++ b/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts @@ -30,14 +30,14 @@ import { } from "@nahkies/typescript-fetch-runtime/main" export class ContosoWidgetManagerServers { - static default(endpoint = ""): Server<"ContosoWidgetManager"> { - return "{endpoint}/widget".replace( - "{endpoint}", - endpoint, - ) as Server<"ContosoWidgetManager"> + static default(): Server<"ContosoWidgetManager"> { + return ContosoWidgetManagerServers.server().build() } - static specific(url: "{endpoint}/widget") { + static server(url?: "{endpoint}/widget"): { + build: (endpoint?: string) => Server<"ContosoWidgetManager"> + } + static server(url: string = "{endpoint}/widget"): unknown { switch (url) { case "{endpoint}/widget": return { @@ -48,18 +48,21 @@ export class ContosoWidgetManagerServers { ) as Server<"ContosoWidgetManager"> }, } + + default: + throw new Error(`no matching server for url '${url}'`) } } - static custom(url: string): Server<"ContosoWidgetManagerCustom"> { - return url as Server<"ContosoWidgetManagerCustom"> + static custom(url: string): Server<"custom_ContosoWidgetManager"> { + return url as Server<"custom_ContosoWidgetManager"> } } export interface ContosoWidgetManagerConfig extends AbstractFetchClientConfig { basePath: | Server<"ContosoWidgetManager"> - | Server<"ContosoWidgetManagerCustom"> + | Server<"custom_ContosoWidgetManager"> } export class ContosoWidgetManager extends AbstractFetchClient { diff --git a/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts b/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts index 5bbcbc5f..6a76b451 100644 --- a/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts +++ b/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts @@ -23,18 +23,28 @@ import { export class ContosoProviderHubClientServers { static default(): Server<"ContosoProviderHubClient"> { - return "https://management.azure.com" as Server<"ContosoProviderHubClient"> + return ContosoProviderHubClientServers.server().build() } - static specific(url: "https://management.azure.com") { + static server(url?: "https://management.azure.com"): { + build: () => Server<"ContosoProviderHubClient"> + } + static server(url: string = "https://management.azure.com"): unknown { switch (url) { case "https://management.azure.com": - return "https://management.azure.com" as Server<"ContosoProviderHubClient"> + return { + build(): Server<"ContosoProviderHubClient"> { + return "https://management.azure.com" as Server<"ContosoProviderHubClient"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) } } - static custom(url: string): Server<"ContosoProviderHubClientCustom"> { - return url as Server<"ContosoProviderHubClientCustom"> + static custom(url: string): Server<"custom_ContosoProviderHubClient"> { + return url as Server<"custom_ContosoProviderHubClient"> } } @@ -42,7 +52,7 @@ export interface ContosoProviderHubClientConfig extends AbstractFetchClientConfig { basePath: | Server<"ContosoProviderHubClient"> - | Server<"ContosoProviderHubClientCustom"> + | Server<"custom_ContosoProviderHubClient"> } export class ContosoProviderHubClient extends AbstractFetchClient { diff --git a/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts index 901ab6b5..fb97973a 100644 --- a/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts @@ -29,16 +29,14 @@ import { } from "@nahkies/typescript-fetch-runtime/main" export class MyAccountManagementServers { - static default( - yourOktaDomain = "subdomain.okta.com", - ): Server<"MyAccountManagement"> { - return "https://{yourOktaDomain}".replace( - "{yourOktaDomain}", - yourOktaDomain, - ) as Server<"MyAccountManagement"> + static default(): Server<"MyAccountManagement"> { + return MyAccountManagementServers.server().build() } - static specific(url: "https://{yourOktaDomain}") { + static server(url?: "https://{yourOktaDomain}"): { + build: (yourOktaDomain?: string) => Server<"MyAccountManagement"> + } + static server(url: string = "https://{yourOktaDomain}"): unknown { switch (url) { case "https://{yourOktaDomain}": return { @@ -51,16 +49,19 @@ export class MyAccountManagementServers { ) as Server<"MyAccountManagement"> }, } + + default: + throw new Error(`no matching server for url '${url}'`) } } - static custom(url: string): Server<"MyAccountManagementCustom"> { - return url as Server<"MyAccountManagementCustom"> + static custom(url: string): Server<"custom_MyAccountManagement"> { + return url as Server<"custom_MyAccountManagement"> } } export interface MyAccountManagementConfig extends AbstractFetchClientConfig { - basePath: Server<"MyAccountManagement"> | Server<"MyAccountManagementCustom"> + basePath: Server<"MyAccountManagement"> | Server<"custom_MyAccountManagement"> } export class MyAccountManagement extends AbstractFetchClient { diff --git a/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts index 9e7f3312..228a2d7e 100644 --- a/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts @@ -43,16 +43,14 @@ import { } from "@nahkies/typescript-fetch-runtime/main" export class OktaOpenIdConnectOAuth20Servers { - static default( - yourOktaDomain = "subdomain.okta.com", - ): Server<"OktaOpenIdConnectOAuth20"> { - return "https://{yourOktaDomain}".replace( - "{yourOktaDomain}", - yourOktaDomain, - ) as Server<"OktaOpenIdConnectOAuth20"> + static default(): Server<"OktaOpenIdConnectOAuth20"> { + return OktaOpenIdConnectOAuth20Servers.server().build() } - static specific(url: "https://{yourOktaDomain}") { + static server(url?: "https://{yourOktaDomain}"): { + build: (yourOktaDomain?: string) => Server<"OktaOpenIdConnectOAuth20"> + } + static server(url: string = "https://{yourOktaDomain}"): unknown { switch (url) { case "https://{yourOktaDomain}": return { @@ -65,11 +63,14 @@ export class OktaOpenIdConnectOAuth20Servers { ) as Server<"OktaOpenIdConnectOAuth20"> }, } + + default: + throw new Error(`no matching server for url '${url}'`) } } - static custom(url: string): Server<"OktaOpenIdConnectOAuth20Custom"> { - return url as Server<"OktaOpenIdConnectOAuth20Custom"> + static custom(url: string): Server<"custom_OktaOpenIdConnectOAuth20"> { + return url as Server<"custom_OktaOpenIdConnectOAuth20"> } } @@ -77,7 +78,7 @@ export interface OktaOpenIdConnectOAuth20Config extends AbstractFetchClientConfig { basePath: | Server<"OktaOpenIdConnectOAuth20"> - | Server<"OktaOpenIdConnectOAuth20Custom"> + | Server<"custom_OktaOpenIdConnectOAuth20"> } export class OktaOpenIdConnectOAuth20 extends AbstractFetchClient { diff --git a/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts index 72f309f5..0c364a77 100644 --- a/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts @@ -14,23 +14,33 @@ import { export class SwaggerPetstoreServers { static default(): Server<"SwaggerPetstore"> { - return "https://petstore.swagger.io/v2" as Server<"SwaggerPetstore"> + return SwaggerPetstoreServers.server().build() } - static specific(url: "https://petstore.swagger.io/v2") { + static server(url?: "https://petstore.swagger.io/v2"): { + build: () => Server<"SwaggerPetstore"> + } + static server(url: string = "https://petstore.swagger.io/v2"): unknown { switch (url) { case "https://petstore.swagger.io/v2": - return "https://petstore.swagger.io/v2" as Server<"SwaggerPetstore"> + return { + build(): Server<"SwaggerPetstore"> { + return "https://petstore.swagger.io/v2" as Server<"SwaggerPetstore"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) } } - static custom(url: string): Server<"SwaggerPetstoreCustom"> { - return url as Server<"SwaggerPetstoreCustom"> + static custom(url: string): Server<"custom_SwaggerPetstore"> { + return url as Server<"custom_SwaggerPetstore"> } } export interface SwaggerPetstoreConfig extends AbstractFetchClientConfig { - basePath: Server<"SwaggerPetstore"> | Server<"SwaggerPetstoreCustom"> + basePath: Server<"SwaggerPetstore"> | Server<"custom_SwaggerPetstore"> } export class SwaggerPetstore extends AbstractFetchClient { diff --git a/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts index ea9daa0d..da24a7cc 100644 --- a/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts @@ -171,40 +171,73 @@ import { TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" +export class StripeApiServersOperations { + static postFiles(url?: "https://files.stripe.com/"): { + build: () => Server<"postFiles_StripeApi"> + } + static postFiles(url: string = "https://files.stripe.com/"): unknown { + switch (url) { + case "https://files.stripe.com/": + return { + build(): Server<"postFiles_StripeApi"> { + return "https://files.stripe.com/" as Server<"postFiles_StripeApi"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) + } + } + + static getQuotesQuotePdf(url?: "https://files.stripe.com/"): { + build: () => Server<"getQuotesQuotePdf_StripeApi"> + } + static getQuotesQuotePdf(url: string = "https://files.stripe.com/"): unknown { + switch (url) { + case "https://files.stripe.com/": + return { + build(): Server<"getQuotesQuotePdf_StripeApi"> { + return "https://files.stripe.com/" as Server<"getQuotesQuotePdf_StripeApi"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) + } + } +} + export class StripeApiServers { static default(): Server<"StripeApi"> { - return "https://api.stripe.com/" as Server<"StripeApi"> + return StripeApiServers.server().build() } - static specific(url: "https://api.stripe.com/") { + static server(url?: "https://api.stripe.com/"): { + build: () => Server<"StripeApi"> + } + static server(url: string = "https://api.stripe.com/"): unknown { switch (url) { case "https://api.stripe.com/": - return "https://api.stripe.com/" as Server<"StripeApi"> + return { + build(): Server<"StripeApi"> { + return "https://api.stripe.com/" as Server<"StripeApi"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) } } - static custom(url: string): Server<"StripeApiCustom"> { - return url as Server<"StripeApiCustom"> - } + static readonly operations = StripeApiServersOperations - static readonly operations = { - postFiles(url: "https://files.stripe.com/") { - switch (url) { - case "https://files.stripe.com/": - return "https://files.stripe.com/" as Server<"postFiles"> - } - }, - getQuotesQuotePdf(url: "https://files.stripe.com/") { - switch (url) { - case "https://files.stripe.com/": - return "https://files.stripe.com/" as Server<"getQuotesQuotePdf"> - } - }, + static custom(url: string): Server<"custom_StripeApi"> { + return url as Server<"custom_StripeApi"> } } export interface StripeApiConfig extends AbstractFetchClientConfig { - basePath: Server<"StripeApi"> | Server<"StripeApiCustom"> + basePath: Server<"StripeApi"> | Server<"custom_StripeApi"> } export class StripeApi extends AbstractFetchClient { @@ -10527,10 +10560,10 @@ export class StripeApi extends AbstractFetchClient { } }, basePath: - | Server<"postFiles"> - | Server<"StripeApiCustom"> = StripeApiServers.operations.postFiles( - "https://files.stripe.com/", - ), + | Server<"postFiles_StripeApi"> + | Server<"custom_StripeApi"> = StripeApiServers.operations + .postFiles() + .build(), timeout?: number, opts: RequestInit = {}, ): Promise | Res>> { @@ -25961,10 +25994,10 @@ export class StripeApi extends AbstractFetchClient { requestBody?: EmptyObject }, basePath: - | Server<"getQuotesQuotePdf"> - | Server<"StripeApiCustom"> = StripeApiServers.operations.getQuotesQuotePdf( - "https://files.stripe.com/", - ), + | Server<"getQuotesQuotePdf_StripeApi"> + | Server<"custom_StripeApi"> = StripeApiServers.operations + .getQuotesQuotePdf() + .build(), timeout?: number, opts: RequestInit = {}, ): Promise | Res>> { diff --git a/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/client.ts index e074e141..c914726d 100644 --- a/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/client.ts @@ -7,18 +7,155 @@ import { t_Error, t_Statuses, t_TodoList, + t_UnknownObject, } from "./models" import { AbstractFetchClient, AbstractFetchClientConfig, Res, + Server, StatusCode, StatusCode4xx, StatusCode5xx, TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" -export interface TodoListsExampleApiConfig extends AbstractFetchClientConfig {} +export class TodoListsExampleApiServersOperations { + static listAttachments(url?: "{schema}://{tenant}.attachments.example.com"): { + build: ( + schema?: "http" | "https", + tenant?: string, + ) => Server<"listAttachments_TodoListsExampleApi"> + } + static listAttachments(url?: "https://attachments.example.com"): { + build: () => Server<"listAttachments_TodoListsExampleApi"> + } + static listAttachments( + url: string = "{schema}://{tenant}.attachments.example.com", + ): unknown { + switch (url) { + case "{schema}://{tenant}.attachments.example.com": + return { + build( + schema: "http" | "https" = "https", + tenant = "your-slug", + ): Server<"listAttachments_TodoListsExampleApi"> { + return "{schema}://{tenant}.attachments.example.com" + .replace("{schema}", schema) + .replace( + "{tenant}", + tenant, + ) as Server<"listAttachments_TodoListsExampleApi"> + }, + } + + case "https://attachments.example.com": + return { + build(): Server<"listAttachments_TodoListsExampleApi"> { + return "https://attachments.example.com" as Server<"listAttachments_TodoListsExampleApi"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) + } + } + + static uploadAttachment( + url?: "{schema}://{tenant}.attachments.example.com", + ): { + build: ( + schema?: "http" | "https", + tenant?: string, + ) => Server<"uploadAttachment_TodoListsExampleApi"> + } + static uploadAttachment(url?: "https://attachments.example.com"): { + build: () => Server<"uploadAttachment_TodoListsExampleApi"> + } + static uploadAttachment( + url: string = "{schema}://{tenant}.attachments.example.com", + ): unknown { + switch (url) { + case "{schema}://{tenant}.attachments.example.com": + return { + build( + schema: "http" | "https" = "https", + tenant = "your-slug", + ): Server<"uploadAttachment_TodoListsExampleApi"> { + return "{schema}://{tenant}.attachments.example.com" + .replace("{schema}", schema) + .replace( + "{tenant}", + tenant, + ) as Server<"uploadAttachment_TodoListsExampleApi"> + }, + } + + case "https://attachments.example.com": + return { + build(): Server<"uploadAttachment_TodoListsExampleApi"> { + return "https://attachments.example.com" as Server<"uploadAttachment_TodoListsExampleApi"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) + } + } +} + +export class TodoListsExampleApiServers { + static default(): Server<"TodoListsExampleApi"> { + return TodoListsExampleApiServers.server().build() + } + + static server(url?: "{schema}://{tenant}.todo-lists.example.com"): { + build: ( + schema?: "http" | "https", + tenant?: string, + ) => Server<"TodoListsExampleApi"> + } + static server(url?: "https://todo-lists.example.com"): { + build: () => Server<"TodoListsExampleApi"> + } + static server( + url: string = "{schema}://{tenant}.todo-lists.example.com", + ): unknown { + switch (url) { + case "{schema}://{tenant}.todo-lists.example.com": + return { + build( + schema: "http" | "https" = "https", + tenant = "your-slug", + ): Server<"TodoListsExampleApi"> { + return "{schema}://{tenant}.todo-lists.example.com" + .replace("{schema}", schema) + .replace("{tenant}", tenant) as Server<"TodoListsExampleApi"> + }, + } + + case "https://todo-lists.example.com": + return { + build(): Server<"TodoListsExampleApi"> { + return "https://todo-lists.example.com" as Server<"TodoListsExampleApi"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) + } + } + + static readonly operations = TodoListsExampleApiServersOperations + + static custom(url: string): Server<"custom_TodoListsExampleApi"> { + return url as Server<"custom_TodoListsExampleApi"> + } +} + +export interface TodoListsExampleApiConfig extends AbstractFetchClientConfig { + basePath: Server<"TodoListsExampleApi"> | Server<"custom_TodoListsExampleApi"> +} export class TodoListsExampleApi extends AbstractFetchClient { constructor(config: TodoListsExampleApiConfig) { @@ -158,6 +295,45 @@ export class TodoListsExampleApi extends AbstractFetchClient { return this._fetch(url, { method: "POST", body, ...opts, headers }, timeout) } + + async listAttachments( + basePath: + | Server<"listAttachments_TodoListsExampleApi"> + | Server<"custom_TodoListsExampleApi"> = TodoListsExampleApiServers.operations + .listAttachments() + .build(), + timeout?: number, + opts: RequestInit = {}, + ): Promise>> { + const url = basePath + `/attachments` + const headers = this._headers({}, opts.headers) + + return this._fetch(url, { method: "GET", ...opts, headers }, timeout) + } + + async uploadAttachment( + p: { + requestBody: { + file?: unknown + } + }, + basePath: + | Server<"uploadAttachment_TodoListsExampleApi"> + | Server<"custom_TodoListsExampleApi"> = TodoListsExampleApiServers.operations + .uploadAttachment() + .build(), + timeout?: number, + opts: RequestInit = {}, + ): Promise>> { + const url = basePath + `/attachments` + const headers = this._headers( + { "Content-Type": "multipart/form-data" }, + opts.headers, + ) + const body = JSON.stringify(p.requestBody) + + return this._fetch(url, { method: "POST", body, ...opts, headers }, timeout) + } } export { TodoListsExampleApi as ApiClient } diff --git a/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/models.ts b/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/models.ts index ba480145..46c34696 100644 --- a/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/models.ts +++ b/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/models.ts @@ -21,3 +21,7 @@ export type t_TodoList = { totalItemCount: number updated: string } + +export type t_UnknownObject = { + [key: string]: unknown | undefined +} diff --git a/integration-tests/typescript-fetch/src/todo-lists.type-tests.ts b/integration-tests/typescript-fetch/src/todo-lists.type-tests.ts new file mode 100644 index 00000000..db075063 --- /dev/null +++ b/integration-tests/typescript-fetch/src/todo-lists.type-tests.ts @@ -0,0 +1,40 @@ +import {TodoListsExampleApiServers} from "./generated/todo-lists.yaml/client" + +TodoListsExampleApiServers.server() + +// @ts-expect-error should reject random urls +TodoListsExampleApiServers.server("https://random.example.com").build() + +TodoListsExampleApiServers.server("https://todo-lists.example.com") + // @ts-expect-error should have no params + .build("foo") + +TodoListsExampleApiServers.server( + "{schema}://{tenant}.todo-lists.example.com", + // @ts-expect-error should be a enum +).build("foo") + +TodoListsExampleApiServers.server( + "{schema}://{tenant}.todo-lists.example.com", +).build("https", "foo") + +TodoListsExampleApiServers.operations.listAttachments() + +TodoListsExampleApiServers.operations + // @ts-expect-error should reject random urls + .listAttachments("https://random.example.com") + .build() + +TodoListsExampleApiServers.operations + .listAttachments("https://attachments.example.com") + // @ts-expect-error should have no params + .build("foo") + +TodoListsExampleApiServers.operations + .listAttachments("{schema}://{tenant}.attachments.example.com") + // @ts-expect-error should be a enum + .build("foo") + +TodoListsExampleApiServers.operations + .listAttachments("{schema}://{tenant}.attachments.example.com") + .build("https", "foo") diff --git a/integration-tests/typescript-koa/src/generated/todo-lists.yaml/generated.ts b/integration-tests/typescript-koa/src/generated/todo-lists.yaml/generated.ts index ce7c6a4c..50a278b0 100644 --- a/integration-tests/typescript-koa/src/generated/todo-lists.yaml/generated.ts +++ b/integration-tests/typescript-koa/src/generated/todo-lists.yaml/generated.ts @@ -11,14 +11,17 @@ import { t_GetTodoListItemsParamSchema, t_GetTodoListsQuerySchema, t_TodoList, + t_UnknownObject, t_UpdateTodoListByIdBodySchema, t_UpdateTodoListByIdParamSchema, + t_UploadAttachmentBodySchema, } from "./models" import { s_CreateUpdateTodoList, s_Error, s_Statuses, s_TodoList, + s_UnknownObject, } from "./schemas" import KoaRouter, { RouterContext } from "@koa/router" import { @@ -160,6 +163,26 @@ export type CreateTodoListItem = ( ctx: RouterContext, ) => Promise | Response<204, void>> +export type ListAttachmentsResponder = { + with200(): KoaRuntimeResponse +} & KoaRuntimeResponder + +export type ListAttachments = ( + params: Params, + respond: ListAttachmentsResponder, + ctx: RouterContext, +) => Promise | Response<200, t_UnknownObject[]>> + +export type UploadAttachmentResponder = { + with202(): KoaRuntimeResponse +} & KoaRuntimeResponder + +export type UploadAttachment = ( + params: Params, + respond: UploadAttachmentResponder, + ctx: RouterContext, +) => Promise | Response<202, void>> + export type Implementation = { getTodoLists: GetTodoLists getTodoListById: GetTodoListById @@ -167,6 +190,8 @@ export type Implementation = { deleteTodoListById: DeleteTodoListById getTodoListItems: GetTodoListItems createTodoListItem: CreateTodoListItem + listAttachments: ListAttachments + uploadAttachment: UploadAttachment } export function createRouter(implementation: Implementation): KoaRouter { @@ -507,6 +532,84 @@ export function createRouter(implementation: Implementation): KoaRouter { }, ) + const listAttachmentsResponseValidator = responseValidationFactory( + [["200", z.array(s_UnknownObject)]], + undefined, + ) + + router.get("listAttachments", "/attachments", async (ctx, next) => { + const input = { + params: undefined, + query: undefined, + body: undefined, + headers: undefined, + } + + const responder = { + with200() { + return new KoaRuntimeResponse(200) + }, + withStatus(status: StatusCode) { + return new KoaRuntimeResponse(status) + }, + } + + const response = await implementation + .listAttachments(input, responder, ctx) + .catch((err) => { + throw KoaRuntimeError.HandlerError(err) + }) + + const { status, body } = + response instanceof KoaRuntimeResponse ? response.unpack() : response + + ctx.body = listAttachmentsResponseValidator(status, body) + ctx.status = status + return next() + }) + + const uploadAttachmentBodySchema = z.object({ file: z.unknown().optional() }) + + const uploadAttachmentResponseValidator = responseValidationFactory( + [["202", z.undefined()]], + undefined, + ) + + router.post("uploadAttachment", "/attachments", async (ctx, next) => { + const input = { + params: undefined, + query: undefined, + body: parseRequestInput( + uploadAttachmentBodySchema, + Reflect.get(ctx.request, "body"), + RequestInputType.RequestBody, + ), + headers: undefined, + } + + const responder = { + with202() { + return new KoaRuntimeResponse(202) + }, + withStatus(status: StatusCode) { + return new KoaRuntimeResponse(status) + }, + } + + const response = await implementation + .uploadAttachment(input, responder, ctx) + .catch((err) => { + throw KoaRuntimeError.HandlerError(err) + }) + + const { status, body } = + response instanceof KoaRuntimeResponse ? response.unpack() : response + + ctx.body = uploadAttachmentResponseValidator(status, body) + ctx.status = status + return next() + }) + return router } diff --git a/integration-tests/typescript-koa/src/generated/todo-lists.yaml/models.ts b/integration-tests/typescript-koa/src/generated/todo-lists.yaml/models.ts index 1422b3d0..4e10e899 100644 --- a/integration-tests/typescript-koa/src/generated/todo-lists.yaml/models.ts +++ b/integration-tests/typescript-koa/src/generated/todo-lists.yaml/models.ts @@ -32,6 +32,10 @@ export type t_DeleteTodoListByIdParamSchema = { listId: string } +export type t_UnknownObject = { + [key: string]: unknown | undefined +} + export type t_GetTodoListByIdParamSchema = { listId: string } @@ -53,3 +57,7 @@ export type t_UpdateTodoListByIdBodySchema = { export type t_UpdateTodoListByIdParamSchema = { listId: string } + +export type t_UploadAttachmentBodySchema = { + file?: unknown +} diff --git a/integration-tests/typescript-koa/src/generated/todo-lists.yaml/schemas.ts b/integration-tests/typescript-koa/src/generated/todo-lists.yaml/schemas.ts index 09f123e7..dd11359e 100644 --- a/integration-tests/typescript-koa/src/generated/todo-lists.yaml/schemas.ts +++ b/integration-tests/typescript-koa/src/generated/todo-lists.yaml/schemas.ts @@ -21,3 +21,5 @@ export const s_TodoList = z.object({ created: z.string().datetime({ offset: true }), updated: z.string().datetime({ offset: true }), }) + +export const s_UnknownObject = z.record(z.unknown()) diff --git a/integration-tests/typescript-koa/src/todo-lists.yaml.ts b/integration-tests/typescript-koa/src/todo-lists.yaml.ts index d32ebb9f..d8e7bb84 100644 --- a/integration-tests/typescript-koa/src/todo-lists.yaml.ts +++ b/integration-tests/typescript-koa/src/todo-lists.yaml.ts @@ -20,6 +20,8 @@ async function main() { deleteTodoListById: notImplemented, getTodoListItems: notImplemented, createTodoListItem: notImplemented, + listAttachments: notImplemented, + uploadAttachment: notImplemented, }), middleware: [genericErrorMiddleware], port: {port: 3000, host: "127.0.0.1"}, diff --git a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.spec.ts b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.spec.ts index ada1cae4..7713f351 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.spec.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.spec.ts @@ -86,23 +86,22 @@ describe("typescript/common/client-servers-builder", () => { ]) }) - it("produces a default, specific, and custom", () => { + it("produces a specific, and custom", () => { expect(result.output).toMatchInlineSnapshot(` "export class UnitTestServers { - static default( - schema: "https" | "http" = "https", - tenant = "example", - ): Server<"UnitTest"> { - return "{schema}://unit-tests-default.{tenant}.example.com" - .replace("{schema}", schema) - .replace("{tenant}", tenant) as Server<"UnitTest"> + static default(): Server<"UnitTest"> { + return UnitTestServers.server().build() } - static specific( - url: - | "{schema}://unit-tests-default.{tenant}.example.com" - | "https://unit-tests-other.example.com", - ) { + static server(url?: "{schema}://unit-tests-default.{tenant}.example.com"): { + build: (schema?: "https" | "http", tenant?: string) => Server<"UnitTest"> + } + static server(url?: "https://unit-tests-other.example.com"): { + build: () => Server<"UnitTest"> + } + static server( + url: string = "{schema}://unit-tests-default.{tenant}.example.com", + ): unknown { switch (url) { case "{schema}://unit-tests-default.{tenant}.example.com": return { @@ -117,12 +116,19 @@ describe("typescript/common/client-servers-builder", () => { } case "https://unit-tests-other.example.com": - return "https://unit-tests-other.example.com" as Server<"UnitTest"> + return { + build(): Server<"UnitTest"> { + return "https://unit-tests-other.example.com" as Server<"UnitTest"> + }, + } + + default: + throw new Error(\`no matching server for url '\${url}'\`) } } - static custom(url: string): Server<"UnitTestCustom"> { - return url as Server<"UnitTestCustom"> + static custom(url: string): Server<"custom_UnitTest"> { + return url as Server<"custom_UnitTest"> } } " @@ -191,53 +197,96 @@ describe("typescript/common/client-servers-builder", () => { ) }) - it("produces a default, specific, custom, and operations", () => { + it("produces a specific, custom, and operations", () => { expect(result.output).toMatchInlineSnapshot(` - "export class UnitTestServers { + "export class UnitTestServersOperations { + static testOperation( + url?: "{schema}}://test-operation.{tenant}.example.com", + ): { + build: ( + schema?: "https" | "http", + tenant?: string, + ) => Server<"testOperation_UnitTest"> + } + static testOperation( + url: string = "{schema}}://test-operation.{tenant}.example.com", + ): unknown { + switch (url) { + case "{schema}}://test-operation.{tenant}.example.com": + return { + build( + schema: "https" | "http" = "https", + tenant = "example", + ): Server<"testOperation_UnitTest"> { + return "{schema}}://test-operation.{tenant}.example.com" + .replace("{schema}", schema) + .replace("{tenant}", tenant) as Server<"testOperation_UnitTest"> + }, + } + + default: + throw new Error(\`no matching server for url '\${url}'\`) + } + } + + static anotherTestOperation( + url?: "https://another-test-operation.example.com", + ): { build: () => Server<"anotherTestOperation_UnitTest"> } + static anotherTestOperation( + url: string = "https://another-test-operation.example.com", + ): unknown { + switch (url) { + case "https://another-test-operation.example.com": + return { + build(): Server<"anotherTestOperation_UnitTest"> { + return "https://another-test-operation.example.com" as Server<"anotherTestOperation_UnitTest"> + }, + } + + default: + throw new Error(\`no matching server for url '\${url}'\`) + } + } + } + + export class UnitTestServers { static default(): Server<"UnitTest"> { - return "https://unit-tests-default.example.com" as Server<"UnitTest"> + return UnitTestServers.server().build() } - static specific( - url: - | "https://unit-tests-default.example.com" - | "https://unit-tests-other.example.com", - ) { + static server(url?: "https://unit-tests-default.example.com"): { + build: () => Server<"UnitTest"> + } + static server(url?: "https://unit-tests-other.example.com"): { + build: () => Server<"UnitTest"> + } + static server( + url: string = "https://unit-tests-default.example.com", + ): unknown { switch (url) { case "https://unit-tests-default.example.com": - return "https://unit-tests-default.example.com" as Server<"UnitTest"> + return { + build(): Server<"UnitTest"> { + return "https://unit-tests-default.example.com" as Server<"UnitTest"> + }, + } case "https://unit-tests-other.example.com": - return "https://unit-tests-other.example.com" as Server<"UnitTest"> + return { + build(): Server<"UnitTest"> { + return "https://unit-tests-other.example.com" as Server<"UnitTest"> + }, + } + + default: + throw new Error(\`no matching server for url '\${url}'\`) } } - static custom(url: string): Server<"UnitTestCustom"> { - return url as Server<"UnitTestCustom"> - } + static readonly operations = UnitTestServersOperations - static readonly operations = { - testOperation(url: "{schema}}://test-operation.{tenant}.example.com") { - switch (url) { - case "{schema}}://test-operation.{tenant}.example.com": - return { - build( - schema: "https" | "http" = "https", - tenant = "example", - ): Server<"testOperation"> { - return "{schema}}://test-operation.{tenant}.example.com" - .replace("{schema}", schema) - .replace("{tenant}", tenant) as Server<"testOperation"> - }, - } - } - }, - anotherTestOperation(url: "https://another-test-operation.example.com") { - switch (url) { - case "https://another-test-operation.example.com": - return "https://another-test-operation.example.com" as Server<"anotherTestOperation"> - } - }, + static custom(url: string): Server<"custom_UnitTest"> { + return url as Server<"custom_UnitTest"> } } " @@ -255,20 +304,20 @@ describe("typescript/common/client-servers-builder", () => { it("typeForOperationId returns the correct type when there are variables", () => { expect( result.builder.typeForOperationId("testOperation"), - ).toMatchInlineSnapshot(`"Server<"testOperation">"`) + ).toMatchInlineSnapshot(`"Server<"testOperation_UnitTest">"`) }) it("typeForOperationId returns the correct type when there are no variables", () => { expect( result.builder.typeForOperationId("anotherTestOperation"), - ).toMatchInlineSnapshot(`"Server<"anotherTestOperation">"`) + ).toMatchInlineSnapshot(`"Server<"anotherTestOperation_UnitTest">"`) }) it("defaultForOperationId returns the correct value when there are variables", () => { expect( result.builder.defaultForOperationId("testOperation"), ).toMatchInlineSnapshot( - `"UnitTestServers.operations.testOperation("{schema}}://test-operation.{tenant}.example.com").build()"`, + `"UnitTestServers.operations.testOperation().build()"`, ) }) @@ -276,7 +325,7 @@ describe("typescript/common/client-servers-builder", () => { expect( result.builder.defaultForOperationId("anotherTestOperation"), ).toMatchInlineSnapshot( - `"UnitTestServers.operations.anotherTestOperation("https://another-test-operation.example.com")"`, + `"UnitTestServers.operations.anotherTestOperation().build()"`, ) }) }) diff --git a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts index c5e321eb..878abc68 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts @@ -23,13 +23,18 @@ export class ClientServersBuilder implements ICompilable { } } - private toParams(variables: { - [k: string]: IRServerVariable - }) { + private toParams( + variables: { + [k: string]: IRServerVariable + }, + includeDefault = true, + ): string { return Object.entries(variables) .map(([name, variable]) => { - const type = union(...variable.enum.map(quotedStringLiteral)) - return `${name}${type ? `:${type}` : ""} = "${variable.default}"` + const type = !includeDefault + ? `${union(...variable.enum.map(quotedStringLiteral)) || "string"}` + : union(...variable.enum.map(quotedStringLiteral)) + return `${name}${type ? `${!includeDefault ? "?" : ""}:${type}` : ""} ${includeDefault ? `= ${quotedStringLiteral(variable.default)}` : ""}` }) .join(",") } @@ -47,8 +52,8 @@ export class ClientServersBuilder implements ICompilable { } return ` - static default(${this.toParams(defaultServer.variables)}): ${this.typeForDefault()} { - return (${this.toReplacer(defaultServer)} as ${this.typeForDefault()}) + static default(): ${this.typeForDefault()} { + return ${this.classExportName}.server().build() } ` } @@ -59,29 +64,35 @@ export class ClientServersBuilder implements ICompilable { typeName: string, isStatic = true, ) { - if (!this.hasServers) { + if (!servers[0]) { return "" } - const urls = servers.map((it) => quotedStringLiteral(it.url)) - return `${isStatic ? "static " : ""}${name}(url: ${union(urls)}){ + const defaultUrl = quotedStringLiteral(servers[0].url) + + const overloads = servers + .map((it) => { + return `${isStatic ? "static " : ""}${name}(url?: ${quotedStringLiteral(it.url)}): {build: (${this.toParams(it.variables, false)}) => ${typeName}};` + }) + .join("\n") + + return ` + ${overloads} + ${isStatic ? "static " : ""}${name}(url: string = ${defaultUrl}): unknown { switch(url) { ${servers .map( (server) => ` case ${quotedStringLiteral(server.url)}: - return ${ - Object.keys(server.variables).length > 0 - ? `{ - build(${this.toParams(server.variables)}): ${typeName} { + return { + build(${this.toParams(server.variables)}): ${typeName} { return (${this.toReplacer(server)} as ${typeName}) } - }` - : `(${quotedStringLiteral(server.url)} as ${typeName})` } `, ) .join("\n")} + default: throw new Error(\`no matching server for url '\${url}'\`) } }` } @@ -103,7 +114,7 @@ export class ClientServersBuilder implements ICompilable { } typeForCustom() { - return `Server<"${this.name}Custom">` + return `Server<"custom_${this.name}">` } defaultForOperationId(operationId: string) { @@ -115,19 +126,17 @@ export class ClientServersBuilder implements ICompilable { throw new Error(`no operation with id '${operationId}'`) } - const defaultServer = operation.servers[0] + const hasServers = operation.servers.length > 0 - if (!defaultServer) { - throw new Error(`no default server for operation '${operationId}'`) + if (!hasServers) { + throw new Error(`no server overrides for operation '${operationId}'`) } - const hasVariables = Object.keys(defaultServer.variables).length > 0 - - return `${this.classExportName}.operations.${operationId}(${quotedStringLiteral(defaultServer.url)})${hasVariables ? ".build()" : ""}` + return `${this.classExportName}.operations.${operationId}().build()` } typeForOperationId(operationId: string) { - return `Server<"${operationId}">` + return `Server<"${operationId}_${this.name}">` } toString() { @@ -141,27 +150,34 @@ export class ClientServersBuilder implements ICompilable { it.operationId, it.servers, this.typeForOperationId(it.operationId), - false, + true, ), ) - .join(",\n") + .join("\n") return ` + ${ + operations.length + ? ` + export class ${this.classExportName}Operations { + ${operations} + }` + : "" + } + export class ${this.classExportName} { ${this.toDefault()} - ${this.toSpecific("specific", this.servers, this.typeForDefault())} - - static custom(url: string): ${this.typeForCustom()} { - return (url as ${this.typeForCustom()}) - } + ${this.toSpecific("server", this.servers, this.typeForDefault())} ${ operations.length - ? `static readonly operations = { - ${operations} - }` + ? `static readonly operations = ${this.classExportName}Operations` : "" } + + static custom(url: string): ${this.typeForCustom()} { + return (url as ${this.typeForCustom()}) + } } ` } From 07d40107199501397fa7a93560c9082d05b5f776 Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Fri, 6 Dec 2024 10:28:13 +0000 Subject: [PATCH 15/24] feat: avoid overloads when single server --- .../api.github.com.yaml/client.service.ts | 12 ++--- .../client.service.ts | 5 +-- .../client.service.ts | 7 ++- .../generated/okta.idp.yaml/client.service.ts | 5 +-- .../okta.oauth.yaml/client.service.ts | 5 +-- .../petstore-expanded.yaml/client.service.ts | 7 ++- .../generated/stripe.yaml/client.service.ts | 19 ++++---- .../generated/api.github.com.yaml/client.ts | 12 ++--- .../client.ts | 5 +-- .../azure-resource-manager.tsp/client.ts | 7 ++- .../src/generated/okta.idp.yaml/client.ts | 5 +-- .../src/generated/okta.oauth.yaml/client.ts | 5 +-- .../petstore-expanded.yaml/client.ts | 7 ++- .../src/generated/stripe.yaml/client.ts | 19 ++++---- .../generated/api.github.com.yaml/client.ts | 12 ++--- .../client.ts | 5 +-- .../azure-resource-manager.tsp/client.ts | 7 ++- .../src/generated/okta.idp.yaml/client.ts | 5 +-- .../src/generated/okta.oauth.yaml/client.ts | 5 +-- .../petstore-expanded.yaml/client.ts | 7 ++- .../src/generated/stripe.yaml/client.ts | 19 ++++---- .../common/client-servers-builder.spec.ts | 14 ++---- .../common/client-servers-builder.ts | 45 ++++++++++--------- 23 files changed, 100 insertions(+), 139 deletions(-) diff --git a/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts index 8578f503..c956ff93 100644 --- a/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts @@ -321,12 +321,9 @@ import { Injectable } from "@angular/core" import { Observable } from "rxjs" export class GitHubV3RestApiServiceServersOperations { - static reposUploadReleaseAsset(url?: "https://uploads.github.com"): { - build: () => Server<"reposUploadReleaseAsset_GitHubV3RestApiService"> - } static reposUploadReleaseAsset( - url: string = "https://uploads.github.com", - ): unknown { + url: "https://uploads.github.com" = "https://uploads.github.com", + ): { build: () => Server<"reposUploadReleaseAsset_GitHubV3RestApiService"> } { switch (url) { case "https://uploads.github.com": return { @@ -346,10 +343,9 @@ export class GitHubV3RestApiServiceServers { return GitHubV3RestApiServiceServers.server().build() } - static server(url?: "https://api.github.com"): { + static server(url: "https://api.github.com" = "https://api.github.com"): { build: () => Server<"GitHubV3RestApiService"> - } - static server(url: string = "https://api.github.com"): unknown { + } { switch (url) { case "https://api.github.com": return { diff --git a/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts b/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts index 25997b46..6bb838e9 100644 --- a/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts @@ -29,10 +29,9 @@ export class ContosoWidgetManagerServiceServers { return ContosoWidgetManagerServiceServers.server().build() } - static server(url?: "{endpoint}/widget"): { + static server(url: "{endpoint}/widget" = "{endpoint}/widget"): { build: (endpoint?: string) => Server<"ContosoWidgetManagerService"> - } - static server(url: string = "{endpoint}/widget"): unknown { + } { switch (url) { case "{endpoint}/widget": return { diff --git a/integration-tests/typescript-angular/src/generated/azure-resource-manager.tsp/client.service.ts b/integration-tests/typescript-angular/src/generated/azure-resource-manager.tsp/client.service.ts index e965830b..7a7bd3cd 100644 --- a/integration-tests/typescript-angular/src/generated/azure-resource-manager.tsp/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/azure-resource-manager.tsp/client.service.ts @@ -21,10 +21,9 @@ export class ContosoProviderHubClientServiceServers { return ContosoProviderHubClientServiceServers.server().build() } - static server(url?: "https://management.azure.com"): { - build: () => Server<"ContosoProviderHubClientService"> - } - static server(url: string = "https://management.azure.com"): unknown { + static server( + url: "https://management.azure.com" = "https://management.azure.com", + ): { build: () => Server<"ContosoProviderHubClientService"> } { switch (url) { case "https://management.azure.com": return { diff --git a/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts index 9ffd8321..66cd04ae 100644 --- a/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts @@ -29,10 +29,9 @@ export class MyAccountManagementServiceServers { return MyAccountManagementServiceServers.server().build() } - static server(url?: "https://{yourOktaDomain}"): { + static server(url: "https://{yourOktaDomain}" = "https://{yourOktaDomain}"): { build: (yourOktaDomain?: string) => Server<"MyAccountManagementService"> - } - static server(url: string = "https://{yourOktaDomain}"): unknown { + } { switch (url) { case "https://{yourOktaDomain}": return { diff --git a/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts index 0d2543ae..c19b7379 100644 --- a/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts @@ -43,12 +43,11 @@ export class OktaOpenIdConnectOAuth20ServiceServers { return OktaOpenIdConnectOAuth20ServiceServers.server().build() } - static server(url?: "https://{yourOktaDomain}"): { + static server(url: "https://{yourOktaDomain}" = "https://{yourOktaDomain}"): { build: ( yourOktaDomain?: string, ) => Server<"OktaOpenIdConnectOAuth20Service"> - } - static server(url: string = "https://{yourOktaDomain}"): unknown { + } { switch (url) { case "https://{yourOktaDomain}": return { diff --git a/integration-tests/typescript-angular/src/generated/petstore-expanded.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/petstore-expanded.yaml/client.service.ts index 94c1cb6f..f39736cf 100644 --- a/integration-tests/typescript-angular/src/generated/petstore-expanded.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/petstore-expanded.yaml/client.service.ts @@ -12,10 +12,9 @@ export class SwaggerPetstoreServiceServers { return SwaggerPetstoreServiceServers.server().build() } - static server(url?: "https://petstore.swagger.io/v2"): { - build: () => Server<"SwaggerPetstoreService"> - } - static server(url: string = "https://petstore.swagger.io/v2"): unknown { + static server( + url: "https://petstore.swagger.io/v2" = "https://petstore.swagger.io/v2", + ): { build: () => Server<"SwaggerPetstoreService"> } { switch (url) { case "https://petstore.swagger.io/v2": return { diff --git a/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts index 8667ed68..5413d09d 100644 --- a/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts @@ -167,10 +167,9 @@ import { Injectable } from "@angular/core" import { Observable } from "rxjs" export class StripeApiServiceServersOperations { - static postFiles(url?: "https://files.stripe.com/"): { - build: () => Server<"postFiles_StripeApiService"> - } - static postFiles(url: string = "https://files.stripe.com/"): unknown { + static postFiles( + url: "https://files.stripe.com/" = "https://files.stripe.com/", + ): { build: () => Server<"postFiles_StripeApiService"> } { switch (url) { case "https://files.stripe.com/": return { @@ -184,10 +183,9 @@ export class StripeApiServiceServersOperations { } } - static getQuotesQuotePdf(url?: "https://files.stripe.com/"): { - build: () => Server<"getQuotesQuotePdf_StripeApiService"> - } - static getQuotesQuotePdf(url: string = "https://files.stripe.com/"): unknown { + static getQuotesQuotePdf( + url: "https://files.stripe.com/" = "https://files.stripe.com/", + ): { build: () => Server<"getQuotesQuotePdf_StripeApiService"> } { switch (url) { case "https://files.stripe.com/": return { @@ -207,10 +205,9 @@ export class StripeApiServiceServers { return StripeApiServiceServers.server().build() } - static server(url?: "https://api.stripe.com/"): { + static server(url: "https://api.stripe.com/" = "https://api.stripe.com/"): { build: () => Server<"StripeApiService"> - } - static server(url: string = "https://api.stripe.com/"): unknown { + } { switch (url) { case "https://api.stripe.com/": return { diff --git a/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts b/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts index 2191cf31..5920a19e 100644 --- a/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts @@ -319,12 +319,9 @@ import { import { AxiosRequestConfig, AxiosResponse } from "axios" export class GitHubV3RestApiServersOperations { - static reposUploadReleaseAsset(url?: "https://uploads.github.com"): { - build: () => Server<"reposUploadReleaseAsset_GitHubV3RestApi"> - } static reposUploadReleaseAsset( - url: string = "https://uploads.github.com", - ): unknown { + url: "https://uploads.github.com" = "https://uploads.github.com", + ): { build: () => Server<"reposUploadReleaseAsset_GitHubV3RestApi"> } { switch (url) { case "https://uploads.github.com": return { @@ -344,10 +341,9 @@ export class GitHubV3RestApiServers { return GitHubV3RestApiServers.server().build() } - static server(url?: "https://api.github.com"): { + static server(url: "https://api.github.com" = "https://api.github.com"): { build: () => Server<"GitHubV3RestApi"> - } - static server(url: string = "https://api.github.com"): unknown { + } { switch (url) { case "https://api.github.com": return { diff --git a/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts b/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts index fe5ea1d1..14f3eaa4 100644 --- a/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts +++ b/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts @@ -31,10 +31,9 @@ export class ContosoWidgetManagerServers { return ContosoWidgetManagerServers.server().build() } - static server(url?: "{endpoint}/widget"): { + static server(url: "{endpoint}/widget" = "{endpoint}/widget"): { build: (endpoint?: string) => Server<"ContosoWidgetManager"> - } - static server(url: string = "{endpoint}/widget"): unknown { + } { switch (url) { case "{endpoint}/widget": return { diff --git a/integration-tests/typescript-axios/src/generated/azure-resource-manager.tsp/client.ts b/integration-tests/typescript-axios/src/generated/azure-resource-manager.tsp/client.ts index 8b802fd0..eced91e3 100644 --- a/integration-tests/typescript-axios/src/generated/azure-resource-manager.tsp/client.ts +++ b/integration-tests/typescript-axios/src/generated/azure-resource-manager.tsp/client.ts @@ -23,10 +23,9 @@ export class ContosoProviderHubClientServers { return ContosoProviderHubClientServers.server().build() } - static server(url?: "https://management.azure.com"): { - build: () => Server<"ContosoProviderHubClient"> - } - static server(url: string = "https://management.azure.com"): unknown { + static server( + url: "https://management.azure.com" = "https://management.azure.com", + ): { build: () => Server<"ContosoProviderHubClient"> } { switch (url) { case "https://management.azure.com": return { diff --git a/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts b/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts index 37056129..0fcdb497 100644 --- a/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts @@ -31,10 +31,9 @@ export class MyAccountManagementServers { return MyAccountManagementServers.server().build() } - static server(url?: "https://{yourOktaDomain}"): { + static server(url: "https://{yourOktaDomain}" = "https://{yourOktaDomain}"): { build: (yourOktaDomain?: string) => Server<"MyAccountManagement"> - } - static server(url: string = "https://{yourOktaDomain}"): unknown { + } { switch (url) { case "https://{yourOktaDomain}": return { diff --git a/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts b/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts index 850d6aca..169081ff 100644 --- a/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts @@ -44,10 +44,9 @@ export class OktaOpenIdConnectOAuth20Servers { return OktaOpenIdConnectOAuth20Servers.server().build() } - static server(url?: "https://{yourOktaDomain}"): { + static server(url: "https://{yourOktaDomain}" = "https://{yourOktaDomain}"): { build: (yourOktaDomain?: string) => Server<"OktaOpenIdConnectOAuth20"> - } - static server(url: string = "https://{yourOktaDomain}"): unknown { + } { switch (url) { case "https://{yourOktaDomain}": return { diff --git a/integration-tests/typescript-axios/src/generated/petstore-expanded.yaml/client.ts b/integration-tests/typescript-axios/src/generated/petstore-expanded.yaml/client.ts index 5e8ebdee..6351c9db 100644 --- a/integration-tests/typescript-axios/src/generated/petstore-expanded.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/petstore-expanded.yaml/client.ts @@ -15,10 +15,9 @@ export class SwaggerPetstoreServers { return SwaggerPetstoreServers.server().build() } - static server(url?: "https://petstore.swagger.io/v2"): { - build: () => Server<"SwaggerPetstore"> - } - static server(url: string = "https://petstore.swagger.io/v2"): unknown { + static server( + url: "https://petstore.swagger.io/v2" = "https://petstore.swagger.io/v2", + ): { build: () => Server<"SwaggerPetstore"> } { switch (url) { case "https://petstore.swagger.io/v2": return { diff --git a/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts b/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts index b73c3b5c..592c90cb 100644 --- a/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts @@ -169,10 +169,9 @@ import { import { AxiosRequestConfig, AxiosResponse } from "axios" export class StripeApiServersOperations { - static postFiles(url?: "https://files.stripe.com/"): { - build: () => Server<"postFiles_StripeApi"> - } - static postFiles(url: string = "https://files.stripe.com/"): unknown { + static postFiles( + url: "https://files.stripe.com/" = "https://files.stripe.com/", + ): { build: () => Server<"postFiles_StripeApi"> } { switch (url) { case "https://files.stripe.com/": return { @@ -186,10 +185,9 @@ export class StripeApiServersOperations { } } - static getQuotesQuotePdf(url?: "https://files.stripe.com/"): { - build: () => Server<"getQuotesQuotePdf_StripeApi"> - } - static getQuotesQuotePdf(url: string = "https://files.stripe.com/"): unknown { + static getQuotesQuotePdf( + url: "https://files.stripe.com/" = "https://files.stripe.com/", + ): { build: () => Server<"getQuotesQuotePdf_StripeApi"> } { switch (url) { case "https://files.stripe.com/": return { @@ -209,10 +207,9 @@ export class StripeApiServers { return StripeApiServers.server().build() } - static server(url?: "https://api.stripe.com/"): { + static server(url: "https://api.stripe.com/" = "https://api.stripe.com/"): { build: () => Server<"StripeApi"> - } - static server(url: string = "https://api.stripe.com/"): unknown { + } { switch (url) { case "https://api.stripe.com/": return { diff --git a/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts index 9077697f..ee285776 100644 --- a/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts @@ -325,12 +325,9 @@ import { } from "@nahkies/typescript-fetch-runtime/main" export class GitHubV3RestApiServersOperations { - static reposUploadReleaseAsset(url?: "https://uploads.github.com"): { - build: () => Server<"reposUploadReleaseAsset_GitHubV3RestApi"> - } static reposUploadReleaseAsset( - url: string = "https://uploads.github.com", - ): unknown { + url: "https://uploads.github.com" = "https://uploads.github.com", + ): { build: () => Server<"reposUploadReleaseAsset_GitHubV3RestApi"> } { switch (url) { case "https://uploads.github.com": return { @@ -350,10 +347,9 @@ export class GitHubV3RestApiServers { return GitHubV3RestApiServers.server().build() } - static server(url?: "https://api.github.com"): { + static server(url: "https://api.github.com" = "https://api.github.com"): { build: () => Server<"GitHubV3RestApi"> - } - static server(url: string = "https://api.github.com"): unknown { + } { switch (url) { case "https://api.github.com": return { diff --git a/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts b/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts index 43033e64..48fe83e5 100644 --- a/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts +++ b/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts @@ -34,10 +34,9 @@ export class ContosoWidgetManagerServers { return ContosoWidgetManagerServers.server().build() } - static server(url?: "{endpoint}/widget"): { + static server(url: "{endpoint}/widget" = "{endpoint}/widget"): { build: (endpoint?: string) => Server<"ContosoWidgetManager"> - } - static server(url: string = "{endpoint}/widget"): unknown { + } { switch (url) { case "{endpoint}/widget": return { diff --git a/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts b/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts index 6a76b451..fff6aaa5 100644 --- a/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts +++ b/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts @@ -26,10 +26,9 @@ export class ContosoProviderHubClientServers { return ContosoProviderHubClientServers.server().build() } - static server(url?: "https://management.azure.com"): { - build: () => Server<"ContosoProviderHubClient"> - } - static server(url: string = "https://management.azure.com"): unknown { + static server( + url: "https://management.azure.com" = "https://management.azure.com", + ): { build: () => Server<"ContosoProviderHubClient"> } { switch (url) { case "https://management.azure.com": return { diff --git a/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts index fb97973a..04d40055 100644 --- a/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts @@ -33,10 +33,9 @@ export class MyAccountManagementServers { return MyAccountManagementServers.server().build() } - static server(url?: "https://{yourOktaDomain}"): { + static server(url: "https://{yourOktaDomain}" = "https://{yourOktaDomain}"): { build: (yourOktaDomain?: string) => Server<"MyAccountManagement"> - } - static server(url: string = "https://{yourOktaDomain}"): unknown { + } { switch (url) { case "https://{yourOktaDomain}": return { diff --git a/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts index 228a2d7e..b0c8645b 100644 --- a/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts @@ -47,10 +47,9 @@ export class OktaOpenIdConnectOAuth20Servers { return OktaOpenIdConnectOAuth20Servers.server().build() } - static server(url?: "https://{yourOktaDomain}"): { + static server(url: "https://{yourOktaDomain}" = "https://{yourOktaDomain}"): { build: (yourOktaDomain?: string) => Server<"OktaOpenIdConnectOAuth20"> - } - static server(url: string = "https://{yourOktaDomain}"): unknown { + } { switch (url) { case "https://{yourOktaDomain}": return { diff --git a/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts index 0c364a77..3102ef6e 100644 --- a/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts @@ -17,10 +17,9 @@ export class SwaggerPetstoreServers { return SwaggerPetstoreServers.server().build() } - static server(url?: "https://petstore.swagger.io/v2"): { - build: () => Server<"SwaggerPetstore"> - } - static server(url: string = "https://petstore.swagger.io/v2"): unknown { + static server( + url: "https://petstore.swagger.io/v2" = "https://petstore.swagger.io/v2", + ): { build: () => Server<"SwaggerPetstore"> } { switch (url) { case "https://petstore.swagger.io/v2": return { diff --git a/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts index da24a7cc..500eba2d 100644 --- a/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts @@ -172,10 +172,9 @@ import { } from "@nahkies/typescript-fetch-runtime/main" export class StripeApiServersOperations { - static postFiles(url?: "https://files.stripe.com/"): { - build: () => Server<"postFiles_StripeApi"> - } - static postFiles(url: string = "https://files.stripe.com/"): unknown { + static postFiles( + url: "https://files.stripe.com/" = "https://files.stripe.com/", + ): { build: () => Server<"postFiles_StripeApi"> } { switch (url) { case "https://files.stripe.com/": return { @@ -189,10 +188,9 @@ export class StripeApiServersOperations { } } - static getQuotesQuotePdf(url?: "https://files.stripe.com/"): { - build: () => Server<"getQuotesQuotePdf_StripeApi"> - } - static getQuotesQuotePdf(url: string = "https://files.stripe.com/"): unknown { + static getQuotesQuotePdf( + url: "https://files.stripe.com/" = "https://files.stripe.com/", + ): { build: () => Server<"getQuotesQuotePdf_StripeApi"> } { switch (url) { case "https://files.stripe.com/": return { @@ -212,10 +210,9 @@ export class StripeApiServers { return StripeApiServers.server().build() } - static server(url?: "https://api.stripe.com/"): { + static server(url: "https://api.stripe.com/" = "https://api.stripe.com/"): { build: () => Server<"StripeApi"> - } - static server(url: string = "https://api.stripe.com/"): unknown { + } { switch (url) { case "https://api.stripe.com/": return { diff --git a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.spec.ts b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.spec.ts index 7713f351..25673c6d 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.spec.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.spec.ts @@ -201,16 +201,13 @@ describe("typescript/common/client-servers-builder", () => { expect(result.output).toMatchInlineSnapshot(` "export class UnitTestServersOperations { static testOperation( - url?: "{schema}}://test-operation.{tenant}.example.com", + url: "{schema}}://test-operation.{tenant}.example.com" = "{schema}}://test-operation.{tenant}.example.com", ): { build: ( schema?: "https" | "http", tenant?: string, ) => Server<"testOperation_UnitTest"> - } - static testOperation( - url: string = "{schema}}://test-operation.{tenant}.example.com", - ): unknown { + } { switch (url) { case "{schema}}://test-operation.{tenant}.example.com": return { @@ -230,11 +227,8 @@ describe("typescript/common/client-servers-builder", () => { } static anotherTestOperation( - url?: "https://another-test-operation.example.com", - ): { build: () => Server<"anotherTestOperation_UnitTest"> } - static anotherTestOperation( - url: string = "https://another-test-operation.example.com", - ): unknown { + url: "https://another-test-operation.example.com" = "https://another-test-operation.example.com", + ): { build: () => Server<"anotherTestOperation_UnitTest"> } { switch (url) { case "https://another-test-operation.example.com": return { diff --git a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts index 878abc68..619ab0d8 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts @@ -58,43 +58,47 @@ export class ClientServersBuilder implements ICompilable { ` } - private toSpecific( - name: string, - servers: IRServer[], - typeName: string, - isStatic = true, - ) { + private toSpecific(name: string, servers: IRServer[], typeName: string) { if (!servers[0]) { return "" } const defaultUrl = quotedStringLiteral(servers[0].url) - const overloads = servers - .map((it) => { - return `${isStatic ? "static " : ""}${name}(url?: ${quotedStringLiteral(it.url)}): {build: (${this.toParams(it.variables, false)}) => ${typeName}};` - }) - .join("\n") + const overloads = servers.map((it) => { + return `static ${name}(url?: ${quotedStringLiteral(it.url)}): {build: (${this.toParams(it.variables, false)}) => ${typeName}};` + }) - return ` - ${overloads} - ${isStatic ? "static " : ""}${name}(url: string = ${defaultUrl}): unknown { + const switchSnippet = ` switch(url) { ${servers .map( - (server) => ` - case ${quotedStringLiteral(server.url)}: + (it) => ` + case ${quotedStringLiteral(it.url)}: return { - build(${this.toParams(server.variables)}): ${typeName} { - return (${this.toReplacer(server)} as ${typeName}) + build(${this.toParams(it.variables)}): ${typeName} { + return (${this.toReplacer(it)} as ${typeName}) } } - `, + `, ) .join("\n")} default: throw new Error(\`no matching server for url '\${url}'\`) } - }` + ` + + if (overloads.length > 1) { + return ` + ${overloads.join("\n")} + static ${name}(url: string = ${defaultUrl}): unknown { + ${switchSnippet} + }` + } + + return ` + static ${name}(url: ${defaultUrl} = ${defaultUrl}): {build: (${this.toParams(servers[0].variables, false)}) => ${typeName}} { + ${switchSnippet} + }` } get hasServers() { @@ -150,7 +154,6 @@ export class ClientServersBuilder implements ICompilable { it.operationId, it.servers, this.typeForOperationId(it.operationId), - true, ), ) .join("\n") From be87ac4deb3e33ac67e92754bc23f1055521698e Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Fri, 6 Dec 2024 10:50:04 +0000 Subject: [PATCH 16/24] fix: ditch custom, add validation --- .../api.github.com.yaml/client.service.ts | 8 +--- .../client.service.ts | 8 +--- .../client.service.ts | 8 +--- .../generated/okta.idp.yaml/client.service.ts | 8 +--- .../okta.oauth.yaml/client.service.ts | 8 +--- .../petstore-expanded.yaml/client.service.ts | 8 +--- .../generated/stripe.yaml/client.service.ts | 6 +-- .../todo-lists.yaml/client.service.ts | 8 +--- .../generated/api.github.com.yaml/client.ts | 10 +--- .../client.ts | 8 +--- .../azure-resource-manager.tsp/client.ts | 8 +--- .../src/generated/okta.idp.yaml/client.ts | 6 +-- .../src/generated/okta.oauth.yaml/client.ts | 8 +--- .../petstore-expanded.yaml/client.ts | 6 +-- .../src/generated/stripe.yaml/client.ts | 12 ++--- .../src/generated/todo-lists.yaml/client.ts | 14 ++---- .../generated/api.github.com.yaml/client.ts | 8 +--- .../client.ts | 8 +--- .../azure-resource-manager.tsp/client.ts | 8 +--- .../src/generated/okta.idp.yaml/client.ts | 6 +-- .../src/generated/okta.oauth.yaml/client.ts | 8 +--- .../petstore-expanded.yaml/client.ts | 6 +-- .../src/generated/stripe.yaml/client.ts | 14 ++---- .../src/generated/todo-lists.yaml/client.ts | 10 ++-- .../src/typescript/common/client-builder.ts | 5 +- .../common/client-servers-builder.spec.ts | 12 +---- .../common/client-servers-builder.ts | 47 +++++++++++++++---- .../typescript-axios-client-builder.ts | 2 +- .../typescript-fetch-client-builder.ts | 2 +- 29 files changed, 77 insertions(+), 193 deletions(-) diff --git a/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts index c956ff93..5fffc42e 100644 --- a/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts @@ -360,16 +360,10 @@ export class GitHubV3RestApiServiceServers { } static readonly operations = GitHubV3RestApiServiceServersOperations - - static custom(url: string): Server<"custom_GitHubV3RestApiService"> { - return url as Server<"custom_GitHubV3RestApiService"> - } } export class GitHubV3RestApiServiceConfig { - basePath: - | Server<"GitHubV3RestApiService"> - | Server<"custom_GitHubV3RestApiService"> = + basePath: Server<"GitHubV3RestApiService"> | string = GitHubV3RestApiServiceServers.default() defaultHeaders: Record = {} } diff --git a/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts b/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts index 6bb838e9..3c56432f 100644 --- a/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/azure-core-data-plane-service.tsp/client.service.ts @@ -47,16 +47,10 @@ export class ContosoWidgetManagerServiceServers { throw new Error(`no matching server for url '${url}'`) } } - - static custom(url: string): Server<"custom_ContosoWidgetManagerService"> { - return url as Server<"custom_ContosoWidgetManagerService"> - } } export class ContosoWidgetManagerServiceConfig { - basePath: - | Server<"ContosoWidgetManagerService"> - | Server<"custom_ContosoWidgetManagerService"> = + basePath: Server<"ContosoWidgetManagerService"> | string = ContosoWidgetManagerServiceServers.default() defaultHeaders: Record = {} } diff --git a/integration-tests/typescript-angular/src/generated/azure-resource-manager.tsp/client.service.ts b/integration-tests/typescript-angular/src/generated/azure-resource-manager.tsp/client.service.ts index 7a7bd3cd..4f12e6e0 100644 --- a/integration-tests/typescript-angular/src/generated/azure-resource-manager.tsp/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/azure-resource-manager.tsp/client.service.ts @@ -36,16 +36,10 @@ export class ContosoProviderHubClientServiceServers { throw new Error(`no matching server for url '${url}'`) } } - - static custom(url: string): Server<"custom_ContosoProviderHubClientService"> { - return url as Server<"custom_ContosoProviderHubClientService"> - } } export class ContosoProviderHubClientServiceConfig { - basePath: - | Server<"ContosoProviderHubClientService"> - | Server<"custom_ContosoProviderHubClientService"> = + basePath: Server<"ContosoProviderHubClientService"> | string = ContosoProviderHubClientServiceServers.default() defaultHeaders: Record = {} } diff --git a/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts index 66cd04ae..7fa617bc 100644 --- a/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/okta.idp.yaml/client.service.ts @@ -49,16 +49,10 @@ export class MyAccountManagementServiceServers { throw new Error(`no matching server for url '${url}'`) } } - - static custom(url: string): Server<"custom_MyAccountManagementService"> { - return url as Server<"custom_MyAccountManagementService"> - } } export class MyAccountManagementServiceConfig { - basePath: - | Server<"MyAccountManagementService"> - | Server<"custom_MyAccountManagementService"> = + basePath: Server<"MyAccountManagementService"> | string = MyAccountManagementServiceServers.default() defaultHeaders: Record = {} } diff --git a/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts index c19b7379..0924c329 100644 --- a/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/okta.oauth.yaml/client.service.ts @@ -65,16 +65,10 @@ export class OktaOpenIdConnectOAuth20ServiceServers { throw new Error(`no matching server for url '${url}'`) } } - - static custom(url: string): Server<"custom_OktaOpenIdConnectOAuth20Service"> { - return url as Server<"custom_OktaOpenIdConnectOAuth20Service"> - } } export class OktaOpenIdConnectOAuth20ServiceConfig { - basePath: - | Server<"OktaOpenIdConnectOAuth20Service"> - | Server<"custom_OktaOpenIdConnectOAuth20Service"> = + basePath: Server<"OktaOpenIdConnectOAuth20Service"> | string = OktaOpenIdConnectOAuth20ServiceServers.default() defaultHeaders: Record = {} } diff --git a/integration-tests/typescript-angular/src/generated/petstore-expanded.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/petstore-expanded.yaml/client.service.ts index f39736cf..9e877922 100644 --- a/integration-tests/typescript-angular/src/generated/petstore-expanded.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/petstore-expanded.yaml/client.service.ts @@ -27,16 +27,10 @@ export class SwaggerPetstoreServiceServers { throw new Error(`no matching server for url '${url}'`) } } - - static custom(url: string): Server<"custom_SwaggerPetstoreService"> { - return url as Server<"custom_SwaggerPetstoreService"> - } } export class SwaggerPetstoreServiceConfig { - basePath: - | Server<"SwaggerPetstoreService"> - | Server<"custom_SwaggerPetstoreService"> = + basePath: Server<"SwaggerPetstoreService"> | string = SwaggerPetstoreServiceServers.default() defaultHeaders: Record = {} } diff --git a/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts index 5413d09d..18b86b6a 100644 --- a/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts @@ -222,14 +222,10 @@ export class StripeApiServiceServers { } static readonly operations = StripeApiServiceServersOperations - - static custom(url: string): Server<"custom_StripeApiService"> { - return url as Server<"custom_StripeApiService"> - } } export class StripeApiServiceConfig { - basePath: Server<"StripeApiService"> | Server<"custom_StripeApiService"> = + basePath: Server<"StripeApiService"> | string = StripeApiServiceServers.default() defaultHeaders: Record = {} } diff --git a/integration-tests/typescript-angular/src/generated/todo-lists.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/todo-lists.yaml/client.service.ts index 55fac03d..c24fc60c 100644 --- a/integration-tests/typescript-angular/src/generated/todo-lists.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/todo-lists.yaml/client.service.ts @@ -143,16 +143,10 @@ export class TodoListsExampleApiServiceServers { } static readonly operations = TodoListsExampleApiServiceServersOperations - - static custom(url: string): Server<"custom_TodoListsExampleApiService"> { - return url as Server<"custom_TodoListsExampleApiService"> - } } export class TodoListsExampleApiServiceConfig { - basePath: - | Server<"TodoListsExampleApiService"> - | Server<"custom_TodoListsExampleApiService"> = + basePath: Server<"TodoListsExampleApiService"> | string = TodoListsExampleApiServiceServers.default() defaultHeaders: Record = {} } diff --git a/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts b/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts index 5920a19e..361de378 100644 --- a/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts @@ -358,14 +358,10 @@ export class GitHubV3RestApiServers { } static readonly operations = GitHubV3RestApiServersOperations - - static custom(url: string): Server<"custom_GitHubV3RestApi"> { - return url as Server<"custom_GitHubV3RestApi"> - } } export interface GitHubV3RestApiConfig extends AbstractAxiosConfig { - basePath: Server<"GitHubV3RestApi"> | Server<"custom_GitHubV3RestApi"> + basePath: Server<"GitHubV3RestApi"> | string } export class GitHubV3RestApi extends AbstractAxiosClient { @@ -20190,9 +20186,7 @@ export class GitHubV3RestApi extends AbstractAxiosClient { label?: string requestBody?: string }, - basePath?: - | Server<"reposUploadReleaseAsset_GitHubV3RestApi"> - | Server<"custom_GitHubV3RestApi">, + basePath?: Server<"reposUploadReleaseAsset_GitHubV3RestApi"> | string, timeout?: number, opts: AxiosRequestConfig = {}, ): Promise> { diff --git a/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts b/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts index 14f3eaa4..d4af95a0 100644 --- a/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts +++ b/integration-tests/typescript-axios/src/generated/azure-core-data-plane-service.tsp/client.ts @@ -49,16 +49,10 @@ export class ContosoWidgetManagerServers { throw new Error(`no matching server for url '${url}'`) } } - - static custom(url: string): Server<"custom_ContosoWidgetManager"> { - return url as Server<"custom_ContosoWidgetManager"> - } } export interface ContosoWidgetManagerConfig extends AbstractAxiosConfig { - basePath: - | Server<"ContosoWidgetManager"> - | Server<"custom_ContosoWidgetManager"> + basePath: Server<"ContosoWidgetManager"> | string } export class ContosoWidgetManager extends AbstractAxiosClient { diff --git a/integration-tests/typescript-axios/src/generated/azure-resource-manager.tsp/client.ts b/integration-tests/typescript-axios/src/generated/azure-resource-manager.tsp/client.ts index eced91e3..58479a50 100644 --- a/integration-tests/typescript-axios/src/generated/azure-resource-manager.tsp/client.ts +++ b/integration-tests/typescript-axios/src/generated/azure-resource-manager.tsp/client.ts @@ -38,16 +38,10 @@ export class ContosoProviderHubClientServers { throw new Error(`no matching server for url '${url}'`) } } - - static custom(url: string): Server<"custom_ContosoProviderHubClient"> { - return url as Server<"custom_ContosoProviderHubClient"> - } } export interface ContosoProviderHubClientConfig extends AbstractAxiosConfig { - basePath: - | Server<"ContosoProviderHubClient"> - | Server<"custom_ContosoProviderHubClient"> + basePath: Server<"ContosoProviderHubClient"> | string } export class ContosoProviderHubClient extends AbstractAxiosClient { diff --git a/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts b/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts index 0fcdb497..55b12817 100644 --- a/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/okta.idp.yaml/client.ts @@ -51,14 +51,10 @@ export class MyAccountManagementServers { throw new Error(`no matching server for url '${url}'`) } } - - static custom(url: string): Server<"custom_MyAccountManagement"> { - return url as Server<"custom_MyAccountManagement"> - } } export interface MyAccountManagementConfig extends AbstractAxiosConfig { - basePath: Server<"MyAccountManagement"> | Server<"custom_MyAccountManagement"> + basePath: Server<"MyAccountManagement"> | string } export class MyAccountManagement extends AbstractAxiosClient { diff --git a/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts b/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts index 169081ff..8a6c74fe 100644 --- a/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/okta.oauth.yaml/client.ts @@ -64,16 +64,10 @@ export class OktaOpenIdConnectOAuth20Servers { throw new Error(`no matching server for url '${url}'`) } } - - static custom(url: string): Server<"custom_OktaOpenIdConnectOAuth20"> { - return url as Server<"custom_OktaOpenIdConnectOAuth20"> - } } export interface OktaOpenIdConnectOAuth20Config extends AbstractAxiosConfig { - basePath: - | Server<"OktaOpenIdConnectOAuth20"> - | Server<"custom_OktaOpenIdConnectOAuth20"> + basePath: Server<"OktaOpenIdConnectOAuth20"> | string } export class OktaOpenIdConnectOAuth20 extends AbstractAxiosClient { diff --git a/integration-tests/typescript-axios/src/generated/petstore-expanded.yaml/client.ts b/integration-tests/typescript-axios/src/generated/petstore-expanded.yaml/client.ts index 6351c9db..ab7f4143 100644 --- a/integration-tests/typescript-axios/src/generated/petstore-expanded.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/petstore-expanded.yaml/client.ts @@ -30,14 +30,10 @@ export class SwaggerPetstoreServers { throw new Error(`no matching server for url '${url}'`) } } - - static custom(url: string): Server<"custom_SwaggerPetstore"> { - return url as Server<"custom_SwaggerPetstore"> - } } export interface SwaggerPetstoreConfig extends AbstractAxiosConfig { - basePath: Server<"SwaggerPetstore"> | Server<"custom_SwaggerPetstore"> + basePath: Server<"SwaggerPetstore"> | string } export class SwaggerPetstore extends AbstractAxiosClient { diff --git a/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts b/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts index 592c90cb..a228ee7c 100644 --- a/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts @@ -224,14 +224,10 @@ export class StripeApiServers { } static readonly operations = StripeApiServersOperations - - static custom(url: string): Server<"custom_StripeApi"> { - return url as Server<"custom_StripeApi"> - } } export interface StripeApiConfig extends AbstractAxiosConfig { - basePath: Server<"StripeApi"> | Server<"custom_StripeApi"> + basePath: Server<"StripeApi"> | string } export class StripeApi extends AbstractAxiosClient { @@ -10754,7 +10750,7 @@ export class StripeApi extends AbstractAxiosClient { | "terminal_reader_splashscreen" } }, - basePath?: Server<"postFiles_StripeApi"> | Server<"custom_StripeApi">, + basePath?: Server<"postFiles_StripeApi"> | string, timeout?: number, opts: AxiosRequestConfig = {}, ): Promise> { @@ -26394,9 +26390,7 @@ export class StripeApi extends AbstractAxiosClient { quote: string requestBody?: EmptyObject }, - basePath?: - | Server<"getQuotesQuotePdf_StripeApi"> - | Server<"custom_StripeApi">, + basePath?: Server<"getQuotesQuotePdf_StripeApi"> | string, timeout?: number, opts: AxiosRequestConfig = {}, ): Promise> { diff --git a/integration-tests/typescript-axios/src/generated/todo-lists.yaml/client.ts b/integration-tests/typescript-axios/src/generated/todo-lists.yaml/client.ts index 4719c9b9..b4550642 100644 --- a/integration-tests/typescript-axios/src/generated/todo-lists.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/todo-lists.yaml/client.ts @@ -142,14 +142,10 @@ export class TodoListsExampleApiServers { } static readonly operations = TodoListsExampleApiServersOperations - - static custom(url: string): Server<"custom_TodoListsExampleApi"> { - return url as Server<"custom_TodoListsExampleApi"> - } } export interface TodoListsExampleApiConfig extends AbstractAxiosConfig { - basePath: Server<"TodoListsExampleApi"> | Server<"custom_TodoListsExampleApi"> + basePath: Server<"TodoListsExampleApi"> | string } export class TodoListsExampleApi extends AbstractAxiosClient { @@ -302,9 +298,7 @@ export class TodoListsExampleApi extends AbstractAxiosClient { } async listAttachments( - basePath?: - | Server<"listAttachments_TodoListsExampleApi"> - | Server<"custom_TodoListsExampleApi">, + basePath?: Server<"listAttachments_TodoListsExampleApi"> | string, timeout?: number, opts: AxiosRequestConfig = {}, ): Promise> { @@ -327,9 +321,7 @@ export class TodoListsExampleApi extends AbstractAxiosClient { file?: unknown } }, - basePath?: - | Server<"uploadAttachment_TodoListsExampleApi"> - | Server<"custom_TodoListsExampleApi">, + basePath?: Server<"uploadAttachment_TodoListsExampleApi"> | string, timeout?: number, opts: AxiosRequestConfig = {}, ): Promise> { diff --git a/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts index ee285776..41d63329 100644 --- a/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/api.github.com.yaml/client.ts @@ -364,14 +364,10 @@ export class GitHubV3RestApiServers { } static readonly operations = GitHubV3RestApiServersOperations - - static custom(url: string): Server<"custom_GitHubV3RestApi"> { - return url as Server<"custom_GitHubV3RestApi"> - } } export interface GitHubV3RestApiConfig extends AbstractFetchClientConfig { - basePath: Server<"GitHubV3RestApi"> | Server<"custom_GitHubV3RestApi"> + basePath: Server<"GitHubV3RestApi"> | string } export class GitHubV3RestApi extends AbstractFetchClient { @@ -20372,7 +20368,7 @@ export class GitHubV3RestApi extends AbstractFetchClient { }, basePath: | Server<"reposUploadReleaseAsset_GitHubV3RestApi"> - | Server<"custom_GitHubV3RestApi"> = GitHubV3RestApiServers.operations + | string = GitHubV3RestApiServers.operations .reposUploadReleaseAsset() .build(), timeout?: number, diff --git a/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts b/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts index 48fe83e5..577d4cbe 100644 --- a/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts +++ b/integration-tests/typescript-fetch/src/generated/azure-core-data-plane-service.tsp/client.ts @@ -52,16 +52,10 @@ export class ContosoWidgetManagerServers { throw new Error(`no matching server for url '${url}'`) } } - - static custom(url: string): Server<"custom_ContosoWidgetManager"> { - return url as Server<"custom_ContosoWidgetManager"> - } } export interface ContosoWidgetManagerConfig extends AbstractFetchClientConfig { - basePath: - | Server<"ContosoWidgetManager"> - | Server<"custom_ContosoWidgetManager"> + basePath: Server<"ContosoWidgetManager"> | string } export class ContosoWidgetManager extends AbstractFetchClient { diff --git a/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts b/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts index fff6aaa5..f33ced0a 100644 --- a/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts +++ b/integration-tests/typescript-fetch/src/generated/azure-resource-manager.tsp/client.ts @@ -41,17 +41,11 @@ export class ContosoProviderHubClientServers { throw new Error(`no matching server for url '${url}'`) } } - - static custom(url: string): Server<"custom_ContosoProviderHubClient"> { - return url as Server<"custom_ContosoProviderHubClient"> - } } export interface ContosoProviderHubClientConfig extends AbstractFetchClientConfig { - basePath: - | Server<"ContosoProviderHubClient"> - | Server<"custom_ContosoProviderHubClient"> + basePath: Server<"ContosoProviderHubClient"> | string } export class ContosoProviderHubClient extends AbstractFetchClient { diff --git a/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts index 04d40055..9f3e412a 100644 --- a/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/okta.idp.yaml/client.ts @@ -53,14 +53,10 @@ export class MyAccountManagementServers { throw new Error(`no matching server for url '${url}'`) } } - - static custom(url: string): Server<"custom_MyAccountManagement"> { - return url as Server<"custom_MyAccountManagement"> - } } export interface MyAccountManagementConfig extends AbstractFetchClientConfig { - basePath: Server<"MyAccountManagement"> | Server<"custom_MyAccountManagement"> + basePath: Server<"MyAccountManagement"> | string } export class MyAccountManagement extends AbstractFetchClient { diff --git a/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts index b0c8645b..5957fd5e 100644 --- a/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/okta.oauth.yaml/client.ts @@ -67,17 +67,11 @@ export class OktaOpenIdConnectOAuth20Servers { throw new Error(`no matching server for url '${url}'`) } } - - static custom(url: string): Server<"custom_OktaOpenIdConnectOAuth20"> { - return url as Server<"custom_OktaOpenIdConnectOAuth20"> - } } export interface OktaOpenIdConnectOAuth20Config extends AbstractFetchClientConfig { - basePath: - | Server<"OktaOpenIdConnectOAuth20"> - | Server<"custom_OktaOpenIdConnectOAuth20"> + basePath: Server<"OktaOpenIdConnectOAuth20"> | string } export class OktaOpenIdConnectOAuth20 extends AbstractFetchClient { diff --git a/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts index 3102ef6e..8629f240 100644 --- a/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/petstore-expanded.yaml/client.ts @@ -32,14 +32,10 @@ export class SwaggerPetstoreServers { throw new Error(`no matching server for url '${url}'`) } } - - static custom(url: string): Server<"custom_SwaggerPetstore"> { - return url as Server<"custom_SwaggerPetstore"> - } } export interface SwaggerPetstoreConfig extends AbstractFetchClientConfig { - basePath: Server<"SwaggerPetstore"> | Server<"custom_SwaggerPetstore"> + basePath: Server<"SwaggerPetstore"> | string } export class SwaggerPetstore extends AbstractFetchClient { diff --git a/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts index 500eba2d..8ef2d32f 100644 --- a/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/stripe.yaml/client.ts @@ -227,14 +227,10 @@ export class StripeApiServers { } static readonly operations = StripeApiServersOperations - - static custom(url: string): Server<"custom_StripeApi"> { - return url as Server<"custom_StripeApi"> - } } export interface StripeApiConfig extends AbstractFetchClientConfig { - basePath: Server<"StripeApi"> | Server<"custom_StripeApi"> + basePath: Server<"StripeApi"> | string } export class StripeApi extends AbstractFetchClient { @@ -10558,9 +10554,7 @@ export class StripeApi extends AbstractFetchClient { }, basePath: | Server<"postFiles_StripeApi"> - | Server<"custom_StripeApi"> = StripeApiServers.operations - .postFiles() - .build(), + | string = StripeApiServers.operations.postFiles().build(), timeout?: number, opts: RequestInit = {}, ): Promise | Res>> { @@ -25992,9 +25986,7 @@ export class StripeApi extends AbstractFetchClient { }, basePath: | Server<"getQuotesQuotePdf_StripeApi"> - | Server<"custom_StripeApi"> = StripeApiServers.operations - .getQuotesQuotePdf() - .build(), + | string = StripeApiServers.operations.getQuotesQuotePdf().build(), timeout?: number, opts: RequestInit = {}, ): Promise | Res>> { diff --git a/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/client.ts index c914726d..f0ef26ea 100644 --- a/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/client.ts @@ -147,14 +147,10 @@ export class TodoListsExampleApiServers { } static readonly operations = TodoListsExampleApiServersOperations - - static custom(url: string): Server<"custom_TodoListsExampleApi"> { - return url as Server<"custom_TodoListsExampleApi"> - } } export interface TodoListsExampleApiConfig extends AbstractFetchClientConfig { - basePath: Server<"TodoListsExampleApi"> | Server<"custom_TodoListsExampleApi"> + basePath: Server<"TodoListsExampleApi"> | string } export class TodoListsExampleApi extends AbstractFetchClient { @@ -299,7 +295,7 @@ export class TodoListsExampleApi extends AbstractFetchClient { async listAttachments( basePath: | Server<"listAttachments_TodoListsExampleApi"> - | Server<"custom_TodoListsExampleApi"> = TodoListsExampleApiServers.operations + | string = TodoListsExampleApiServers.operations .listAttachments() .build(), timeout?: number, @@ -319,7 +315,7 @@ export class TodoListsExampleApi extends AbstractFetchClient { }, basePath: | Server<"uploadAttachment_TodoListsExampleApi"> - | Server<"custom_TodoListsExampleApi"> = TodoListsExampleApiServers.operations + | string = TodoListsExampleApiServers.operations .uploadAttachment() .build(), timeout?: number, diff --git a/packages/openapi-code-generator/src/typescript/common/client-builder.ts b/packages/openapi-code-generator/src/typescript/common/client-builder.ts index 872a1d8a..388548c1 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-builder.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-builder.ts @@ -40,10 +40,7 @@ export abstract class TypescriptClientBuilder implements ICompilable { this.config.enableTypedBasePaths && this.clientServersBuilder.hasServers ) { - return union( - this.clientServersBuilder.typeForDefault(), - this.clientServersBuilder.typeForCustom(), - ) + return union(this.clientServersBuilder.typeForDefault(), "string") } return "" diff --git a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.spec.ts b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.spec.ts index 25673c6d..c7039d5e 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.spec.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.spec.ts @@ -86,7 +86,7 @@ describe("typescript/common/client-servers-builder", () => { ]) }) - it("produces a specific, and custom", () => { + it("produces a server", () => { expect(result.output).toMatchInlineSnapshot(` "export class UnitTestServers { static default(): Server<"UnitTest"> { @@ -126,10 +126,6 @@ describe("typescript/common/client-servers-builder", () => { throw new Error(\`no matching server for url '\${url}'\`) } } - - static custom(url: string): Server<"custom_UnitTest"> { - return url as Server<"custom_UnitTest"> - } } " `) @@ -197,7 +193,7 @@ describe("typescript/common/client-servers-builder", () => { ) }) - it("produces a specific, custom, and operations", () => { + it("produces a server, and operations", () => { expect(result.output).toMatchInlineSnapshot(` "export class UnitTestServersOperations { static testOperation( @@ -278,10 +274,6 @@ describe("typescript/common/client-servers-builder", () => { } static readonly operations = UnitTestServersOperations - - static custom(url: string): Server<"custom_UnitTest"> { - return url as Server<"custom_UnitTest"> - } } " `) diff --git a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts index 619ab0d8..7cb50194 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts @@ -15,14 +15,51 @@ export class ClientServersBuilder implements ICompilable { readonly name: string, readonly servers: IRServer[], readonly imports: ImportBuilder, - ) {} + ) { + for (const server of servers) { + this.validateServer(server) + } + } addOperation(operation: Pick) { if (operation.servers.length) { + for (const server of operation.servers) { + this.validateServer(server) + } this.operations.push(operation) } } + private validateServer(server: IRServer) { + const regex = /\{([^{}]+)}/g + const placeholders: string[] = [] + let match: RegExpExecArray | null + // biome-ignore lint/suspicious/noAssignInExpressions: + while ((match = regex.exec(server.url)) !== null) { + if (match[1]) { + placeholders.push(match[1]) + } + } + + const variables = Object.keys(server.variables) + + for (const placeholder of placeholders) { + if (!variables.includes(placeholder)) { + throw new Error( + `server '${server.url}' has placeholder '${placeholder}' but no variable with that name`, + ) + } + } + + for (const variable of variables) { + if (!placeholders.includes(variable)) { + throw new Error( + `server '${server.url}' has variable '${variable}' but no placeholder with that name`, + ) + } + } + } + private toParams( variables: { [k: string]: IRServerVariable @@ -117,10 +154,6 @@ export class ClientServersBuilder implements ICompilable { return `Server<"${this.name}">` } - typeForCustom() { - return `Server<"custom_${this.name}">` - } - defaultForOperationId(operationId: string) { const operation = this.operations.find( (it) => it.operationId === operationId, @@ -177,10 +210,6 @@ export class ClientServersBuilder implements ICompilable { ? `static readonly operations = ${this.classExportName}Operations` : "" } - - static custom(url: string): ${this.typeForCustom()} { - return (url as ${this.typeForCustom()}) - } } ` } diff --git a/packages/openapi-code-generator/src/typescript/typescript-axios/typescript-axios-client-builder.ts b/packages/openapi-code-generator/src/typescript/typescript-axios/typescript-axios-client-builder.ts index c5400b1c..8c656aa1 100644 --- a/packages/openapi-code-generator/src/typescript/typescript-axios/typescript-axios-client-builder.ts +++ b/packages/openapi-code-generator/src/typescript/typescript-axios/typescript-axios-client-builder.ts @@ -93,7 +93,7 @@ export class TypescriptAxiosClientBuilder extends TypescriptClientBuilder { name: "basePath", type: union( this.clientServersBuilder.typeForOperationId(operationId), - this.clientServersBuilder.typeForCustom(), + "string", ), required: false, } diff --git a/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts b/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts index b1b4a7f2..d44ca12d 100644 --- a/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts +++ b/packages/openapi-code-generator/src/typescript/typescript-fetch/typescript-fetch-client-builder.ts @@ -98,7 +98,7 @@ export class TypescriptFetchClientBuilder extends TypescriptClientBuilder { name: "basePath", type: union( this.clientServersBuilder.typeForOperationId(operationId), - this.clientServersBuilder.typeForCustom(), + "string", ), default: this.clientServersBuilder.defaultForOperationId(operationId), From 998937d9be76fc94bd4fe191f0c203537dffb55e Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Fri, 6 Dec 2024 11:00:45 +0000 Subject: [PATCH 17/24] fix: apply default to all clients --- .../api.github.com.yaml/client.service.ts | 23 ++++-- .../generated/stripe.yaml/client.service.ts | 74 +++++++++++-------- .../todo-lists.yaml/client.service.ts | 25 +++++-- .../generated/api.github.com.yaml/client.ts | 6 +- .../src/generated/stripe.yaml/client.ts | 8 +- .../src/generated/todo-lists.yaml/client.ts | 12 ++- .../angular-service-builder.ts | 18 ++++- .../typescript-axios-client-builder.ts | 3 +- 8 files changed, 115 insertions(+), 54 deletions(-) diff --git a/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts index 5fffc42e..e7983805 100644 --- a/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts @@ -20135,14 +20135,21 @@ export class GitHubV3RestApiService { ) } - reposUploadReleaseAsset(p: { - owner: string - repo: string - releaseId: number - name: string - label?: string - requestBody?: string - }): Observable< + reposUploadReleaseAsset( + p: { + owner: string + repo: string + releaseId: number + name: string + label?: string + requestBody?: string + }, + basePath: + | Server<"reposUploadReleaseAsset_GitHubV3RestApiService"> + | string = GitHubV3RestApiServiceServers.operations + .reposUploadReleaseAsset() + .build(), + ): Observable< | (HttpResponse & { status: 201 }) | (HttpResponse & { status: 422 }) | HttpResponse diff --git a/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts index 18b86b6a..d7bbd394 100644 --- a/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts @@ -10882,33 +10882,38 @@ export class StripeApiService { ) } - postFiles(p: { - requestBody: { - expand?: string[] - file: string - file_link_data?: { - create: boolean - expires_at?: number - metadata?: - | { - [key: string]: string | undefined - } - | "" - } - purpose: - | "account_requirement" - | "additional_verification" - | "business_icon" - | "business_logo" - | "customer_signature" - | "dispute_evidence" - | "identity_document" - | "issuing_regulatory_reporting" - | "pci_document" - | "tax_document_user_upload" - | "terminal_reader_splashscreen" - } - }): Observable< + postFiles( + p: { + requestBody: { + expand?: string[] + file: string + file_link_data?: { + create: boolean + expires_at?: number + metadata?: + | { + [key: string]: string | undefined + } + | "" + } + purpose: + | "account_requirement" + | "additional_verification" + | "business_icon" + | "business_logo" + | "customer_signature" + | "dispute_evidence" + | "identity_document" + | "issuing_regulatory_reporting" + | "pci_document" + | "tax_document_user_upload" + | "terminal_reader_splashscreen" + } + }, + basePath: + | Server<"postFiles_StripeApiService"> + | string = StripeApiServiceServers.operations.postFiles().build(), + ): Observable< | (HttpResponse & { status: 200 }) | (HttpResponse & { status: StatusCode }) | HttpResponse @@ -26613,11 +26618,16 @@ export class StripeApiService { ) } - getQuotesQuotePdf(p: { - expand?: string[] - quote: string - requestBody?: EmptyObject - }): Observable< + getQuotesQuotePdf( + p: { + expand?: string[] + quote: string + requestBody?: EmptyObject + }, + basePath: + | Server<"getQuotesQuotePdf_StripeApiService"> + | string = StripeApiServiceServers.operations.getQuotesQuotePdf().build(), + ): Observable< | (HttpResponse & { status: 200 }) | (HttpResponse & { status: StatusCode }) | HttpResponse diff --git a/integration-tests/typescript-angular/src/generated/todo-lists.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/todo-lists.yaml/client.service.ts index c24fc60c..fa9fc9aa 100644 --- a/integration-tests/typescript-angular/src/generated/todo-lists.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/todo-lists.yaml/client.service.ts @@ -359,7 +359,13 @@ export class TodoListsExampleApiService { ) } - listAttachments(): Observable< + listAttachments( + basePath: + | Server<"listAttachments_TodoListsExampleApiService"> + | string = TodoListsExampleApiServiceServers.operations + .listAttachments() + .build(), + ): Observable< (HttpResponse & { status: 200 }) | HttpResponse > { return this.httpClient.request( @@ -372,11 +378,18 @@ export class TodoListsExampleApiService { ) } - uploadAttachment(p: { - requestBody: { - file?: unknown - } - }): Observable< + uploadAttachment( + p: { + requestBody: { + file?: unknown + } + }, + basePath: + | Server<"uploadAttachment_TodoListsExampleApiService"> + | string = TodoListsExampleApiServiceServers.operations + .uploadAttachment() + .build(), + ): Observable< (HttpResponse & { status: 202 }) | HttpResponse > { const headers = this._headers({ "Content-Type": "multipart/form-data" }) diff --git a/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts b/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts index 361de378..3a78e05b 100644 --- a/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts @@ -20186,7 +20186,11 @@ export class GitHubV3RestApi extends AbstractAxiosClient { label?: string requestBody?: string }, - basePath?: Server<"reposUploadReleaseAsset_GitHubV3RestApi"> | string, + basePath: + | Server<"reposUploadReleaseAsset_GitHubV3RestApi"> + | string = GitHubV3RestApiServers.operations + .reposUploadReleaseAsset() + .build(), timeout?: number, opts: AxiosRequestConfig = {}, ): Promise> { diff --git a/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts b/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts index a228ee7c..4abd1f18 100644 --- a/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts @@ -10750,7 +10750,9 @@ export class StripeApi extends AbstractAxiosClient { | "terminal_reader_splashscreen" } }, - basePath?: Server<"postFiles_StripeApi"> | string, + basePath: + | Server<"postFiles_StripeApi"> + | string = StripeApiServers.operations.postFiles().build(), timeout?: number, opts: AxiosRequestConfig = {}, ): Promise> { @@ -26390,7 +26392,9 @@ export class StripeApi extends AbstractAxiosClient { quote: string requestBody?: EmptyObject }, - basePath?: Server<"getQuotesQuotePdf_StripeApi"> | string, + basePath: + | Server<"getQuotesQuotePdf_StripeApi"> + | string = StripeApiServers.operations.getQuotesQuotePdf().build(), timeout?: number, opts: AxiosRequestConfig = {}, ): Promise> { diff --git a/integration-tests/typescript-axios/src/generated/todo-lists.yaml/client.ts b/integration-tests/typescript-axios/src/generated/todo-lists.yaml/client.ts index b4550642..d0e1ec53 100644 --- a/integration-tests/typescript-axios/src/generated/todo-lists.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/todo-lists.yaml/client.ts @@ -298,7 +298,11 @@ export class TodoListsExampleApi extends AbstractAxiosClient { } async listAttachments( - basePath?: Server<"listAttachments_TodoListsExampleApi"> | string, + basePath: + | Server<"listAttachments_TodoListsExampleApi"> + | string = TodoListsExampleApiServers.operations + .listAttachments() + .build(), timeout?: number, opts: AxiosRequestConfig = {}, ): Promise> { @@ -321,7 +325,11 @@ export class TodoListsExampleApi extends AbstractAxiosClient { file?: unknown } }, - basePath?: Server<"uploadAttachment_TodoListsExampleApi"> | string, + basePath: + | Server<"uploadAttachment_TodoListsExampleApi"> + | string = TodoListsExampleApiServers.operations + .uploadAttachment() + .build(), timeout?: number, opts: AxiosRequestConfig = {}, ): Promise> { diff --git a/packages/openapi-code-generator/src/typescript/typescript-angular/angular-service-builder.ts b/packages/openapi-code-generator/src/typescript/typescript-angular/angular-service-builder.ts index dcc43371..98e187a4 100644 --- a/packages/openapi-code-generator/src/typescript/typescript-angular/angular-service-builder.ts +++ b/packages/openapi-code-generator/src/typescript/typescript-angular/angular-service-builder.ts @@ -1,6 +1,7 @@ import {TypescriptClientBuilder} from "../common/client-builder" import type {ClientOperationBuilder} from "../common/client-operation-builder" import type {ImportBuilder} from "../common/import-builder" +import {union} from "../common/type-utils" import {buildMethod, routeToTemplateString} from "../common/typescript-common" export class AngularServiceBuilder extends TypescriptClientBuilder { @@ -15,7 +16,7 @@ export class AngularServiceBuilder extends TypescriptClientBuilder { } protected buildOperation(builder: ClientOperationBuilder): string { - const {operationId, route, method} = builder + const {operationId, route, method, hasServers} = builder const {requestBodyParameter} = builder.requestBodyAsParameter() const operationParameter = builder.methodParameter() @@ -61,7 +62,20 @@ return this.httpClient.request( return buildMethod({ name: operationId, - parameters: [operationParameter], + parameters: [ + operationParameter, + hasServers + ? { + name: "basePath", + type: union( + this.clientServersBuilder.typeForOperationId(operationId), + "string", + ), + default: + this.clientServersBuilder.defaultForOperationId(operationId), + } + : undefined, + ], returnType: `Observable<${returnType}>`, body, }) diff --git a/packages/openapi-code-generator/src/typescript/typescript-axios/typescript-axios-client-builder.ts b/packages/openapi-code-generator/src/typescript/typescript-axios/typescript-axios-client-builder.ts index 8c656aa1..0322ea42 100644 --- a/packages/openapi-code-generator/src/typescript/typescript-axios/typescript-axios-client-builder.ts +++ b/packages/openapi-code-generator/src/typescript/typescript-axios/typescript-axios-client-builder.ts @@ -95,7 +95,8 @@ export class TypescriptAxiosClientBuilder extends TypescriptClientBuilder { this.clientServersBuilder.typeForOperationId(operationId), "string", ), - required: false, + default: + this.clientServersBuilder.defaultForOperationId(operationId), } : undefined, {name: "timeout", type: "number", required: false}, From e51d4748d092f476f3baa3df3b81a05b1e9756b9 Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Fri, 6 Dec 2024 11:16:29 +0000 Subject: [PATCH 18/24] test: e2e --- .github/workflows/ci.yml | 6 ++--- e2e/openapi.yaml | 5 ++++ e2e/src/generated/client/axios/client.ts | 30 +++++++++++++++++++++++- e2e/src/generated/client/fetch/client.ts | 30 +++++++++++++++++++++++- e2e/src/index.axios.spec.ts | 5 +++- e2e/src/index.fetch.spec.ts | 6 +++-- package.json | 2 ++ scripts/ci-pipeline.sh | 8 +++---- 8 files changed, 79 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c6fa96a3..269f8c82 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,10 +51,8 @@ jobs: - run: yarn integration:generate - run: yarn integration:validate - - run: yarn workspace e2e clean - - run: yarn workspace e2e generate - - run: yarn workspace e2e build - - run: yarn workspace e2e test + - run: yarn e2e:generate + - run: yarn e2e:validate - name: Check for uncommitted changes run: ./scripts/assert-clean-working-directory.sh diff --git a/e2e/openapi.yaml b/e2e/openapi.yaml index e4ad0beb..332974a9 100644 --- a/e2e/openapi.yaml +++ b/e2e/openapi.yaml @@ -5,6 +5,11 @@ info: tags: - name: request headers - name: validation +servers: + - url: http://localhost:{port} + variables: + port: + default: '8080' paths: /headers/undeclared: get: diff --git a/e2e/src/generated/client/axios/client.ts b/e2e/src/generated/client/axios/client.ts index af93dd71..f87af6c4 100644 --- a/e2e/src/generated/client/axios/client.ts +++ b/e2e/src/generated/client/axios/client.ts @@ -10,10 +10,38 @@ import { import { AbstractAxiosClient, AbstractAxiosConfig, + Server, } from "@nahkies/typescript-axios-runtime/main" import { AxiosRequestConfig, AxiosResponse } from "axios" -export interface E2ETestClientConfig extends AbstractAxiosConfig {} +export class E2ETestClientServers { + static default(): Server<"E2ETestClient"> { + return E2ETestClientServers.server().build() + } + + static server(url: "http://localhost:{port}" = "http://localhost:{port}"): { + build: (port?: string) => Server<"E2ETestClient"> + } { + switch (url) { + case "http://localhost:{port}": + return { + build(port = "8080"): Server<"E2ETestClient"> { + return "http://localhost:{port}".replace( + "{port}", + port, + ) as Server<"E2ETestClient"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) + } + } +} + +export interface E2ETestClientConfig extends AbstractAxiosConfig { + basePath: Server<"E2ETestClient"> | string +} export class E2ETestClient extends AbstractAxiosClient { constructor(config: E2ETestClientConfig) { diff --git a/e2e/src/generated/client/fetch/client.ts b/e2e/src/generated/client/fetch/client.ts index 191cec0c..926574f4 100644 --- a/e2e/src/generated/client/fetch/client.ts +++ b/e2e/src/generated/client/fetch/client.ts @@ -11,10 +11,38 @@ import { AbstractFetchClient, AbstractFetchClientConfig, Res, + Server, TypedFetchResponse, } from "@nahkies/typescript-fetch-runtime/main" -export interface E2ETestClientConfig extends AbstractFetchClientConfig {} +export class E2ETestClientServers { + static default(): Server<"E2ETestClient"> { + return E2ETestClientServers.server().build() + } + + static server(url: "http://localhost:{port}" = "http://localhost:{port}"): { + build: (port?: string) => Server<"E2ETestClient"> + } { + switch (url) { + case "http://localhost:{port}": + return { + build(port = "8080"): Server<"E2ETestClient"> { + return "http://localhost:{port}".replace( + "{port}", + port, + ) as Server<"E2ETestClient"> + }, + } + + default: + throw new Error(`no matching server for url '${url}'`) + } + } +} + +export interface E2ETestClientConfig extends AbstractFetchClientConfig { + basePath: Server<"E2ETestClient"> | string +} export class E2ETestClient extends AbstractFetchClient { constructor(config: E2ETestClientConfig) { diff --git a/e2e/src/index.axios.spec.ts b/e2e/src/index.axios.spec.ts index ea620b41..a3e5940e 100644 --- a/e2e/src/index.axios.spec.ts +++ b/e2e/src/index.axios.spec.ts @@ -1,6 +1,7 @@ import type {Server} from "node:http" import {beforeAll, describe, expect, it} from "@jest/globals" import {ApiClient} from "./generated/client/axios/client" +import {E2ETestClientServers} from "./generated/client/fetch/client" import {main} from "./index" import {numberBetween} from "./test-utils" @@ -11,7 +12,9 @@ describe("e2e - typescript-axios client", () => { beforeAll(async () => { const args = await main() client = new ApiClient({ - basePath: `http://localhost:${args.address.port}`, + basePath: E2ETestClientServers.server().build( + args.address.port.toString(), + ), defaultHeaders: { Authorization: "Bearer default-header", }, diff --git a/e2e/src/index.fetch.spec.ts b/e2e/src/index.fetch.spec.ts index 1114b4a5..6e008409 100644 --- a/e2e/src/index.fetch.spec.ts +++ b/e2e/src/index.fetch.spec.ts @@ -1,6 +1,6 @@ import type {Server} from "node:http" import {beforeAll, describe, expect, it} from "@jest/globals" -import {ApiClient} from "./generated/client/fetch/client" +import {ApiClient, E2ETestClientServers} from "./generated/client/fetch/client" import {main} from "./index" import {numberBetween} from "./test-utils" @@ -11,7 +11,9 @@ describe("e2e - typescript-fetch client", () => { beforeAll(async () => { const args = await main() client = new ApiClient({ - basePath: `http://localhost:${args.address.port}`, + basePath: E2ETestClientServers.server("http://localhost:{port}").build( + args.address.port.toString(), + ), defaultHeaders: { Authorization: "Bearer default-header", }, diff --git a/package.json b/package.json index 353ba7c8..aa5a8751 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,8 @@ "test": "NODE_OPTIONS=--experimental-vm-modules jest", "integration:generate": "node ./scripts/generate.mjs", "integration:validate": "lerna run validate --stream", + "e2e:generate": "yarn workspace e2e clean && yarn workspace e2e generate", + "e2e:validate": "yarn workspace e2e build && yarn workspace e2e test", "ci-build": "yarn build", "ci-test": "NODE_OPTIONS=--experimental-vm-modules jest --coverage", "ci-lint": "biome ci .", diff --git a/scripts/ci-pipeline.sh b/scripts/ci-pipeline.sh index edba7017..ff004371 100755 --- a/scripts/ci-pipeline.sh +++ b/scripts/ci-pipeline.sh @@ -4,12 +4,12 @@ set -ex yarn ci-build yarn ci-test + yarn lint yarn format + yarn integration:generate yarn integration:validate -yarn workspace e2e clean -yarn workspace e2e generate -yarn workspace e2e build -yarn workspace e2e test +yarn e2e:generate +yarn e2e:validate From a6f345676b37f3825a36df711dcd29412e62462e Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Fri, 6 Dec 2024 11:21:58 +0000 Subject: [PATCH 19/24] fix: validate a default is present --- .../src/typescript/common/client-servers-builder.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts index 7cb50194..2ed8d73e 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts @@ -57,6 +57,12 @@ export class ClientServersBuilder implements ICompilable { `server '${server.url}' has variable '${variable}' but no placeholder with that name`, ) } + + if (server.variables[variable]?.default === undefined) { + throw new Error( + `server '${server.url}' has variable '${variable}' with no default value`, + ) + } } } From 57a144f0fb4a8646bd08c2b325e1d4826d18b5a8 Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Fri, 6 Dec 2024 11:22:59 +0000 Subject: [PATCH 20/24] fix: revert --- .../typescript-axios/src/uniform-github-repositories.ts | 7 ++----- .../typescript-fetch/src/uniform-github-repositories.ts | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/integration-tests/typescript-axios/src/uniform-github-repositories.ts b/integration-tests/typescript-axios/src/uniform-github-repositories.ts index 11295ca7..aba416f3 100644 --- a/integration-tests/typescript-axios/src/uniform-github-repositories.ts +++ b/integration-tests/typescript-axios/src/uniform-github-repositories.ts @@ -3,17 +3,14 @@ import dotenv from "dotenv" dotenv.config() import axios from "axios" -import { - ApiClient, - GitHubV3RestApiServers, -} from "./generated/api.github.com.yaml/client" +import {ApiClient} from "./generated/api.github.com.yaml/client" import type {t_repository} from "./generated/api.github.com.yaml/models" const client = new ApiClient({ axios: axios.create({ headers: {Authorization: `Bearer ${process.env.GITHUB_TOKEN}`}, }), - basePath: GitHubV3RestApiServers.default(), + basePath: "https://api.github.com", defaultTimeout: 5_000, defaultHeaders: {}, }) diff --git a/integration-tests/typescript-fetch/src/uniform-github-repositories.ts b/integration-tests/typescript-fetch/src/uniform-github-repositories.ts index a80aea74..2b9bef8b 100644 --- a/integration-tests/typescript-fetch/src/uniform-github-repositories.ts +++ b/integration-tests/typescript-fetch/src/uniform-github-repositories.ts @@ -2,16 +2,13 @@ import dotenv from "dotenv" dotenv.config() -import { - ApiClient, - GitHubV3RestApiServers, -} from "./generated/api.github.com.yaml/client" +import {ApiClient} from "./generated/api.github.com.yaml/client" import type {t_repository} from "./generated/api.github.com.yaml/models" const {writeHeapSnapshot} = require("node:v8") const client = new ApiClient({ - basePath: GitHubV3RestApiServers.default(), + basePath: "https://api.github.com", defaultHeaders: {Authorization: `Bearer ${process.env.GITHUB_TOKEN}`}, defaultTimeout: 5_000, }) From 9858061cc6d705eeb7f32a2fe28a79bc81891fb9 Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Fri, 6 Dec 2024 11:27:41 +0000 Subject: [PATCH 21/24] fix: use override in angular --- .../api.github.com.yaml/client.service.ts | 2 +- .../generated/stripe.yaml/client.service.ts | 18 +++++------- .../todo-lists.yaml/client.service.ts | 28 +++++++------------ .../generated/api.github.com.yaml/client.ts | 2 +- .../src/generated/stripe.yaml/client.ts | 4 +-- .../src/generated/todo-lists.yaml/client.ts | 4 +-- .../angular-service-builder.ts | 2 +- .../typescript-axios-client-builder.ts | 2 +- 8 files changed, 25 insertions(+), 37 deletions(-) diff --git a/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts index e7983805..44668f7a 100644 --- a/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/api.github.com.yaml/client.service.ts @@ -20162,7 +20162,7 @@ export class GitHubV3RestApiService { return this.httpClient.request( "POST", - this.config.basePath + + basePath + `/repos/${p["owner"]}/${p["repo"]}/releases/${p["releaseId"]}/assets`, { params, diff --git a/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts index d7bbd394..a2385e20 100644 --- a/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/stripe.yaml/client.service.ts @@ -10921,16 +10921,12 @@ export class StripeApiService { const headers = this._headers({ "Content-Type": "multipart/form-data" }) const body = p["requestBody"] - return this.httpClient.request( - "POST", - this.config.basePath + `/v1/files`, - { - headers, - body, - observe: "response", - reportProgress: false, - }, - ) + return this.httpClient.request("POST", basePath + `/v1/files`, { + headers, + body, + observe: "response", + reportProgress: false, + }) } getFilesFile(p: { @@ -26640,7 +26636,7 @@ export class StripeApiService { return this.httpClient.request( "GET", - this.config.basePath + `/v1/quotes/${p["quote"]}/pdf`, + basePath + `/v1/quotes/${p["quote"]}/pdf`, { params, headers, diff --git a/integration-tests/typescript-angular/src/generated/todo-lists.yaml/client.service.ts b/integration-tests/typescript-angular/src/generated/todo-lists.yaml/client.service.ts index fa9fc9aa..5fa9f96d 100644 --- a/integration-tests/typescript-angular/src/generated/todo-lists.yaml/client.service.ts +++ b/integration-tests/typescript-angular/src/generated/todo-lists.yaml/client.service.ts @@ -368,14 +368,10 @@ export class TodoListsExampleApiService { ): Observable< (HttpResponse & { status: 200 }) | HttpResponse > { - return this.httpClient.request( - "GET", - this.config.basePath + `/attachments`, - { - observe: "response", - reportProgress: false, - }, - ) + return this.httpClient.request("GET", basePath + `/attachments`, { + observe: "response", + reportProgress: false, + }) } uploadAttachment( @@ -395,16 +391,12 @@ export class TodoListsExampleApiService { const headers = this._headers({ "Content-Type": "multipart/form-data" }) const body = p["requestBody"] - return this.httpClient.request( - "POST", - this.config.basePath + `/attachments`, - { - headers, - body, - observe: "response", - reportProgress: false, - }, - ) + return this.httpClient.request("POST", basePath + `/attachments`, { + headers, + body, + observe: "response", + reportProgress: false, + }) } } diff --git a/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts b/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts index 3a78e05b..8ac205a4 100644 --- a/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/api.github.com.yaml/client.ts @@ -20206,7 +20206,7 @@ export class GitHubV3RestApi extends AbstractAxiosClient { url: url + query, method: "POST", data: body, - ...(basePath ? { baseURL: basePath } : {}), + baseURL: basePath, ...(timeout ? { timeout } : {}), ...opts, headers, diff --git a/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts b/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts index 4abd1f18..99db70c4 100644 --- a/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/stripe.yaml/client.ts @@ -10767,7 +10767,7 @@ export class StripeApi extends AbstractAxiosClient { url: url, method: "POST", data: body, - ...(basePath ? { baseURL: basePath } : {}), + baseURL: basePath, ...(timeout ? { timeout } : {}), ...opts, headers, @@ -26410,7 +26410,7 @@ export class StripeApi extends AbstractAxiosClient { url: url + query, method: "GET", data: body, - ...(basePath ? { baseURL: basePath } : {}), + baseURL: basePath, ...(timeout ? { timeout } : {}), ...opts, headers, diff --git a/integration-tests/typescript-axios/src/generated/todo-lists.yaml/client.ts b/integration-tests/typescript-axios/src/generated/todo-lists.yaml/client.ts index d0e1ec53..c754ea6a 100644 --- a/integration-tests/typescript-axios/src/generated/todo-lists.yaml/client.ts +++ b/integration-tests/typescript-axios/src/generated/todo-lists.yaml/client.ts @@ -312,7 +312,7 @@ export class TodoListsExampleApi extends AbstractAxiosClient { return this._request({ url: url, method: "GET", - ...(basePath ? { baseURL: basePath } : {}), + baseURL: basePath, ...(timeout ? { timeout } : {}), ...opts, headers, @@ -344,7 +344,7 @@ export class TodoListsExampleApi extends AbstractAxiosClient { url: url, method: "POST", data: body, - ...(basePath ? { baseURL: basePath } : {}), + baseURL: basePath, ...(timeout ? { timeout } : {}), ...opts, headers, diff --git a/packages/openapi-code-generator/src/typescript/typescript-angular/angular-service-builder.ts b/packages/openapi-code-generator/src/typescript/typescript-angular/angular-service-builder.ts index 98e187a4..b029be5f 100644 --- a/packages/openapi-code-generator/src/typescript/typescript-angular/angular-service-builder.ts +++ b/packages/openapi-code-generator/src/typescript/typescript-angular/angular-service-builder.ts @@ -47,7 +47,7 @@ export class AngularServiceBuilder extends TypescriptClientBuilder { return this.httpClient.request( "${method}", - this.config.basePath + \`${url}\`, { + ${hasServers ? "basePath" : "this.config.basePath"} + \`${url}\`, { ${[ queryString ? "params," : "", headers ? "headers," : "", diff --git a/packages/openapi-code-generator/src/typescript/typescript-axios/typescript-axios-client-builder.ts b/packages/openapi-code-generator/src/typescript/typescript-axios/typescript-axios-client-builder.ts index 0322ea42..30eacfcb 100644 --- a/packages/openapi-code-generator/src/typescript/typescript-axios/typescript-axios-client-builder.ts +++ b/packages/openapi-code-generator/src/typescript/typescript-axios/typescript-axios-client-builder.ts @@ -50,9 +50,9 @@ export class TypescriptAxiosClientBuilder extends TypescriptClientBuilder { `url: url ${queryString ? "+ query" : ""}`, `method: "${method}"`, requestBodyParameter ? "data: body" : "", + hasServers ? "baseURL: basePath" : undefined, // ensure compatibility with `exactOptionalPropertyTypes` compiler option // https://www.typescriptlang.org/tsconfig#exactOptionalPropertyTypes - hasServers ? "...(basePath? {baseURL: basePath} : {})" : undefined, "...(timeout ? {timeout} : {})", "...opts", "headers", From 463042271b0a2f63c359842b01b13d1a23e0d067 Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Fri, 6 Dec 2024 12:02:53 +0000 Subject: [PATCH 22/24] docs: update documentation --- e2e/openapi.yaml | 7 +- e2e/src/generated/client/axios/client.ts | 26 ++-- e2e/src/generated/client/fetch/client.ts | 26 ++-- e2e/src/index.axios.spec.ts | 4 +- e2e/src/index.fetch.spec.ts | 4 +- .../concepts/servers-object-handling.mdx | 112 ++++++++++++++++++ .../src/pages/overview/compatibility.mdx | 16 +-- .../src/pages/reference/cli-options.mdx | 10 +- 8 files changed, 177 insertions(+), 28 deletions(-) create mode 100644 packages/documentation/src/pages/guides/concepts/servers-object-handling.mdx diff --git a/e2e/openapi.yaml b/e2e/openapi.yaml index 332974a9..9119b34c 100644 --- a/e2e/openapi.yaml +++ b/e2e/openapi.yaml @@ -6,8 +6,13 @@ tags: - name: request headers - name: validation servers: - - url: http://localhost:{port} + - url: '{protocol}://{host}:{port}' variables: + host: + default: localhost + protocol: + enum: [http, https] + default: http port: default: '8080' paths: diff --git a/e2e/src/generated/client/axios/client.ts b/e2e/src/generated/client/axios/client.ts index f87af6c4..a41e1628 100644 --- a/e2e/src/generated/client/axios/client.ts +++ b/e2e/src/generated/client/axios/client.ts @@ -19,17 +19,27 @@ export class E2ETestClientServers { return E2ETestClientServers.server().build() } - static server(url: "http://localhost:{port}" = "http://localhost:{port}"): { - build: (port?: string) => Server<"E2ETestClient"> + static server( + url: "{protocol}://{host}:{port}" = "{protocol}://{host}:{port}", + ): { + build: ( + host?: string, + protocol?: "http" | "https", + port?: string, + ) => Server<"E2ETestClient"> } { switch (url) { - case "http://localhost:{port}": + case "{protocol}://{host}:{port}": return { - build(port = "8080"): Server<"E2ETestClient"> { - return "http://localhost:{port}".replace( - "{port}", - port, - ) as Server<"E2ETestClient"> + build( + host = "localhost", + protocol: "http" | "https" = "http", + port = "8080", + ): Server<"E2ETestClient"> { + return "{protocol}://{host}:{port}" + .replace("{host}", host) + .replace("{protocol}", protocol) + .replace("{port}", port) as Server<"E2ETestClient"> }, } diff --git a/e2e/src/generated/client/fetch/client.ts b/e2e/src/generated/client/fetch/client.ts index 926574f4..1f9fed18 100644 --- a/e2e/src/generated/client/fetch/client.ts +++ b/e2e/src/generated/client/fetch/client.ts @@ -20,17 +20,27 @@ export class E2ETestClientServers { return E2ETestClientServers.server().build() } - static server(url: "http://localhost:{port}" = "http://localhost:{port}"): { - build: (port?: string) => Server<"E2ETestClient"> + static server( + url: "{protocol}://{host}:{port}" = "{protocol}://{host}:{port}", + ): { + build: ( + host?: string, + protocol?: "http" | "https", + port?: string, + ) => Server<"E2ETestClient"> } { switch (url) { - case "http://localhost:{port}": + case "{protocol}://{host}:{port}": return { - build(port = "8080"): Server<"E2ETestClient"> { - return "http://localhost:{port}".replace( - "{port}", - port, - ) as Server<"E2ETestClient"> + build( + host = "localhost", + protocol: "http" | "https" = "http", + port = "8080", + ): Server<"E2ETestClient"> { + return "{protocol}://{host}:{port}" + .replace("{host}", host) + .replace("{protocol}", protocol) + .replace("{port}", port) as Server<"E2ETestClient"> }, } diff --git a/e2e/src/index.axios.spec.ts b/e2e/src/index.axios.spec.ts index a3e5940e..a434ce61 100644 --- a/e2e/src/index.axios.spec.ts +++ b/e2e/src/index.axios.spec.ts @@ -12,7 +12,9 @@ describe("e2e - typescript-axios client", () => { beforeAll(async () => { const args = await main() client = new ApiClient({ - basePath: E2ETestClientServers.server().build( + basePath: E2ETestClientServers.server("{protocol}://{host}:{port}").build( + undefined, + undefined, args.address.port.toString(), ), defaultHeaders: { diff --git a/e2e/src/index.fetch.spec.ts b/e2e/src/index.fetch.spec.ts index 6e008409..1a2d866a 100644 --- a/e2e/src/index.fetch.spec.ts +++ b/e2e/src/index.fetch.spec.ts @@ -11,7 +11,9 @@ describe("e2e - typescript-fetch client", () => { beforeAll(async () => { const args = await main() client = new ApiClient({ - basePath: E2ETestClientServers.server("http://localhost:{port}").build( + basePath: E2ETestClientServers.server("{protocol}://{host}:{port}").build( + undefined, + undefined, args.address.port.toString(), ), defaultHeaders: { diff --git a/packages/documentation/src/pages/guides/concepts/servers-object-handling.mdx b/packages/documentation/src/pages/guides/concepts/servers-object-handling.mdx new file mode 100644 index 00000000..252e4659 --- /dev/null +++ b/packages/documentation/src/pages/guides/concepts/servers-object-handling.mdx @@ -0,0 +1,112 @@ +import {Callout} from "nextra/components" + +# Guide to servers object handling + +OpenAPI 3 has a `servers` property that can be used to define the base url for the whole document, or +specific operations. This guide aims to explain how this is processed. + +You can find the specifications definition of the servers object [here](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.1.md#server-object) + +This is fully supported, including placeholder/variable substitution, and overriding. + + + The servers object is only used for **client SDK** templates. + + It doesn't really make sense in the context of a server template, and so is ignored. + + + +## Overview +Consider an example servers block: +```yaml +servers: + - url: '{protocol}://{host}:{port}' + variables: + host: + default: localhost + protocol: + enum: [http, https] + default: http + port: + default: '8080' +``` + +It defines a single server, with three variables. + +This will generate a `ApiClientServers` object that you can use to create a `basePath` like so: +```typescript +const client = new ApiClient({ +// basePath will be https://localhost:80 +basePath: ApiClientServers + .server("{protocol}://{host}:{port}") // the url template determines the build function + .build( + "https", // string literal union to form the enum + undefined, // pass undefined to take the default + "80", // override defaults + ) +}) +``` + +If you pass no args to build, the defaults from the specification are used: +```typescript +const client = new ApiClient({ +// basePath will be http://localhost:8080 +basePath: ApiClientServers + .server("{protocol}://{host}:{port}") + .build() +}) +``` + +You can also take the default (first) server like so: +```typescript +const client = new ApiClient({ +// basePath will be http://localhost:8080 +basePath: ApiClientServers + .server() + .build() +}) +``` + +## Operation specific overrides + +You can specify `servers` overrides at the path, or individual operation level. The most specific `servers` block +will be used for a given operation. + +For example, override the url for all operations under the `/attachments` route: +```yaml +paths: + /attachments: + servers: + - url: 'https://attachments.example.com' +``` + +When overrides are specified, an additional `basePath` positional argument will be added to the operation, defaulting +to the first overridden `server` with default placeholder values. + +```typescript +export class ApiClient { + async uploadAttachment( + p: { ... }, + // Overridden server param + basePath: + | Server<"uploadAttachment_ApiClient"> + | string = ApiClientServers.operations + .uploadAttachment() + .build(), + timeout?: number, + opts: RequestInit = {}, + ): Promise>> { + ... + } +} +``` + +As you can see the overrides for each operation are exposed as `ApiClientServers.operations.()` following +the same pattern as the root servers. + +## Configuration +This behavior is optional, and be turned off by passing: +``` +--enable-typed-base-paths=false +``` +See also [CLI reference](/reference/cli-options#--enable-typed-base-paths-client-sdks-only) diff --git a/packages/documentation/src/pages/overview/compatibility.mdx b/packages/documentation/src/pages/overview/compatibility.mdx index 869fe039..e9b55fe6 100644 --- a/packages/documentation/src/pages/overview/compatibility.mdx +++ b/packages/documentation/src/pages/overview/compatibility.mdx @@ -47,7 +47,7 @@ for each attribute for further details. | openapi | ✅ | Either 3.1.x or 3.0.x is supported | | info | __N/A__ | Ignored | | jsonSchemaDialect | 🚫 | Not yet supported | -| [servers](#server-object) | ✅ | Server URLs cam be usd to type client SDK base paths | +| [servers](#server-object) | ✅ | Server URLs can be usd to type client SDK base paths | | [paths](#paths-object) | ✅ | | | webhooks | 🚫 | Not yet supported. Emulate by defining as normal paths. | | [components](#components-object) | ✅ | | @@ -83,14 +83,16 @@ Example: | description | __N/A__ | Ignored | ### Server Object -Currently only used by the client SDKs to generate string literal types for `basePath` -for intellisense purposes. +Used by the client SDKs to generate builders for `basePath`'s and override parameters. + +See [servers object guide](../guides/concepts/servers-object-handling) for detailed explanation +of how this is handled. | Attribute | Supported | Notes | |:------------|:---------:|------------------------------------------------------------:| | url | ✅ | Optionally used to type client SDK `basePath` configuration | | description | __N/A__ | Ignored | -| variables | 🚫 | Not yet supported | +| variables | ✅ | | ### Components Object Technically you can define any "components" you like, and `$ref`s to them will work, regardless @@ -119,7 +121,7 @@ Paths are well supported. | [`{path}`](#path-item-object) | ✅ | | ### Path Item Object -All common http methods are supported. Overriding `servers` for individual paths is not supported. +All common http methods are supported. | Attribute | Supported | Notes | |:--------------------------------|:---------:|------------------:| @@ -132,7 +134,7 @@ All common http methods are supported. Overriding `servers` for individual paths | [head](#operation-object) | ✅ | | | [patch](#operation-object) | ✅ | | | trace | 🚫 | Not yet supported | -| servers | 🚫 | Not yet supported | +| [servers](#server-object) | ✅ | | | [parameters](#parameter-object) | ✅ | | ### Operation Object @@ -152,7 +154,7 @@ one will be generated from the path / http method, which is often overly verbose | callbacks | 🚫 | Not yet supported | | deprecated | 🚫 | Not yet supported | | security | 🚫 | Not yet supported | -| servers | 🚫 | Not yet supported | +| [servers](#server-object) | ✅ | | ### Parameter Object Whilst attributes like `style` and `explode` are not yet supported, for most common parameter use-cases diff --git a/packages/documentation/src/pages/reference/cli-options.mdx b/packages/documentation/src/pages/reference/cli-options.mdx index 6e72f37f..4cc399cc 100644 --- a/packages/documentation/src/pages/reference/cli-options.mdx +++ b/packages/documentation/src/pages/reference/cli-options.mdx @@ -88,8 +88,14 @@ Default `false` #### `--enable-typed-base-paths` (client sdks only) As environment variable `OPENAPI_ENABLE_TYPED_BASE_PATHS` -Controls whether to generate a union-type of string literals + `string` from the openapi specifications -array of server urls. Handy for intellisense. +Controls whether to generate a URL builder from the openapi specifications +array of server urls and placeholder variables. + +When disabled a plain `string` type is used for these parameters. + +See [servers object guide](../guides/concepts/servers-object-handling) for detailed explanation +of how this is handled. + Default: `true` From 7346967522ee98bd0824f1faaee49629095585a7 Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Fri, 6 Dec 2024 12:14:29 +0000 Subject: [PATCH 23/24] fix: support disabling properly --- .../concepts/servers-object-handling.mdx | 4 +++ packages/openapi-code-generator/src/cli.ts | 2 +- .../src/typescript/common/client-builder.ts | 10 ++----- .../common/client-servers-builder.ts | 28 ++++++++++++++++--- .../angular-service-builder.ts | 2 +- 5 files changed, 32 insertions(+), 14 deletions(-) diff --git a/packages/documentation/src/pages/guides/concepts/servers-object-handling.mdx b/packages/documentation/src/pages/guides/concepts/servers-object-handling.mdx index 252e4659..94949d64 100644 --- a/packages/documentation/src/pages/guides/concepts/servers-object-handling.mdx +++ b/packages/documentation/src/pages/guides/concepts/servers-object-handling.mdx @@ -109,4 +109,8 @@ This behavior is optional, and be turned off by passing: ``` --enable-typed-base-paths=false ``` + +When disabled `basePath: string` parameters will still be added to operations that have a `servers` override, but +no code based on the `url` or `variables` will be generated. + See also [CLI reference](/reference/cli-options#--enable-typed-base-paths-client-sdks-only) diff --git a/packages/openapi-code-generator/src/cli.ts b/packages/openapi-code-generator/src/cli.ts index c8a22e74..18a8a8b6 100644 --- a/packages/openapi-code-generator/src/cli.ts +++ b/packages/openapi-code-generator/src/cli.ts @@ -145,7 +145,7 @@ const program = new Command() ) .addOption( new Option( - "--enable-typed-base-paths", + "--enable-typed-base-paths [bool]", "(client sdks only) whether to produce a union type for the client basePath from the `servers` array", ) .env("OPENAPI_ENABLE_TYPED_BASE_PATHS") diff --git a/packages/openapi-code-generator/src/typescript/common/client-builder.ts b/packages/openapi-code-generator/src/typescript/common/client-builder.ts index 388548c1..862d9e9e 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-builder.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-builder.ts @@ -32,18 +32,12 @@ export abstract class TypescriptClientBuilder implements ICompilable { this.exportName, this.input.servers(), this.imports, + {enableTypedBasePaths: config.enableTypedBasePaths}, ) } basePathType() { - if ( - this.config.enableTypedBasePaths && - this.clientServersBuilder.hasServers - ) { - return union(this.clientServersBuilder.typeForDefault(), "string") - } - - return "" + return union(this.clientServersBuilder.typeForDefault(), "string") } add(operation: IROperation): void { diff --git a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts index 2ed8d73e..1566ff68 100644 --- a/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts +++ b/packages/openapi-code-generator/src/typescript/common/client-servers-builder.ts @@ -15,6 +15,7 @@ export class ClientServersBuilder implements ICompilable { readonly name: string, readonly servers: IRServer[], readonly imports: ImportBuilder, + readonly config = {enableTypedBasePaths: true}, ) { for (const server of servers) { this.validateServer(server) @@ -156,8 +157,18 @@ export class ClientServersBuilder implements ICompilable { return `${this.name}Servers` } + default() { + if (this.config.enableTypedBasePaths) { + return `${this.classExportName}.default()` + } + return "''" + } + typeForDefault() { - return `Server<"${this.name}">` + if (this.config.enableTypedBasePaths) { + return `Server<"${this.name}">` + } + return "string" } defaultForOperationId(operationId: string) { @@ -175,15 +186,24 @@ export class ClientServersBuilder implements ICompilable { throw new Error(`no server overrides for operation '${operationId}'`) } - return `${this.classExportName}.operations.${operationId}().build()` + if (this.config.enableTypedBasePaths) { + return `${this.classExportName}.operations.${operationId}().build()` + } + return "" } typeForOperationId(operationId: string) { - return `Server<"${operationId}_${this.name}">` + if (this.config.enableTypedBasePaths) { + return `Server<"${operationId}_${this.name}">` + } + return "string" } toString() { - if (!this.hasServers && !this.hasOperationServers) { + if ( + (!this.hasServers && !this.hasOperationServers) || + !this.config.enableTypedBasePaths + ) { return "" } diff --git a/packages/openapi-code-generator/src/typescript/typescript-angular/angular-service-builder.ts b/packages/openapi-code-generator/src/typescript/typescript-angular/angular-service-builder.ts index b029be5f..e75ab122 100644 --- a/packages/openapi-code-generator/src/typescript/typescript-angular/angular-service-builder.ts +++ b/packages/openapi-code-generator/src/typescript/typescript-angular/angular-service-builder.ts @@ -86,7 +86,7 @@ return this.httpClient.request( return ` export class ${clientName}Config { - basePath: ${basePathType ? basePathType : "string"} = ${this.clientServersBuilder.hasServers ? `${this.clientServersBuilder.classExportName}.default()` : "''"} + basePath: ${basePathType ? basePathType : "string"} = ${this.clientServersBuilder.default()} defaultHeaders: Record = {} } From cf77f6b2bdfc6fbeb9b8b3b885069339bb8de156 Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Fri, 6 Dec 2024 12:19:59 +0000 Subject: [PATCH 24/24] docs: tweak --- .../src/pages/overview/compatibility.mdx | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/documentation/src/pages/overview/compatibility.mdx b/packages/documentation/src/pages/overview/compatibility.mdx index e9b55fe6..8d642b79 100644 --- a/packages/documentation/src/pages/overview/compatibility.mdx +++ b/packages/documentation/src/pages/overview/compatibility.mdx @@ -47,7 +47,7 @@ for each attribute for further details. | openapi | ✅ | Either 3.1.x or 3.0.x is supported | | info | __N/A__ | Ignored | | jsonSchemaDialect | 🚫 | Not yet supported | -| [servers](#server-object) | ✅ | Server URLs can be usd to type client SDK base paths | +| [servers](#server-object) | ✅ | Used by client SDK templates | | [paths](#paths-object) | ✅ | | | webhooks | 🚫 | Not yet supported. Emulate by defining as normal paths. | | [components](#components-object) | ✅ | | @@ -123,19 +123,19 @@ Paths are well supported. ### Path Item Object All common http methods are supported. -| Attribute | Supported | Notes | -|:--------------------------------|:---------:|------------------:| -| summary | __N/A__ | Ignored | -| description | __N/A__ | Ignored | -| [get](#operation-object) | ✅ | | -| [put](#operation-object) | ✅ | | -| [post](#operation-object) | ✅ | | -| [delete](#operation-object) | ✅ | | -| [head](#operation-object) | ✅ | | -| [patch](#operation-object) | ✅ | | -| trace | 🚫 | Not yet supported | -| [servers](#server-object) | ✅ | | -| [parameters](#parameter-object) | ✅ | | +| Attribute | Supported | Notes | +|:--------------------------------|:---------:|-----------------------------:| +| summary | __N/A__ | Ignored | +| description | __N/A__ | Ignored | +| [get](#operation-object) | ✅ | | +| [put](#operation-object) | ✅ | | +| [post](#operation-object) | ✅ | | +| [delete](#operation-object) | ✅ | | +| [head](#operation-object) | ✅ | | +| [patch](#operation-object) | ✅ | | +| trace | 🚫 | Not yet supported | +| [servers](#server-object) | ✅ | Used by client SDK templates | +| [parameters](#parameter-object) | ✅ | | ### Operation Object Most things are supported. It's recommended you supply an `operationId` as otherwise @@ -154,7 +154,7 @@ one will be generated from the path / http method, which is often overly verbose | callbacks | 🚫 | Not yet supported | | deprecated | 🚫 | Not yet supported | | security | 🚫 | Not yet supported | -| [servers](#server-object) | ✅ | | +| [servers](#server-object) | ✅ | Used by client SDK templates | ### Parameter Object Whilst attributes like `style` and `explode` are not yet supported, for most common parameter use-cases