diff --git a/packages/nextlove/src/generators/generate-openapi/fern-sdk-utils.ts b/packages/nextlove/src/generators/generate-openapi/fern-sdk-utils.ts new file mode 100644 index 000000000..88723800b --- /dev/null +++ b/packages/nextlove/src/generators/generate-openapi/fern-sdk-utils.ts @@ -0,0 +1,72 @@ +import { RouteSpec } from "../../types" + +function transformPathToFernSdkMethodName(path: string) { + const parts = path.split("/").filter((part) => part !== "") + const lastPart = parts[parts.length - 1] + if (lastPart.startsWith("[") && lastPart.endsWith("]")) { + // Convert [param] to by_param + const param = lastPart.slice(1, -1) + return `by_${param}` + } + + return lastPart +} + +// fern docs: https://buildwithfern.com/docs/spec/extensions +function transformPathToFernSdkGroupName(path: string) { + const parts = path.split("/").filter((part) => part !== "") + return parts.slice(0, parts.length - 1) +} + +function getFernSdkMetadata(path: string): + | { + "x-fern-ignore": true + } + | { + "x-fern-sdk-group-name": string[] + "x-fern-sdk-method-name": string + } { + if (path.split("/").filter((part) => part !== "").length === 1) { + return { + "x-fern-ignore": true, + } + } + + return { + "x-fern-sdk-group-name": transformPathToFernSdkGroupName(path), + "x-fern-sdk-method-name": transformPathToFernSdkMethodName(path), + } +} + +export function mapMethodsToFernSdkMetadata( + methods: RouteSpec["methods"], + path: string +) { + const fernSdkMetadata = getFernSdkMetadata(path) + if (methods.length === 1) { + return { + [methods[0]]: fernSdkMetadata, + } + } + + const mappedMethods = {} + const atLeastOneMethodIsPost = methods.includes("POST") + + if (!atLeastOneMethodIsPost) { + throw new Error( + "A route that accepts multiple methods should include at least the POST method." + ) + } + + methods.forEach((method) => { + if (method === "POST") { + mappedMethods[method] = fernSdkMetadata + } else { + mappedMethods[method] = { + "x-fern-ignore": true, + } + } + }) + + return mappedMethods +} diff --git a/packages/nextlove/src/generators/generate-openapi/index.ts b/packages/nextlove/src/generators/generate-openapi/index.ts index b6fe9ed7f..57233df50 100644 --- a/packages/nextlove/src/generators/generate-openapi/index.ts +++ b/packages/nextlove/src/generators/generate-openapi/index.ts @@ -10,17 +10,18 @@ import chalk from "chalk" import { z } from "zod" import { parseRoutesInPackage } from "../lib/parse-routes-in-package" import { embedSchemaReferences } from "./embed-schema-references" +import { mapMethodsToFernSdkMetadata } from "./fern-sdk-utils" -function transformPathToOperationId(path: string): string { - function replaceFirstCharToLowercase(str: string) { - if (str.length === 0) { - return str - } - - const firstChar = str.charAt(0).toLowerCase() - return firstChar + str.slice(1) +function replaceFirstCharToLowercase(str: string) { + if (str.length === 0) { + return str } + const firstChar = str.charAt(0).toLowerCase() + return firstChar + str.slice(1) +} + +function transformPathToOperationId(path: string): string { const parts = path .replace(/-/g, "_") .split("/") @@ -273,11 +274,17 @@ export async function generateOpenAPI(opts: GenerateOpenAPIOpts) { } } + const methodsMappedToFernSdkMetadata = mapMethodsToFernSdkMetadata( + methods, + routePath + ) + // Some routes accept multiple methods builder.addPath(routePath, { ...methods .map((method) => ({ [method.toLowerCase()]: { + ...methodsMappedToFernSdkMetadata[method], ...route, operationId: `${transformPathToOperationId(routePath)}${pascalCase( method