Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(cli): Consolidate OpenAPI settings #5338

Merged
merged 1 commit into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
@@ -1,4 +1,4 @@
export { type ParseOpenAPIOptions } from "./options";
export { parse, type Document, type OpenAPIDocument, type SpecImportSettings } from "./parse";
export { type ParseOpenAPIOptions, getParseOptions } from "./options";
export { parse, type Document, type OpenAPIDocument } from "./parse";
export { FernOpenAPIExtension, XFernStreaming, FERN_TYPE_EXTENSIONS } from "./openapi/v3/extensions/fernExtensions";
export * from "./asyncapi/v2";
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export interface ParseOpenAPIOptions {
/* Whether or not to disable OpenAPI example generation */
disableExamples: boolean;
/*
* Parses discriminated unions as undiscriminated unions with literals.
Expand All @@ -21,6 +22,14 @@ export interface ParseOpenAPIOptions {
inlinePathParameters: boolean;
/* Whether or not to preserve original schema Ids in the IR */
preserveSchemaIds: boolean;
/* Whether or not to parse object query parameters. */
objectQueryParameters: boolean;
/* Whether or not to use undiscriminated unions with literals. */
shouldUseUndiscriminatedUnionsWithLiterals: boolean;

// For now, we include an AsyncAPI-specific option here, but this is better
// handled with a discriminated union.
asyncApiNaming: "v1" | "v2";
}

export const DEFAULT_PARSE_OPENAPI_SETTINGS: ParseOpenAPIOptions = {
Expand All @@ -33,5 +42,58 @@ export const DEFAULT_PARSE_OPENAPI_SETTINGS: ParseOpenAPIOptions = {
respectReadonlySchemas: false,
onlyIncludeReferencedSchemas: false,
inlinePathParameters: false,
preserveSchemaIds: false
preserveSchemaIds: false,
objectQueryParameters: false,
shouldUseUndiscriminatedUnionsWithLiterals: false,
asyncApiNaming: "v1"
};

export function getParseOptions({
options,
overrides
}: {
options?: ParseOpenAPIOptions;
overrides?: Partial<ParseOpenAPIOptions>;
}): ParseOpenAPIOptions {
return {
disableExamples: overrides?.disableExamples ?? DEFAULT_PARSE_OPENAPI_SETTINGS.disableExamples,
discriminatedUnionV2:
overrides?.discriminatedUnionV2 ??
options?.discriminatedUnionV2 ??
DEFAULT_PARSE_OPENAPI_SETTINGS.discriminatedUnionV2,
useTitlesAsName:
overrides?.useTitlesAsName ?? options?.useTitlesAsName ?? DEFAULT_PARSE_OPENAPI_SETTINGS.useTitlesAsName,
audiences: overrides?.audiences ?? options?.audiences ?? DEFAULT_PARSE_OPENAPI_SETTINGS.audiences,
optionalAdditionalProperties:
overrides?.optionalAdditionalProperties ??
options?.optionalAdditionalProperties ??
DEFAULT_PARSE_OPENAPI_SETTINGS.optionalAdditionalProperties,
cooerceEnumsToLiterals:
overrides?.cooerceEnumsToLiterals ??
options?.cooerceEnumsToLiterals ??
DEFAULT_PARSE_OPENAPI_SETTINGS.cooerceEnumsToLiterals,
respectReadonlySchemas:
overrides?.respectReadonlySchemas ??
options?.respectReadonlySchemas ??
DEFAULT_PARSE_OPENAPI_SETTINGS.respectReadonlySchemas,
onlyIncludeReferencedSchemas:
overrides?.onlyIncludeReferencedSchemas ??
options?.onlyIncludeReferencedSchemas ??
DEFAULT_PARSE_OPENAPI_SETTINGS.onlyIncludeReferencedSchemas,
inlinePathParameters:
overrides?.inlinePathParameters ??
options?.inlinePathParameters ??
DEFAULT_PARSE_OPENAPI_SETTINGS.inlinePathParameters,
preserveSchemaIds: overrides?.preserveSchemaIds ?? DEFAULT_PARSE_OPENAPI_SETTINGS.preserveSchemaIds,
shouldUseUndiscriminatedUnionsWithLiterals:
overrides?.shouldUseUndiscriminatedUnionsWithLiterals ??
options?.shouldUseUndiscriminatedUnionsWithLiterals ??
DEFAULT_PARSE_OPENAPI_SETTINGS.shouldUseUndiscriminatedUnionsWithLiterals,
objectQueryParameters:
overrides?.objectQueryParameters ??
options?.objectQueryParameters ??
DEFAULT_PARSE_OPENAPI_SETTINGS.objectQueryParameters,
asyncApiNaming:
overrides?.asyncApiNaming ?? options?.asyncApiNaming ?? DEFAULT_PARSE_OPENAPI_SETTINGS.asyncApiNaming
};
}
75 changes: 10 additions & 65 deletions packages/cli/api-importers/openapi/openapi-ir-parser/src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { parseAsyncAPI } from "./asyncapi/parse";
import { AsyncAPIV2 } from "./asyncapi/v2";
import { generateIr as generateIrFromV2 } from "./openapi/v2/generateIr";
import { generateIr as generateIrFromV3 } from "./openapi/v3/generateIr";
import { DEFAULT_PARSE_OPENAPI_SETTINGS, ParseOpenAPIOptions } from "./options";
import { getParseOptions, ParseOpenAPIOptions } from "./options";

export type Document = OpenAPIDocument | AsyncAPIDocument;

Expand All @@ -16,28 +16,15 @@ export interface OpenAPIDocument {
value: OpenAPI.Document;
source?: OpenApiIrSource;
namespace?: string;
settings?: SpecImportSettings;
settings?: ParseOpenAPIOptions;
}

export interface AsyncAPIDocument {
type: "asyncapi";
value: AsyncAPIV2.Document;
source?: OpenApiIrSource;
namespace?: string;
settings?: SpecImportSettings;
}

export interface SpecImportSettings {
audiences: string[];
shouldUseTitleAsName: boolean;
shouldUseUndiscriminatedUnionsWithLiterals: boolean;
asyncApiNaming?: "v1" | "v2";
optionalAdditionalProperties: boolean;
cooerceEnumsToLiterals: boolean;
objectQueryParameters: boolean;
respectReadonlySchemas: boolean;
onlyIncludeReferencedSchemas: boolean;
inlinePathParameters: boolean;
settings?: ParseOpenAPIOptions;
}

export async function parse({
Expand Down Expand Up @@ -82,7 +69,7 @@ export async function parse({
const openapiIr = generateIrFromV3({
taskContext: context,
openApi: document.value,
options: getParseOptions({ specSettings: document.settings, overrides: options }),
options: getParseOptions({ options: document.settings, overrides: options }),
source,
namespace: document.namespace
});
Expand All @@ -91,7 +78,7 @@ export async function parse({
const openapiIr = await generateIrFromV2({
taskContext: context,
openApi: document.value,
options: getParseOptions({ specSettings: document.settings }),
options: getParseOptions({ options: document.settings, overrides: options }),
source,
namespace: document.namespace
});
Expand All @@ -103,9 +90,9 @@ export async function parse({
const parsedAsyncAPI = parseAsyncAPI({
document: document.value,
taskContext: context,
options: getParseOptions({ specSettings: document.settings }),
options: getParseOptions({ options: document.settings, overrides: options }),
source,
asyncApiOptions: getParseAsyncOptions({ specSettings: document.settings }),
asyncApiOptions: getParseAsyncOptions({ options: document.settings }),
namespace: document.namespace
});
if (parsedAsyncAPI.channel != null) {
Expand All @@ -126,57 +113,15 @@ export async function parse({
return ir;
}

export function getParseOptions({
specSettings,
overrides
}: {
specSettings?: SpecImportSettings;
overrides?: Partial<ParseOpenAPIOptions>;
}): ParseOpenAPIOptions {
return {
disableExamples: overrides?.disableExamples ?? DEFAULT_PARSE_OPENAPI_SETTINGS.disableExamples,
discriminatedUnionV2:
overrides?.discriminatedUnionV2 ??
specSettings?.shouldUseUndiscriminatedUnionsWithLiterals ??
DEFAULT_PARSE_OPENAPI_SETTINGS.discriminatedUnionV2,
useTitlesAsName:
overrides?.useTitlesAsName ??
specSettings?.shouldUseTitleAsName ??
DEFAULT_PARSE_OPENAPI_SETTINGS.useTitlesAsName,
audiences: overrides?.audiences ?? specSettings?.audiences ?? DEFAULT_PARSE_OPENAPI_SETTINGS.audiences,
optionalAdditionalProperties:
overrides?.optionalAdditionalProperties ??
specSettings?.optionalAdditionalProperties ??
DEFAULT_PARSE_OPENAPI_SETTINGS.optionalAdditionalProperties,
cooerceEnumsToLiterals:
overrides?.cooerceEnumsToLiterals ??
specSettings?.cooerceEnumsToLiterals ??
DEFAULT_PARSE_OPENAPI_SETTINGS.cooerceEnumsToLiterals,
respectReadonlySchemas:
overrides?.respectReadonlySchemas ??
specSettings?.respectReadonlySchemas ??
DEFAULT_PARSE_OPENAPI_SETTINGS.respectReadonlySchemas,
onlyIncludeReferencedSchemas:
overrides?.onlyIncludeReferencedSchemas ??
specSettings?.onlyIncludeReferencedSchemas ??
DEFAULT_PARSE_OPENAPI_SETTINGS.onlyIncludeReferencedSchemas,
inlinePathParameters:
overrides?.inlinePathParameters ??
specSettings?.inlinePathParameters ??
DEFAULT_PARSE_OPENAPI_SETTINGS.inlinePathParameters,
preserveSchemaIds: overrides?.preserveSchemaIds ?? DEFAULT_PARSE_OPENAPI_SETTINGS.preserveSchemaIds
};
}

function getParseAsyncOptions({
specSettings,
options,
overrides
}: {
specSettings?: SpecImportSettings;
options?: ParseOpenAPIOptions;
overrides?: Partial<ParseAsyncAPIOptions>;
}): ParseAsyncAPIOptions {
return {
naming: overrides?.naming ?? specSettings?.asyncApiNaming ?? DEFAULT_PARSE_ASYNCAPI_SETTINGS.naming
naming: overrides?.naming ?? options?.asyncApiNaming ?? DEFAULT_PARSE_ASYNCAPI_SETTINGS.naming
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
export interface ConvertOpenAPIOptions {
/**
* If true, each error will be made unique per endpoint. This is the preferred behavior for Docs.
* If false, error codes will be shared across endpoints. The side effect is that if more than one error schema is detected for each error code, then the error schema will default to unknown. This is the preferred behavior for SDKs.
*/
enableUniqueErrorsPerEndpoint: boolean;

/**
* If true, the converter will detect frequently headers and add extract them as global headers within
* the IR. This is primarily used for generating SDKs, but disabled for docs as it allows the documentation
*/
detectGlobalHeaders: boolean;

/**
* If true, the converter will generate complex query parameters in the generated Fern Definition.
*/
objectQueryParameters: boolean;

/**
* If true, the converter will respect readonly properties in OpenAPI schemas.
*/
respectReadonlySchemas: boolean;

/**
* If true, the converter will only include schemas referenced by endpoints.
*/
onlyIncludeReferencedSchemas: boolean;

/**
* If true, the converter will include path parameters in the in-lined request.
*/
inlinePathParameters: boolean;
}

export const DEFAULT_CONVERT_OPENAPI_OPTIONS: ConvertOpenAPIOptions = {
enableUniqueErrorsPerEndpoint: false,
detectGlobalHeaders: true,
objectQueryParameters: false,
respectReadonlySchemas: false,
onlyIncludeReferencedSchemas: false,
inlinePathParameters: false
};

export function getConvertOptions({
options,
overrides
}: {
options?: ConvertOpenAPIOptions;
overrides?: Partial<ConvertOpenAPIOptions>;
}): ConvertOpenAPIOptions {
return {
enableUniqueErrorsPerEndpoint:
overrides?.enableUniqueErrorsPerEndpoint ??
options?.enableUniqueErrorsPerEndpoint ??
DEFAULT_CONVERT_OPENAPI_OPTIONS.enableUniqueErrorsPerEndpoint,
detectGlobalHeaders:
overrides?.detectGlobalHeaders ??
options?.detectGlobalHeaders ??
DEFAULT_CONVERT_OPENAPI_OPTIONS.detectGlobalHeaders,
objectQueryParameters:
overrides?.objectQueryParameters ??
options?.objectQueryParameters ??
DEFAULT_CONVERT_OPENAPI_OPTIONS.objectQueryParameters,
respectReadonlySchemas:
overrides?.respectReadonlySchemas ??
options?.respectReadonlySchemas ??
DEFAULT_CONVERT_OPENAPI_OPTIONS.respectReadonlySchemas,
onlyIncludeReferencedSchemas:
overrides?.onlyIncludeReferencedSchemas ??
options?.onlyIncludeReferencedSchemas ??
DEFAULT_CONVERT_OPENAPI_OPTIONS.onlyIncludeReferencedSchemas,
inlinePathParameters:
overrides?.inlinePathParameters ??
options?.inlinePathParameters ??
DEFAULT_CONVERT_OPENAPI_OPTIONS.inlinePathParameters
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,48 +13,16 @@ import { TaskContext } from "@fern-api/task-context";
import { FernDefinitionBuilder, FernDefinitionBuilderImpl } from "@fern-api/importer-commons";
import { isSchemaEqual } from "@fern-api/openapi-ir";
import { State } from "./State";
import { ConvertOpenAPIOptions } from "./ConvertOpenAPIOptions";

export interface OpenApiIrConverterContextOpts {
taskContext: TaskContext;
ir: OpenApiIntermediateRepresentation;

/**
* If true, each error will be made unique per endpoint. This is the preferred behavior for Docs.
* If false, error codes will be shared across endpoints. The side effect is that if more than one error schema is detected for each error code, then the error schema will default to unknown. This is the preferred behavior for SDKs.
*/
enableUniqueErrorsPerEndpoint: boolean;

/**
* If true, the converter will detect frequently headers and add extract them as global headers within
* the IR. This is primarily used for generating SDKs, but disabled for docs as it allows the documentation
*/
detectGlobalHeaders: boolean;

options?: ConvertOpenAPIOptions;
authOverrides?: RawSchemas.WithAuthSchema;

environmentOverrides?: RawSchemas.WithEnvironmentsSchema;

globalHeaderOverrides?: RawSchemas.WithHeadersSchema;

/**
* If true, the converter will generate complex query parameters in the generated Fern Definition.
*/
objectQueryParameters: boolean;

/**
* If true, the converter will respect readonly properties in OpenAPI schemas.
*/
respectReadonlySchemas: boolean;

/**
* If true, the converter will only include schemas referenced by endpoints.
*/
onlyIncludeReferencedSchemas: boolean;

/**
* If true, the converter will include path parameters in the in-lined request.
*/
inlinePathParameters: boolean;
}

export class OpenApiIrConverterContext {
Expand Down Expand Up @@ -99,33 +67,28 @@ export class OpenApiIrConverterContext {
constructor({
taskContext,
ir,
enableUniqueErrorsPerEndpoint,
detectGlobalHeaders,
options,
environmentOverrides,
globalHeaderOverrides,
authOverrides,
objectQueryParameters,
respectReadonlySchemas,
onlyIncludeReferencedSchemas,
inlinePathParameters
authOverrides
}: OpenApiIrConverterContextOpts) {
this.logger = taskContext.logger;
this.taskContext = taskContext;
this.ir = ir;
this.enableUniqueErrorsPerEndpoint = enableUniqueErrorsPerEndpoint;
this.builder = new FernDefinitionBuilderImpl(enableUniqueErrorsPerEndpoint);
if (ir.title != null) {
this.builder.setDisplayName({ displayName: ir.title });
}
this.detectGlobalHeaders = detectGlobalHeaders;
this.environmentOverrides = environmentOverrides;
this.authOverrides = authOverrides;
this.globalHeaderOverrides = globalHeaderOverrides;
this.objectQueryParameters = objectQueryParameters;
this.respectReadonlySchemas = respectReadonlySchemas;
this.onlyIncludeReferencedSchemas = onlyIncludeReferencedSchemas;
this.inlinePathParameters = inlinePathParameters;
this.referencedSchemaIds = onlyIncludeReferencedSchemas ? new Set() : undefined;
this.detectGlobalHeaders = options?.detectGlobalHeaders ?? true;
this.objectQueryParameters = options?.objectQueryParameters ?? false;
this.respectReadonlySchemas = options?.respectReadonlySchemas ?? false;
this.onlyIncludeReferencedSchemas = options?.onlyIncludeReferencedSchemas ?? false;
this.inlinePathParameters = options?.inlinePathParameters ?? false;
this.referencedSchemaIds = options?.onlyIncludeReferencedSchemas ? new Set() : undefined;
this.enableUniqueErrorsPerEndpoint = options?.enableUniqueErrorsPerEndpoint ?? false;
this.builder = new FernDefinitionBuilderImpl(this.enableUniqueErrorsPerEndpoint);
if (ir.title != null) {
this.builder.setDisplayName({ displayName: ir.title });
}

const schemaByStatusCode: Record<number, Schema> = {};
if (!this.enableUniqueErrorsPerEndpoint) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { convert, type OpenApiConvertedFernDefinition } from "./convert";
export { getEndpointLocation } from "./utils/getEndpointLocation";
export { getConvertOptions, type ConvertOpenAPIOptions } from "./ConvertOpenAPIOptions";
Loading
Loading