Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
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,11 @@ export const TypescriptCustomConfigSchema = z.strictObject({
// OAuth token override configuration
oauthTokenOverride: z.optional(z.boolean()),

// Auth configuration for ANY auth requirement
// v1 (default): SDK tries each auth method and catches errors
// v2: User explicitly chooses the auth type via a `type` field (discriminated union)
anyAuth: z.optional(z.enum(["v1", "v2"])),

// 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,
anyAuth: parsed?.anyAuth ?? "v1"
};

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,
anyAuth: customConfig.anyAuth ?? "v1"
}
});
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;
anyAuth: "v1" | "v2" | undefined;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { GeneratedFile, SdkContext } from "@fern-typescript/contexts";

import {
AnyAuthProviderGenerator,
AnyAuthV2ProviderGenerator,
AuthProviderGenerator,
BasicAuthProviderGenerator,
BearerAuthProviderGenerator,
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: "anyAuthV2" };
neverThrowErrors: boolean;
includeSerdeLayer: boolean;
oauthTokenOverride: boolean;
Expand All @@ -38,6 +39,13 @@ export class AuthProvidersGenerator implements GeneratedFile<SdkContext> {
return new AnyAuthProviderGenerator({
ir
});
case "anyAuthV2":
return new AnyAuthV2ProviderGenerator({
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;
anyAuth: "v1" | "v2";
}
}

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

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

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.anyAuth === "v2") {
authOptionsTypes.push("AnyAuthProvider.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.anyAuth === "v2") {
// Use AnyAuthProvider v2 (discriminated union style)
context.sourceFile.addImportDeclaration({
moduleSpecifier: "./auth/AnyAuthProvider.js",
namedImports: ["AnyAuthProvider"]
});

authProviderCreation = `new AnyAuthProvider(${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 @@ -45,6 +45,7 @@ import {
import { Code, code } from "ts-poet";
import {
AnyAuthProviderInstance,
AnyAuthV2ProviderInstance,
AuthProviderInstance,
BasicAuthProviderInstance,
BearerAuthProviderInstance,
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;
anyAuth: "v1" | "v2";
}
}

Expand Down Expand Up @@ -165,7 +167,8 @@ export class GeneratedSdkClientClassImpl implements GeneratedSdkClientClass {
generateEndpointMetadata,
parameterNaming,
offsetSemantics,
oauthTokenOverride
oauthTokenOverride,
anyAuth
}: 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 anyAuth is v2, use the AnyAuthV2 provider (discriminated union style)
if (isAnyAuth && anyAuth === "v2") {
this.authProvider = new AnyAuthV2ProviderInstance(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;
anyAuth: "v1" | "v2";
}

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 anyAuth: "v1" | "v2";

constructor({
intermediateRepresentation,
Expand All @@ -96,7 +98,8 @@ export class SdkClientClassGenerator {
generateEndpointMetadata,
parameterNaming,
offsetSemantics,
oauthTokenOverride
oauthTokenOverride,
anyAuth
}: 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.anyAuth = anyAuth;
}

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,
anyAuth: this.anyAuth
});
}
}
Loading
Loading