Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ export const TypescriptCustomConfigSchema = z.strictObject({
// OAuth token override configuration
oauthTokenOverride: z.optional(z.boolean()),

// Auth configuration
// When true, generates a discriminated union type for auth where the user explicitly chooses
// the auth type via a `type` field, instead of the default ANY behavior where the SDK tries
// each auth method and catches errors.
useDiscriminatedUnionAuth: z.optional(z.boolean()),

// beta (not in docs)
includeContentHeadersOnFileDownloadResponse: z.optional(z.boolean()),
includeUtilsOnUnionMembers: z.optional(z.boolean()),
Expand Down
6 changes: 4 additions & 2 deletions generators/typescript/sdk/cli/src/SdkGeneratorCli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ export class SdkGeneratorCli extends AbstractGeneratorCli<SdkCustomConfig> {
formatter: parsed?.formatter ?? "biome",
generateSubpackageExports: parsed?.generateSubpackageExports ?? false,
offsetSemantics: parsed?.offsetSemantics ?? "item-index",
oauthTokenOverride: parsed?.oauthTokenOverride ?? false
oauthTokenOverride: parsed?.oauthTokenOverride ?? false,
useDiscriminatedUnionAuth: parsed?.useDiscriminatedUnionAuth ?? false
};

if (parsed?.noSerdeLayer === false && typeof parsed?.enableInlineTypes === "undefined") {
Expand Down Expand Up @@ -244,7 +245,8 @@ export class SdkGeneratorCli extends AbstractGeneratorCli<SdkCustomConfig> {
linter: customConfig.linter,
generateSubpackageExports: customConfig.generateSubpackageExports ?? false,
offsetSemantics: customConfig.offsetSemantics,
oauthTokenOverride: customConfig.oauthTokenOverride ?? false
oauthTokenOverride: customConfig.oauthTokenOverride ?? false,
useDiscriminatedUnionAuth: customConfig.useDiscriminatedUnionAuth ?? false
}
});
const typescriptProject = await sdkGenerator.generate();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,5 @@ export interface SdkCustomConfig {
generateSubpackageExports: boolean | undefined;
offsetSemantics: "item-index" | "page-index";
oauthTokenOverride: boolean | undefined;
useDiscriminatedUnionAuth: boolean | undefined;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
AuthProviderGenerator,
BasicAuthProviderGenerator,
BearerAuthProviderGenerator,
DiscriminatedUnionAuthProviderGenerator,
HeaderAuthProviderGenerator,
InferredAuthProviderGenerator,
OAuthAuthProviderGenerator
Expand All @@ -16,7 +17,7 @@ import {
export declare namespace AuthProvidersGenerator {
export interface Init {
ir: IntermediateRepresentation;
authScheme: AuthScheme | { type: "any" };
authScheme: AuthScheme | { type: "any" } | { type: "discriminatedUnion" };
neverThrowErrors: boolean;
includeSerdeLayer: boolean;
oauthTokenOverride: boolean;
Expand All @@ -38,6 +39,13 @@ export class AuthProvidersGenerator implements GeneratedFile<SdkContext> {
return new AnyAuthProviderGenerator({
ir
});
case "discriminatedUnion":
return new DiscriminatedUnionAuthProviderGenerator({
ir,
neverThrowErrors,
includeSerdeLayer,
oauthTokenOverride
});
case "inferred":
return new InferredAuthProviderGenerator({
ir,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export declare namespace BaseClientTypeGenerator {
ir: FernIr.IntermediateRepresentation;
omitFernHeaders: boolean;
oauthTokenOverride: boolean;
useDiscriminatedUnionAuth: boolean;
}
}

Expand All @@ -22,17 +23,20 @@ export class BaseClientTypeGenerator {
private readonly ir: FernIr.IntermediateRepresentation;
private readonly omitFernHeaders: boolean;
private readonly oauthTokenOverride: boolean;
private readonly useDiscriminatedUnionAuth: boolean;

constructor({
generateIdempotentRequestOptions,
ir,
omitFernHeaders,
oauthTokenOverride
oauthTokenOverride,
useDiscriminatedUnionAuth
}: BaseClientTypeGenerator.Init) {
this.generateIdempotentRequestOptions = generateIdempotentRequestOptions;
this.ir = ir;
this.omitFernHeaders = omitFernHeaders;
this.oauthTokenOverride = oauthTokenOverride;
this.useDiscriminatedUnionAuth = useDiscriminatedUnionAuth;
}

public writeToFile(context: SdkContext): void {
Expand Down Expand Up @@ -85,7 +89,11 @@ export type BaseClientOptions = {
const isAnyAuth = this.ir.auth.requirement === "ANY";

if (isAnyAuth) {
authOptionsTypes.push("AnyAuthProvider.AuthOptions");
if (this.useDiscriminatedUnionAuth) {
authOptionsTypes.push("DiscriminatedUnionAuthProvider.AuthOptions");
} else {
authOptionsTypes.push("AnyAuthProvider.AuthOptions");
}
} else {
for (const authScheme of this.ir.auth.schemes) {
const authOptionsType = this.getAuthOptionsTypeForScheme(authScheme, context);
Expand Down Expand Up @@ -239,60 +247,71 @@ export type NormalizedClientOptionsWithAuth<T extends BaseClientOptions> = Norma
const isAnyAuth = this.ir.auth.requirement === "ANY";

if (isAnyAuth) {
context.sourceFile.addImportDeclaration({
moduleSpecifier: "./auth/AnyAuthProvider.js",
namedImports: ["AnyAuthProvider"]
});

const providerImports: string[] = [];
const providerInstantiations: string[] = [];

for (const authScheme of this.ir.auth.schemes) {
if (authScheme.type === "bearer") {
context.sourceFile.addImportDeclaration({
moduleSpecifier: "./auth/BearerAuthProvider.js",
namedImports: ["BearerAuthProvider"]
});
providerImports.push("BearerAuthProvider");
providerInstantiations.push(
"if (BearerAuthProvider.canCreate(normalizedWithNoOpAuthProvider)) { authProviders.push(new BearerAuthProvider(normalizedWithNoOpAuthProvider)); }"
);
} else if (authScheme.type === "basic") {
context.sourceFile.addImportDeclaration({
moduleSpecifier: "./auth/BasicAuthProvider.js",
namedImports: ["BasicAuthProvider"]
});
providerImports.push("BasicAuthProvider");
providerInstantiations.push(
"if (BasicAuthProvider.canCreate(normalizedWithNoOpAuthProvider)) { authProviders.push(new BasicAuthProvider(normalizedWithNoOpAuthProvider)); }"
);
} else if (authScheme.type === "header") {
context.sourceFile.addImportDeclaration({
moduleSpecifier: "./auth/HeaderAuthProvider.js",
namedImports: ["HeaderAuthProvider"]
});
providerImports.push("HeaderAuthProvider");
providerInstantiations.push(
"if (HeaderAuthProvider.canCreate(normalizedWithNoOpAuthProvider)) { authProviders.push(new HeaderAuthProvider(normalizedWithNoOpAuthProvider)); }"
);
} else if (authScheme.type === "oauth") {
context.sourceFile.addImportDeclaration({
moduleSpecifier: "./auth/OAuthAuthProvider.js",
namedImports: ["OAuthAuthProvider"]
});
providerImports.push("OAuthAuthProvider");
const oauthCreation = this.oauthTokenOverride
? "if (OAuthAuthProvider.canCreate(normalizedWithNoOpAuthProvider)) { authProviders.push(OAuthAuthProvider.createInstance(normalizedWithNoOpAuthProvider)); }"
: "if (OAuthAuthProvider.canCreate(normalizedWithNoOpAuthProvider)) { authProviders.push(new OAuthAuthProvider(normalizedWithNoOpAuthProvider)); }";
providerInstantiations.push(oauthCreation);
if (this.useDiscriminatedUnionAuth) {
// Use discriminated union auth provider
context.sourceFile.addImportDeclaration({
moduleSpecifier: "./auth/DiscriminatedUnionAuthProvider.js",
namedImports: ["DiscriminatedUnionAuthProvider"]
});

authProviderCreation = `new DiscriminatedUnionAuthProvider(${OPTIONS_PARAMETER_NAME}.auth)`;
} else {
// Use the existing AnyAuthProvider approach
context.sourceFile.addImportDeclaration({
moduleSpecifier: "./auth/AnyAuthProvider.js",
namedImports: ["AnyAuthProvider"]
});

const providerImports: string[] = [];
const providerInstantiations: string[] = [];

for (const authScheme of this.ir.auth.schemes) {
if (authScheme.type === "bearer") {
context.sourceFile.addImportDeclaration({
moduleSpecifier: "./auth/BearerAuthProvider.js",
namedImports: ["BearerAuthProvider"]
});
providerImports.push("BearerAuthProvider");
providerInstantiations.push(
"if (BearerAuthProvider.canCreate(normalizedWithNoOpAuthProvider)) { authProviders.push(new BearerAuthProvider(normalizedWithNoOpAuthProvider)); }"
);
} else if (authScheme.type === "basic") {
context.sourceFile.addImportDeclaration({
moduleSpecifier: "./auth/BasicAuthProvider.js",
namedImports: ["BasicAuthProvider"]
});
providerImports.push("BasicAuthProvider");
providerInstantiations.push(
"if (BasicAuthProvider.canCreate(normalizedWithNoOpAuthProvider)) { authProviders.push(new BasicAuthProvider(normalizedWithNoOpAuthProvider)); }"
);
} else if (authScheme.type === "header") {
context.sourceFile.addImportDeclaration({
moduleSpecifier: "./auth/HeaderAuthProvider.js",
namedImports: ["HeaderAuthProvider"]
});
providerImports.push("HeaderAuthProvider");
providerInstantiations.push(
"if (HeaderAuthProvider.canCreate(normalizedWithNoOpAuthProvider)) { authProviders.push(new HeaderAuthProvider(normalizedWithNoOpAuthProvider)); }"
);
} else if (authScheme.type === "oauth") {
context.sourceFile.addImportDeclaration({
moduleSpecifier: "./auth/OAuthAuthProvider.js",
namedImports: ["OAuthAuthProvider"]
});
providerImports.push("OAuthAuthProvider");
const oauthCreation = this.oauthTokenOverride
? "if (OAuthAuthProvider.canCreate(normalizedWithNoOpAuthProvider)) { authProviders.push(OAuthAuthProvider.createInstance(normalizedWithNoOpAuthProvider)); }"
: "if (OAuthAuthProvider.canCreate(normalizedWithNoOpAuthProvider)) { authProviders.push(new OAuthAuthProvider(normalizedWithNoOpAuthProvider)); }";
providerInstantiations.push(oauthCreation);
}
}
}

authProviderCreation = `(() => {
authProviderCreation = `(() => {
const authProviders: ${getTextOfTsNode(context.coreUtilities.auth.AuthProvider._getReferenceToType())}[] = [];
${providerInstantiations.join("\n ")}
return new AnyAuthProvider(authProviders);
})()`;
}
} else {
for (const authScheme of this.ir.auth.schemes) {
if (authScheme.type === "bearer") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
AuthProviderInstance,
BasicAuthProviderInstance,
BearerAuthProviderInstance,
DiscriminatedUnionAuthProviderInstance,
HeaderAuthProviderInstance,
InferredAuthProviderInstance,
OAuthAuthProviderInstance
Expand Down Expand Up @@ -94,6 +95,7 @@ export declare namespace GeneratedSdkClientClassImpl {
parameterNaming: "originalName" | "wireValue" | "camelCase" | "snakeCase" | "default";
offsetSemantics: "item-index" | "page-index";
oauthTokenOverride: boolean;
useDiscriminatedUnionAuth: boolean;
}
}

Expand Down Expand Up @@ -165,7 +167,8 @@ export class GeneratedSdkClientClassImpl implements GeneratedSdkClientClass {
generateEndpointMetadata,
parameterNaming,
offsetSemantics,
oauthTokenOverride
oauthTokenOverride,
useDiscriminatedUnionAuth
}: GeneratedSdkClientClassImpl.Init) {
this.isRoot = isRoot;
this.intermediateRepresentation = intermediateRepresentation;
Expand Down Expand Up @@ -436,19 +439,24 @@ export class GeneratedSdkClientClassImpl implements GeneratedSdkClientClass {
}
});

for (const authScheme of authSchemes) {
if (isAnyAuth) {
const authProvider = getAuthProvider(authScheme);
anyAuthProviders.push(authProvider);
} else {
this.authProvider = getAuthProvider(authScheme);
break;
// If useDiscriminatedUnionAuth is enabled, use the discriminated union auth provider
if (isAnyAuth && useDiscriminatedUnionAuth) {
this.authProvider = new DiscriminatedUnionAuthProviderInstance(intermediateRepresentation);
} else {
for (const authScheme of authSchemes) {
if (isAnyAuth) {
const authProvider = getAuthProvider(authScheme);
anyAuthProviders.push(authProvider);
} else {
this.authProvider = getAuthProvider(authScheme);
break;
}
}
}

// After the loop, if isAnyAuth, create AnyAuthProviderInstance with all collected providers
if (isAnyAuth && anyAuthProviders.length > 0) {
this.authProvider = new AnyAuthProviderInstance(anyAuthProviders);
// After the loop, if isAnyAuth, create AnyAuthProviderInstance with all collected providers
if (isAnyAuth && anyAuthProviders.length > 0) {
this.authProvider = new AnyAuthProviderInstance(anyAuthProviders);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export declare namespace SdkClientClassGenerator {
parameterNaming: "originalName" | "wireValue" | "camelCase" | "snakeCase" | "default";
offsetSemantics: "item-index" | "page-index";
oauthTokenOverride: boolean;
useDiscriminatedUnionAuth: boolean;
}

export namespace generateService {
Expand Down Expand Up @@ -70,6 +71,7 @@ export class SdkClientClassGenerator {
private readonly parameterNaming: "originalName" | "wireValue" | "camelCase" | "snakeCase" | "default";
private readonly offsetSemantics: "item-index" | "page-index";
private readonly oauthTokenOverride: boolean;
private readonly useDiscriminatedUnionAuth: boolean;

constructor({
intermediateRepresentation,
Expand All @@ -96,7 +98,8 @@ export class SdkClientClassGenerator {
generateEndpointMetadata,
parameterNaming,
offsetSemantics,
oauthTokenOverride
oauthTokenOverride,
useDiscriminatedUnionAuth
}: SdkClientClassGenerator.Init) {
this.intermediateRepresentation = intermediateRepresentation;
this.errorResolver = errorResolver;
Expand All @@ -123,6 +126,7 @@ export class SdkClientClassGenerator {
this.parameterNaming = parameterNaming;
this.offsetSemantics = offsetSemantics;
this.oauthTokenOverride = oauthTokenOverride;
this.useDiscriminatedUnionAuth = useDiscriminatedUnionAuth;
}

public generateService({
Expand Down Expand Up @@ -159,7 +163,8 @@ export class SdkClientClassGenerator {
generateEndpointMetadata: this.generateEndpointMetadata,
parameterNaming: this.parameterNaming,
offsetSemantics: this.offsetSemantics,
oauthTokenOverride: this.oauthTokenOverride
oauthTokenOverride: this.oauthTokenOverride,
useDiscriminatedUnionAuth: this.useDiscriminatedUnionAuth
});
}
}
Loading
Loading