From c562a2ff367eeb65293199028665fa60a20b3f6b Mon Sep 17 00:00:00 2001 From: Deep Singhvi Date: Thu, 28 Mar 2024 20:28:00 -0400 Subject: [PATCH] (chore): introduce `x-fern-groups` and `x-fern-display-name` to plumb through display name (#3290) * (chore): introduce to plumb through display name * fix --- .../fern/definition/finalIr.yml | 9 ++++++ .../OpenApiIntermediateRepresentation.ts | 2 ++ .../resources/finalIr/types/SdkGroupInfo.ts | 8 +++++ .../sdk/api/resources/finalIr/types/index.ts | 1 + .../OpenApiIntermediateRepresentation.ts | 5 ++++ .../resources/finalIr/types/SdkGroupInfo.ts | 20 +++++++++++++ .../resources/finalIr/types/index.ts | 1 + .../__test__/FernDefinitionBuilder.test.ts | 1 + .../openapi-ir-to-fern/src/buildChannel.ts | 4 +++ .../openapi-ir-to-fern/src/buildServices.ts | 18 ++++++++++-- .../src/asyncapi/fernExtensions.ts | 5 ++++ .../cli/openapi-parser/src/asyncapi/parse.ts | 4 ++- .../cli/openapi-parser/src/openapi/parse.ts | 9 ++++-- .../openapi/v3/extensions/fernExtensions.ts | 16 ++++++++++ .../openapi/v3/extensions/getFernGroups.ts | 29 +++++++++++++++++++ .../src/openapi/v3/generateIr.ts | 8 +++++ 16 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 packages/cli/openapi-ir-sdk/src/sdk/api/resources/finalIr/types/SdkGroupInfo.ts create mode 100644 packages/cli/openapi-ir-sdk/src/sdk/serialization/resources/finalIr/types/SdkGroupInfo.ts create mode 100644 packages/cli/openapi-parser/src/openapi/v3/extensions/getFernGroups.ts diff --git a/packages/cli/openapi-ir-sdk/fern/definition/finalIr.yml b/packages/cli/openapi-ir-sdk/fern/definition/finalIr.yml index b119f1517ea..cf3703cba89 100644 --- a/packages/cli/openapi-ir-sdk/fern/definition/finalIr.yml +++ b/packages/cli/openapi-ir-sdk/fern/definition/finalIr.yml @@ -8,6 +8,10 @@ types: title: optional description: optional servers: list + groups: + docs: | + Top level group information populated through `x-fern-groups`. + type: map tags: Tags hasEndpointsMarkedInternal: boolean endpoints: list @@ -22,6 +26,11 @@ types: securitySchemes: map globalHeaders: optional> + SdkGroupInfo: + properties: + summary: optional + description: optional + GlobalHeader: properties: header: string diff --git a/packages/cli/openapi-ir-sdk/src/sdk/api/resources/finalIr/types/OpenApiIntermediateRepresentation.ts b/packages/cli/openapi-ir-sdk/src/sdk/api/resources/finalIr/types/OpenApiIntermediateRepresentation.ts index 06da4200c36..8b9ff95e2ea 100644 --- a/packages/cli/openapi-ir-sdk/src/sdk/api/resources/finalIr/types/OpenApiIntermediateRepresentation.ts +++ b/packages/cli/openapi-ir-sdk/src/sdk/api/resources/finalIr/types/OpenApiIntermediateRepresentation.ts @@ -8,6 +8,8 @@ export interface OpenApiIntermediateRepresentation { title: string | undefined; description: string | undefined; servers: FernOpenapiIr.Server[]; + /** Top level group information populated through `x-fern-groups`. */ + groups: Record; tags: FernOpenapiIr.Tags; hasEndpointsMarkedInternal: boolean; endpoints: FernOpenapiIr.Endpoint[]; diff --git a/packages/cli/openapi-ir-sdk/src/sdk/api/resources/finalIr/types/SdkGroupInfo.ts b/packages/cli/openapi-ir-sdk/src/sdk/api/resources/finalIr/types/SdkGroupInfo.ts new file mode 100644 index 00000000000..ff1ab652815 --- /dev/null +++ b/packages/cli/openapi-ir-sdk/src/sdk/api/resources/finalIr/types/SdkGroupInfo.ts @@ -0,0 +1,8 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export interface SdkGroupInfo { + summary: string | undefined; + description: string | undefined; +} diff --git a/packages/cli/openapi-ir-sdk/src/sdk/api/resources/finalIr/types/index.ts b/packages/cli/openapi-ir-sdk/src/sdk/api/resources/finalIr/types/index.ts index bfd2f7d512d..d4924a9b679 100644 --- a/packages/cli/openapi-ir-sdk/src/sdk/api/resources/finalIr/types/index.ts +++ b/packages/cli/openapi-ir-sdk/src/sdk/api/resources/finalIr/types/index.ts @@ -1,4 +1,5 @@ export * from "./OpenApiIntermediateRepresentation"; +export * from "./SdkGroupInfo"; export * from "./GlobalHeader"; export * from "./Tags"; export * from "./HttpError"; diff --git a/packages/cli/openapi-ir-sdk/src/sdk/serialization/resources/finalIr/types/OpenApiIntermediateRepresentation.ts b/packages/cli/openapi-ir-sdk/src/sdk/serialization/resources/finalIr/types/OpenApiIntermediateRepresentation.ts index 0667d3bbf76..d08e74467ca 100644 --- a/packages/cli/openapi-ir-sdk/src/sdk/serialization/resources/finalIr/types/OpenApiIntermediateRepresentation.ts +++ b/packages/cli/openapi-ir-sdk/src/sdk/serialization/resources/finalIr/types/OpenApiIntermediateRepresentation.ts @@ -13,6 +13,10 @@ export const OpenApiIntermediateRepresentation: core.serialization.ObjectSchema< title: core.serialization.string().optional(), description: core.serialization.string().optional(), servers: core.serialization.list(core.serialization.lazyObject(async () => (await import("../../..")).Server)), + groups: core.serialization.record( + core.serialization.string(), + core.serialization.lazyObject(async () => (await import("../../..")).SdkGroupInfo) + ), tags: core.serialization.lazyObject(async () => (await import("../../..")).Tags), hasEndpointsMarkedInternal: core.serialization.boolean(), endpoints: core.serialization.list(core.serialization.lazyObject(async () => (await import("../../..")).Endpoint)), @@ -49,6 +53,7 @@ export declare namespace OpenApiIntermediateRepresentation { title?: string | null; description?: string | null; servers: serializers.Server.Raw[]; + groups: Record; tags: serializers.Tags.Raw; hasEndpointsMarkedInternal: boolean; endpoints: serializers.Endpoint.Raw[]; diff --git a/packages/cli/openapi-ir-sdk/src/sdk/serialization/resources/finalIr/types/SdkGroupInfo.ts b/packages/cli/openapi-ir-sdk/src/sdk/serialization/resources/finalIr/types/SdkGroupInfo.ts new file mode 100644 index 00000000000..315bd444147 --- /dev/null +++ b/packages/cli/openapi-ir-sdk/src/sdk/serialization/resources/finalIr/types/SdkGroupInfo.ts @@ -0,0 +1,20 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../.."; +import * as FernOpenapiIr from "../../../../api"; +import * as core from "../../../../core"; + +export const SdkGroupInfo: core.serialization.ObjectSchema = + core.serialization.objectWithoutOptionalProperties({ + summary: core.serialization.string().optional(), + description: core.serialization.string().optional(), + }); + +export declare namespace SdkGroupInfo { + interface Raw { + summary?: string | null; + description?: string | null; + } +} diff --git a/packages/cli/openapi-ir-sdk/src/sdk/serialization/resources/finalIr/types/index.ts b/packages/cli/openapi-ir-sdk/src/sdk/serialization/resources/finalIr/types/index.ts index bfd2f7d512d..d4924a9b679 100644 --- a/packages/cli/openapi-ir-sdk/src/sdk/serialization/resources/finalIr/types/index.ts +++ b/packages/cli/openapi-ir-sdk/src/sdk/serialization/resources/finalIr/types/index.ts @@ -1,4 +1,5 @@ export * from "./OpenApiIntermediateRepresentation"; +export * from "./SdkGroupInfo"; export * from "./GlobalHeader"; export * from "./Tags"; export * from "./HttpError"; diff --git a/packages/cli/openapi-ir-to-fern/src/__test__/FernDefinitionBuilder.test.ts b/packages/cli/openapi-ir-to-fern/src/__test__/FernDefinitionBuilder.test.ts index b0ba7683a03..119c3efc93e 100644 --- a/packages/cli/openapi-ir-to-fern/src/__test__/FernDefinitionBuilder.test.ts +++ b/packages/cli/openapi-ir-to-fern/src/__test__/FernDefinitionBuilder.test.ts @@ -21,6 +21,7 @@ describe("Fern Definition Builder", () => { nonRequestReferencedSchemas: new Set(), securitySchemes: {}, globalHeaders: [], + groups: {}, channel: [] }, true diff --git a/packages/cli/openapi-ir-to-fern/src/buildChannel.ts b/packages/cli/openapi-ir-to-fern/src/buildChannel.ts index 5ba5f637b59..7a2459b0ff1 100644 --- a/packages/cli/openapi-ir-to-fern/src/buildChannel.ts +++ b/packages/cli/openapi-ir-to-fern/src/buildChannel.ts @@ -22,6 +22,10 @@ export function buildChannel({ auth: false }; + if (channel.summary != null) { + convertedChannel["display-name"] = channel.summary; + } + const queryParameters: Record = {}; if (channel.handshake.queryParameters.length > 0) { for (const queryParameter of channel.handshake.queryParameters) { diff --git a/packages/cli/openapi-ir-to-fern/src/buildServices.ts b/packages/cli/openapi-ir-to-fern/src/buildServices.ts index 77426a2d1c0..1c1509623ec 100644 --- a/packages/cli/openapi-ir-to-fern/src/buildServices.ts +++ b/packages/cli/openapi-ir-to-fern/src/buildServices.ts @@ -1,3 +1,4 @@ +import { FernOpenapiIr } from "@fern-api/openapi-ir-sdk"; import { buildEndpoint } from "./buildEndpoint"; import { OpenApiIrConverterContext } from "./OpenApiIrConverterContext"; import { getEndpointLocation } from "./utils/getEndpointLocation"; @@ -15,6 +16,19 @@ export function buildServices(context: OpenApiIrConverterContext): { if (sdkGroup != null) { sdkGroups.add(sdkGroup); } + let group = undefined; + if (endpoint.sdkName != null) { + const groupInfo: Record = context.ir.groups; + for (const groupName of endpoint.sdkName.groupName) { + const value = groupInfo[groupName]; + if (value == null) { + break; + } else if (value.summary != null || value.description != null) { + group = groupInfo[groupName]; + break; + } + } + } const irTag = tag == null ? undefined : tags.tagsById[tag]; const convertedEndpoint = buildEndpoint({ context, @@ -28,8 +42,8 @@ export function buildServices(context: OpenApiIrConverterContext): { }); if (irTag?.id != null || irTag?.description != null) { context.builder.setServiceInfo(file, { - displayName: irTag?.id, - docs: irTag?.description ?? undefined + displayName: group?.summary ?? irTag?.id, + docs: group?.description ?? irTag?.description ?? undefined }); } } diff --git a/packages/cli/openapi-parser/src/asyncapi/fernExtensions.ts b/packages/cli/openapi-parser/src/asyncapi/fernExtensions.ts index 66448d048e1..309ebf0a5b0 100644 --- a/packages/cli/openapi-parser/src/asyncapi/fernExtensions.ts +++ b/packages/cli/openapi-parser/src/asyncapi/fernExtensions.ts @@ -1,6 +1,11 @@ import { Values } from "@fern-api/core-utils"; export const FernAsyncAPIExtension = { + /** + * The x-fern-summary allows you to specify a display name for the websocket channel. + */ + FERN_DISPLAY_NAME: "x-fern-display-name", + /** * The x-fern-examples allows you to specify examples for the websocket session. * diff --git a/packages/cli/openapi-parser/src/asyncapi/parse.ts b/packages/cli/openapi-parser/src/asyncapi/parse.ts index 1f97b789f40..085756c17ba 100644 --- a/packages/cli/openapi-parser/src/asyncapi/parse.ts +++ b/packages/cli/openapi-parser/src/asyncapi/parse.ts @@ -9,12 +9,14 @@ import { } from "@fern-api/openapi-ir-sdk"; import { TaskContext } from "@fern-api/task-context"; import { OpenAPIV3 } from "openapi-types"; +import { getExtension } from "../getExtension"; import { convertSchema } from "../schema/convertSchemas"; import { convertUndiscriminatedOneOf } from "../schema/convertUndiscriminatedOneOf"; import { convertSchemaWithExampleToSchema } from "../schema/utils/convertSchemaWithExampleToSchema"; import { isReferenceObject } from "../schema/utils/isReferenceObject"; import { AsyncAPIV2ParserContext } from "./AsyncAPIParserContext"; import { ExampleWebsocketSessionFactory } from "./ExampleWebsocketSessionFactory"; +import { FernAsyncAPIExtension } from "./fernExtensions"; import { getFernExamples, WebsocketSessionExampleExtension } from "./getFernExamples"; import { AsyncAPIV2 } from "./v2"; @@ -156,7 +158,7 @@ export function parseAsyncAPI({ publish: publishSchema != null ? convertSchemaWithExampleToSchema(publishSchema) : publishSchema, subscribe: subscribeSchema != null ? convertSchemaWithExampleToSchema(subscribeSchema) : subscribeSchema, - summary: undefined, + summary: getExtension(channel, FernAsyncAPIExtension.FERN_DISPLAY_NAME), path: channelPath, description: undefined, examples diff --git a/packages/cli/openapi-parser/src/openapi/parse.ts b/packages/cli/openapi-parser/src/openapi/parse.ts index fc1e3a57565..c9c42904354 100644 --- a/packages/cli/openapi-parser/src/openapi/parse.ts +++ b/packages/cli/openapi-parser/src/openapi/parse.ts @@ -52,7 +52,8 @@ export async function parse({ variables: {}, nonRequestReferencedSchemas: new Set(), securitySchemes: {}, - globalHeaders: [] + globalHeaders: [], + groups: {} }; for (const spec of workspace.specs) { @@ -142,7 +143,11 @@ function merge( ...ir1.securitySchemes, ...ir2.securitySchemes }, - globalHeaders: ir1.globalHeaders != null ? [...ir1.globalHeaders, ...(ir2.globalHeaders ?? [])] : undefined + globalHeaders: ir1.globalHeaders != null ? [...ir1.globalHeaders, ...(ir2.globalHeaders ?? [])] : undefined, + groups: { + ...ir1.groups, + ...ir2.groups + } }; } diff --git a/packages/cli/openapi-parser/src/openapi/v3/extensions/fernExtensions.ts b/packages/cli/openapi-parser/src/openapi/v3/extensions/fernExtensions.ts index 4a0fc79bd11..362fe6a2552 100644 --- a/packages/cli/openapi-parser/src/openapi/v3/extensions/fernExtensions.ts +++ b/packages/cli/openapi-parser/src/openapi/v3/extensions/fernExtensions.ts @@ -4,6 +4,7 @@ import { TypedExtensionId } from "./id"; export const FernOpenAPIExtension = { SDK_METHOD_NAME: TypedExtensionId.of("x-fern-sdk-method-name"), SDK_GROUP_NAME: TypedExtensionId.of("x-fern-sdk-group-name"), + REQUEST_NAME_V1: "x-request-name", REQUEST_NAME_V2: "x-fern-request-name", TYPE_NAME: "x-fern-type-name", @@ -12,6 +13,21 @@ export const FernOpenAPIExtension = { SERVER_NAME_V1: "x-name", SERVER_NAME_V2: "x-fern-server-name", + /** + * Should align with the OpenAPI spec's `x-fern-sdk-group-name` extension. + * This is a place where you can specify any display names related to the + * configured SDK group names. These display names and descriptions will + * come through in the docs. + * + * x-fern-groups: + * group1: + * display-name: Group 1 + * description: This is group 1 + * groups: + * group2 # add child groups + */ + GROUPS: TypedExtensionId.of("x-fern-groups"), + /** * Filepath that contains any OpenAPI overrides * that you wan't Fern to add on top of your existing spec. diff --git a/packages/cli/openapi-parser/src/openapi/v3/extensions/getFernGroups.ts b/packages/cli/openapi-parser/src/openapi/v3/extensions/getFernGroups.ts new file mode 100644 index 00000000000..01233d28a0d --- /dev/null +++ b/packages/cli/openapi-parser/src/openapi/v3/extensions/getFernGroups.ts @@ -0,0 +1,29 @@ +import { OpenAPIV3 } from "openapi-types"; +import { z } from "zod"; +import { getExtensionAndValidate } from "../../../getExtension"; +import { OpenAPIV3ParserContext } from "../OpenAPIV3ParserContext"; +import { FernOpenAPIExtension } from "./fernExtensions"; + +export const XFernGroupsSchema = z.record( + z.string(), + z.object({ + summary: z.string().optional(), + description: z.string().optional() + }) +); +export type XFernGroupsSchema = z.infer; + +export function getFernGroups({ + document, + context +}: { + document: OpenAPIV3.Document; + context: OpenAPIV3ParserContext; +}): XFernGroupsSchema | undefined { + return getExtensionAndValidate( + document, + FernOpenAPIExtension.GROUPS, + XFernGroupsSchema, + context + ); +} diff --git a/packages/cli/openapi-parser/src/openapi/v3/generateIr.ts b/packages/cli/openapi-parser/src/openapi/v3/generateIr.ts index dafe62055f6..bbee0c95725 100644 --- a/packages/cli/openapi-parser/src/openapi/v3/generateIr.ts +++ b/packages/cli/openapi-parser/src/openapi/v3/generateIr.ts @@ -23,6 +23,7 @@ import { convertServer } from "./converters/convertServer"; import { ERROR_NAMES } from "./converters/convertToHttpError"; import { ExampleEndpointFactory } from "./converters/ExampleEndpointFactory"; import { FernOpenAPIExtension } from "./extensions/fernExtensions"; +import { getFernGroups } from "./extensions/getFernGroups"; import { getGlobalHeaders } from "./extensions/getGlobalHeaders"; import { getVariableDefinitions } from "./extensions/getVariableDefinitions"; import { hasIncompleteExample } from "./hasIncompleteExample"; @@ -195,9 +196,16 @@ export function generateIr({ taskContext.logger.debug(`Converted schema ${key}`); } + const groupInfo = getFernGroups({ document: openApi, context }); + const ir: OpenApiIntermediateRepresentation = { title: openApi.info.title, description: openApi.info.description, + groups: Object.fromEntries( + Object.entries(groupInfo ?? {}).map(([key, value]) => { + return [key, { summary: value.summary ?? undefined, description: value.description ?? undefined }]; + }) + ), servers: (openApi.servers ?? []).map((server) => convertServer(server)), tags: { tagsById: Object.fromEntries(