Skip to content

Commit

Permalink
improvement: allow naming for asyncapi messages to pull message name (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
armandobelardo authored Aug 8, 2024
1 parent 0345f40 commit 66dff51
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface SingleNamespaceAPIDefinition {
export interface APIDefinitionSettings {
shouldUseTitleAsName: boolean | undefined;
shouldUseUndiscriminatedUnionsWithLiterals: boolean | undefined;
asyncApiMessageNaming: "v1" | "v2" | undefined;
}

export interface APIDefinitionLocation {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,11 @@ async function parseAPIConfiguration(
origin: undefined,
overrides: undefined,
audiences: [],
settings: { shouldUseTitleAsName: undefined, shouldUseUndiscriminatedUnionsWithLiterals: undefined }
settings: {
shouldUseTitleAsName: undefined,
shouldUseUndiscriminatedUnionsWithLiterals: undefined,
asyncApiMessageNaming: undefined
}
});
} else if (isRawProtobufAPIDefinitionSchema(apiConfiguration)) {
apiDefinitions.push({
Expand All @@ -100,7 +104,11 @@ async function parseAPIConfiguration(
origin: undefined,
overrides: apiConfiguration.proto.overrides,
audiences: [],
settings: { shouldUseTitleAsName: undefined, shouldUseUndiscriminatedUnionsWithLiterals: undefined }
settings: {
shouldUseTitleAsName: undefined,
shouldUseUndiscriminatedUnionsWithLiterals: undefined,
asyncApiMessageNaming: undefined
}
});
} else if (Array.isArray(apiConfiguration)) {
for (const definition of apiConfiguration) {
Expand All @@ -115,7 +123,8 @@ async function parseAPIConfiguration(
audiences: [],
settings: {
shouldUseTitleAsName: undefined,
shouldUseUndiscriminatedUnionsWithLiterals: undefined
shouldUseUndiscriminatedUnionsWithLiterals: undefined,
asyncApiMessageNaming: undefined
}
});
} else if (isRawProtobufAPIDefinitionSchema(definition)) {
Expand All @@ -131,7 +140,8 @@ async function parseAPIConfiguration(
audiences: [],
settings: {
shouldUseTitleAsName: undefined,
shouldUseUndiscriminatedUnionsWithLiterals: undefined
shouldUseUndiscriminatedUnionsWithLiterals: undefined,
asyncApiMessageNaming: undefined
}
});
} else {
Expand All @@ -145,7 +155,8 @@ async function parseAPIConfiguration(
audiences: definition.audiences,
settings: {
shouldUseTitleAsName: definition.settings?.["use-title"],
shouldUseUndiscriminatedUnionsWithLiterals: definition.settings?.unions === "v1"
shouldUseUndiscriminatedUnionsWithLiterals: definition.settings?.unions === "v1",
asyncApiMessageNaming: definition.settings?.["message-naming"]
}
});
}
Expand All @@ -161,7 +172,8 @@ async function parseAPIConfiguration(
audiences: apiConfiguration.audiences,
settings: {
shouldUseTitleAsName: apiConfiguration.settings?.["use-title"],
shouldUseUndiscriminatedUnionsWithLiterals: apiConfiguration.settings?.unions === "v1"
shouldUseUndiscriminatedUnionsWithLiterals: apiConfiguration.settings?.unions === "v1",
asyncApiMessageNaming: apiConfiguration.settings?.["message-naming"]
}
});
}
Expand All @@ -183,7 +195,8 @@ async function parseAPIConfiguration(
audiences: [],
settings: {
shouldUseTitleAsName: settings?.["use-title"],
shouldUseUndiscriminatedUnionsWithLiterals: settings?.unions === "v1"
shouldUseUndiscriminatedUnionsWithLiterals: settings?.unions === "v1",
asyncApiMessageNaming: undefined
}
});
} else if (openapi != null) {
Expand All @@ -197,7 +210,8 @@ async function parseAPIConfiguration(
audiences: [],
settings: {
shouldUseTitleAsName: openapi.settings?.["use-title"],
shouldUseUndiscriminatedUnionsWithLiterals: openapi.settings?.unions === "v1"
shouldUseUndiscriminatedUnionsWithLiterals: openapi.settings?.unions === "v1",
asyncApiMessageNaming: undefined
}
});
}
Expand All @@ -213,7 +227,8 @@ async function parseAPIConfiguration(
audiences: [],
settings: {
shouldUseTitleAsName: settings?.["use-title"],
shouldUseUndiscriminatedUnionsWithLiterals: settings?.unions === "v1"
shouldUseUndiscriminatedUnionsWithLiterals: settings?.unions === "v1",
asyncApiMessageNaming: settings?.["message-naming"]
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ export const APIDefinitionSettingsSchema = z.object({
),
unions: z
.optional(z.enum(["v1"]))
.describe("What version of union generation to use, this will grow over time. Defaults to v0.")
.describe("What version of union generation to use, this will grow over time. Defaults to v0."),
"message-naming": z
.optional(z.enum(["v1", "v2"]))
.describe(
"What version of message naming to use for AsyncAPI messages, this will grow over time. Defaults to v1."
)
});

export type APIDefinitionSettingsSchema = z.infer<typeof APIDefinitionSettingsSchema>;
Expand Down
7 changes: 7 additions & 0 deletions packages/cli/openapi-parser/src/asyncapi/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface ParseAsyncAPIOptions {
naming: "v1" | "v2";
}

export const DEFAULT_PARSE_ASYNCAPI_SETTINGS: ParseAsyncAPIOptions = {
naming: "v1"
};
44 changes: 36 additions & 8 deletions packages/cli/openapi-parser/src/asyncapi/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ import { getExtension } from "../getExtension";
import { ParseOpenAPIOptions } from "../options";
import { convertAvailability } from "../schema/convertAvailability";
import { convertSchema } from "../schema/convertSchemas";
import { convertUndiscriminatedOneOf } from "../schema/convertUndiscriminatedOneOf";
import { convertUndiscriminatedOneOf, UndiscriminatedOneOfPrefix } 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 { ParseAsyncAPIOptions } from "./options";
import { AsyncAPIV2 } from "./v2";

export interface AsyncAPIIntermediateRepresentation {
Expand All @@ -33,16 +34,21 @@ export interface AsyncAPIIntermediateRepresentation {
export function parseAsyncAPI({
document,
taskContext,
options
options,
asyncApiOptions
}: {
document: AsyncAPIV2.Document;
taskContext: TaskContext;
options: ParseOpenAPIOptions;
asyncApiOptions: ParseAsyncAPIOptions;
}): AsyncAPIIntermediateRepresentation {
const breadcrumbs: string[] = [];
if (document.tags?.[0] != null) {
breadcrumbs.push(document.tags[0].name);
} else {
} else if (asyncApiOptions.naming !== "v2") {
// In improved naming, we allow you to not have any prefixes here at all
// by not specifying tags. Without useImprovedMessageNaming, and no tags,
// we do still prefix with "Websocket".
breadcrumbs.push("websocket");
}

Expand Down Expand Up @@ -137,7 +143,9 @@ export function parseAsyncAPI({
generatedName: channel.publish.operationId ?? "PublishEvent",
event: channel.publish,
breadcrumbs,
context
context,
options,
asyncApiOptions
});
}

Expand All @@ -147,7 +155,9 @@ export function parseAsyncAPI({
generatedName: channel.subscribe.operationId ?? "SubscribeEvent",
event: channel.subscribe,
breadcrumbs,
context
context,
options,
asyncApiOptions
});
}

Expand Down Expand Up @@ -229,22 +239,39 @@ function convertMessageToSchema({
generatedName,
event,
context,
breadcrumbs
breadcrumbs,
options,
asyncApiOptions
}: {
breadcrumbs: string[];
generatedName: string;
event: AsyncAPIV2.PublishEvent | AsyncAPIV2.SubscribeEvent;
context: AsyncAPIV2ParserContext;
options: ParseOpenAPIOptions;
asyncApiOptions: ParseAsyncAPIOptions;
}): SchemaWithExample | undefined {
if (event.message.oneOf != null) {
const subtypes: (OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject)[] = [];
const prefixes: UndiscriminatedOneOfPrefix[] = [];
for (const schema of event.message.oneOf) {
let resolvedSchema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject;
let namePrefix: UndiscriminatedOneOfPrefix = { type: "notFound" };
if (isReferenceObject(schema)) {
resolvedSchema = context.resolveMessageReference(schema).payload;
const resolvedMessage = context.resolveMessageReference(schema);
if (!isReferenceObject(resolvedMessage.payload) && asyncApiOptions.naming === "v2") {
namePrefix = resolvedMessage.name ? { type: "name", name: resolvedMessage.name } : namePrefix;
resolvedSchema = {
...resolvedMessage.payload,
title: resolvedMessage.name ?? resolvedMessage.payload.title,
description: resolvedMessage.name ?? resolvedMessage.payload.description
};
} else {
resolvedSchema = resolvedMessage.payload;
}
} else {
resolvedSchema = schema;
}
prefixes.push(namePrefix);
subtypes.push(resolvedSchema);
}
return convertUndiscriminatedOneOf({
Expand All @@ -256,7 +283,8 @@ function convertMessageToSchema({
groupName: undefined,
wrapAsNullable: false,
breadcrumbs,
context
context,
subtypePrefixOverrides: asyncApiOptions.naming === "v2" ? prefixes : []
});
}
return undefined;
Expand Down
1 change: 1 addition & 0 deletions packages/cli/openapi-parser/src/asyncapi/v2/asyncapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface Document {

export interface Message {
messageId: string;
name?: string;
summary?: string;
payload: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject;
}
Expand Down
18 changes: 16 additions & 2 deletions packages/cli/openapi-parser/src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { TaskContext } from "@fern-api/task-context";
import { readFile } from "fs/promises";
import yaml from "js-yaml";
import { OpenAPI, OpenAPIV2, OpenAPIV3 } from "openapi-types";
import { DEFAULT_PARSE_ASYNCAPI_SETTINGS, ParseAsyncAPIOptions } from "./asyncapi/options";
import { parseAsyncAPI } from "./asyncapi/parse";
import { AsyncAPIV2 } from "./asyncapi/v2";
import { loadOpenAPI } from "./loadOpenAPI";
Expand All @@ -22,8 +23,8 @@ export interface SpecImportSettings {
audiences: string[];
shouldUseTitleAsName: boolean;
shouldUseUndiscriminatedUnionsWithLiterals: boolean;
asyncApiNaming?: "v1" | "v2";
}

export interface RawOpenAPIFile {
absoluteFilepath: AbsoluteFilePath;
contents: string;
Expand Down Expand Up @@ -99,7 +100,8 @@ export async function parse({
const parsedAsyncAPI = parseAsyncAPI({
document: asyncAPI,
taskContext,
options: getParseOptions({ specSettings: spec.settings })
options: getParseOptions({ specSettings: spec.settings }),
asyncApiOptions: getParseAsyncOptions({ specSettings: spec.settings })
});
if (parsedAsyncAPI.channel != null) {
ir.channel.push(parsedAsyncAPI.channel);
Expand Down Expand Up @@ -142,6 +144,18 @@ function getParseOptions({
};
}

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

function merge(
ir1: OpenApiIntermediateRepresentation,
ir2: OpenApiIntermediateRepresentation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ import { isReferenceObject } from "./utils/isReferenceObject";
import { isSchemaEqual } from "./utils/isSchemaEqual";
import { convertNumberToSnakeCase } from "./utils/replaceStartingNumber";

export interface UndiscriminatedOneOfPrefixNotFound {
type: "notFound";
}

export interface UndiscriminatedOneOfPrefixName {
type: "name";
name: string;
}

export type UndiscriminatedOneOfPrefix = UndiscriminatedOneOfPrefixName | UndiscriminatedOneOfPrefixNotFound;

export function convertUndiscriminatedOneOf({
nameOverride,
generatedName,
Expand All @@ -24,7 +35,8 @@ export function convertUndiscriminatedOneOf({
wrapAsNullable,
context,
subtypes,
groupName
groupName,
subtypePrefixOverrides
}: {
nameOverride: string | undefined;
generatedName: string;
Expand All @@ -35,8 +47,9 @@ export function convertUndiscriminatedOneOf({
context: SchemaParserContext;
subtypes: (OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject)[];
groupName: SdkGroupName | undefined;
subtypePrefixOverrides?: UndiscriminatedOneOfPrefix[];
}): SchemaWithExample {
const subtypePrefixes = getUniqueSubTypeNames({ schemas: subtypes });
const derivedSubtypePrefixes = getUniqueSubTypeNames({ schemas: subtypes });

const convertedSubtypes = subtypes.flatMap((schema, index) => {
if (!isReferenceObject(schema) && schema.enum != null) {
Expand All @@ -51,7 +64,14 @@ export function convertUndiscriminatedOneOf({
});
});
}
return [convertSchema(schema, false, context, [...breadcrumbs, subtypePrefixes[index] ?? `${index}`])];
let subtypePrefix = derivedSubtypePrefixes[index];
if (subtypePrefixOverrides != null) {
const override = subtypePrefixOverrides[index];
if (override != null && "name" in override) {
subtypePrefix = override.name;
}
}
return [convertSchema(schema, false, context, [...breadcrumbs, subtypePrefix ?? `${index}`])];
});

const uniqueSubtypes: SchemaWithExample[] = [];
Expand Down Expand Up @@ -279,6 +299,7 @@ function getUniqueSubTypeNames({
}
++i;
}

return prefixes;
}

Expand Down
3 changes: 2 additions & 1 deletion packages/cli/workspace-loader/src/loadAPIWorkspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ export async function loadAPIWorkspace({
audiences: definition.audiences ?? [],
shouldUseTitleAsName: definition.settings?.shouldUseTitleAsName ?? true,
shouldUseUndiscriminatedUnionsWithLiterals:
definition.settings?.shouldUseUndiscriminatedUnionsWithLiterals ?? false
definition.settings?.shouldUseUndiscriminatedUnionsWithLiterals ?? false,
asyncApiNaming: definition.settings?.asyncApiMessageNaming
}
});
}
Expand Down
1 change: 1 addition & 0 deletions packages/cli/workspace-loader/src/types/Workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export interface SpecImportSettings {
audiences: string[];
shouldUseTitleAsName: boolean;
shouldUseUndiscriminatedUnionsWithLiterals: boolean;
asyncApiNaming?: "v1" | "v2";
}
export interface APIChangelog {
files: ChangelogFile[];
Expand Down

0 comments on commit 66dff51

Please sign in to comment.