From 545ea13436fd4d5bf07b38f32595d2a0eb5613a8 Mon Sep 17 00:00:00 2001 From: Rohin Bhargava Date: Tue, 11 Feb 2025 20:58:53 -0500 Subject: [PATCH] feat: Support GET webhooks OpenApi parser v2 (#2157) --- .../fdr/definition/api/latest/webhook.yml | 1 + .../src/api-definition/migrators/v1ToV2.ts | 1 + .../webhook/types/WebhookDefinition.ts | 1 + .../v1/resources/register/client/Client.ts | 3 + .../requests/RegisterApiDefinitionRequest.ts | 3 + packages/parsers/package.json | 2 +- .../webhook/types/WebhookDefinition.ts | 1 + .../OAuth2SecuritySchemeConverter.node.ts | 1 + .../openapi/3.1/guards/isWebhookDefinition.ts | 2 +- .../paths/OperationObjectConverter.node.ts | 6 +- .../3.1/paths/WebhooksObjectConverter.node.ts | 66 ++++++++++--------- .../__test__/__snapshots__/webhooks.json | 62 ++++++++++++++++- .../__test__/fixtures/webhooks/openapi.yml | 7 ++ .../src/openapi/utils/getEndpointId.ts | 5 +- .../webhook/types/WebhookDefinition.d.ts | 1 + 15 files changed, 126 insertions(+), 36 deletions(-) diff --git a/fern/apis/fdr/definition/api/latest/webhook.yml b/fern/apis/fdr/definition/api/latest/webhook.yml index 09b5333b9f..ea5afe3faa 100644 --- a/fern/apis/fdr/definition/api/latest/webhook.yml +++ b/fern/apis/fdr/definition/api/latest/webhook.yml @@ -19,6 +19,7 @@ types: method: WebhookHttpMethod path: list headers: optional> + queryParameters: optional> payloads: optional> examples: optional> diff --git a/packages/fdr-sdk/src/api-definition/migrators/v1ToV2.ts b/packages/fdr-sdk/src/api-definition/migrators/v1ToV2.ts index 883e27bc58..abe10a0b06 100644 --- a/packages/fdr-sdk/src/api-definition/migrators/v1ToV2.ts +++ b/packages/fdr-sdk/src/api-definition/migrators/v1ToV2.ts @@ -247,6 +247,7 @@ export class ApiDefinitionV1ToLatest { availability: undefined, method: v1.method, path: v1.path, + queryParameters: undefined, headers: this.migrateParameters(v1.headers), payloads: [payload], examples: v1.examples.map((example) => ({ diff --git a/packages/fdr-sdk/src/client/generated/api/resources/api/resources/latest/resources/webhook/types/WebhookDefinition.ts b/packages/fdr-sdk/src/client/generated/api/resources/api/resources/latest/resources/webhook/types/WebhookDefinition.ts index 939a3ceda3..553f844422 100644 --- a/packages/fdr-sdk/src/client/generated/api/resources/api/resources/latest/resources/webhook/types/WebhookDefinition.ts +++ b/packages/fdr-sdk/src/client/generated/api/resources/api/resources/latest/resources/webhook/types/WebhookDefinition.ts @@ -14,6 +14,7 @@ export interface WebhookDefinition method: FernRegistry.api.latest.WebhookHttpMethod; path: string[]; headers: FernRegistry.api.latest.ObjectProperty[] | undefined; + queryParameters: FernRegistry.api.latest.ObjectProperty[] | undefined; payloads: FernRegistry.api.latest.WebhookPayload[] | undefined; examples: FernRegistry.api.v1.read.ExampleWebhookPayload[] | undefined; } diff --git a/packages/fdr-sdk/src/client/generated/api/resources/api/resources/v1/resources/register/client/Client.ts b/packages/fdr-sdk/src/client/generated/api/resources/api/resources/v1/resources/register/client/Client.ts index a74d06ca24..c4fda5f617 100644 --- a/packages/fdr-sdk/src/client/generated/api/resources/api/resources/v1/resources/register/client/Client.ts +++ b/packages/fdr-sdk/src/client/generated/api/resources/api/resources/v1/resources/register/client/Client.ts @@ -658,6 +658,9 @@ export class Register { * headers: [{ * "key": "value" * }], + * queryParameters: [{ + * "key": "value" + * }], * payloads: [{ * "key": "value" * }], diff --git a/packages/fdr-sdk/src/client/generated/api/resources/api/resources/v1/resources/register/client/requests/RegisterApiDefinitionRequest.ts b/packages/fdr-sdk/src/client/generated/api/resources/api/resources/v1/resources/register/client/requests/RegisterApiDefinitionRequest.ts index 5878f3189c..286e1fed88 100644 --- a/packages/fdr-sdk/src/client/generated/api/resources/api/resources/v1/resources/register/client/requests/RegisterApiDefinitionRequest.ts +++ b/packages/fdr-sdk/src/client/generated/api/resources/api/resources/v1/resources/register/client/requests/RegisterApiDefinitionRequest.ts @@ -631,6 +631,9 @@ import * as FernRegistry from "../../../../../../../../index"; * headers: [{ * "key": "value" * }], + * queryParameters: [{ + * "key": "value" + * }], * payloads: [{ * "key": "value" * }], diff --git a/packages/parsers/package.json b/packages/parsers/package.json index d0aafb500c..2d28dcc27f 100644 --- a/packages/parsers/package.json +++ b/packages/parsers/package.json @@ -1,6 +1,6 @@ { "name": "@fern-api/docs-parsers", - "version": "0.0.54", + "version": "0.0.55", "repository": { "type": "git", "url": "https://github.com/fern-api/fern-platform.git", diff --git a/packages/parsers/src/client/generated/api/resources/api/resources/latest/resources/webhook/types/WebhookDefinition.ts b/packages/parsers/src/client/generated/api/resources/api/resources/latest/resources/webhook/types/WebhookDefinition.ts index 939a3ceda3..553f844422 100644 --- a/packages/parsers/src/client/generated/api/resources/api/resources/latest/resources/webhook/types/WebhookDefinition.ts +++ b/packages/parsers/src/client/generated/api/resources/api/resources/latest/resources/webhook/types/WebhookDefinition.ts @@ -14,6 +14,7 @@ export interface WebhookDefinition method: FernRegistry.api.latest.WebhookHttpMethod; path: string[]; headers: FernRegistry.api.latest.ObjectProperty[] | undefined; + queryParameters: FernRegistry.api.latest.ObjectProperty[] | undefined; payloads: FernRegistry.api.latest.WebhookPayload[] | undefined; examples: FernRegistry.api.v1.read.ExampleWebhookPayload[] | undefined; } diff --git a/packages/parsers/src/openapi/3.1/auth/OAuth2SecuritySchemeConverter.node.ts b/packages/parsers/src/openapi/3.1/auth/OAuth2SecuritySchemeConverter.node.ts index 86f13f2c86..6b4ba9ee34 100644 --- a/packages/parsers/src/openapi/3.1/auth/OAuth2SecuritySchemeConverter.node.ts +++ b/packages/parsers/src/openapi/3.1/auth/OAuth2SecuritySchemeConverter.node.ts @@ -79,6 +79,7 @@ export class OAuth2SecuritySchemeConverterNode extends BaseOpenApiV3_1ConverterN this.authorizationUrl, "POST", undefined, + undefined, undefined ); if (endpointId == null) { diff --git a/packages/parsers/src/openapi/3.1/guards/isWebhookDefinition.ts b/packages/parsers/src/openapi/3.1/guards/isWebhookDefinition.ts index 3c0b78c2ad..ecb799b94f 100644 --- a/packages/parsers/src/openapi/3.1/guards/isWebhookDefinition.ts +++ b/packages/parsers/src/openapi/3.1/guards/isWebhookDefinition.ts @@ -5,5 +5,5 @@ export function isWebhookDefinition( | FernRegistry.api.latest.EndpointDefinition | FernRegistry.api.latest.WebhookDefinition ): definition is FernRegistry.api.latest.WebhookDefinition { - return "payloads" in definition && definition.payloads != null; + return "payloads" in definition; } diff --git a/packages/parsers/src/openapi/3.1/paths/OperationObjectConverter.node.ts b/packages/parsers/src/openapi/3.1/paths/OperationObjectConverter.node.ts index 9da8693bb1..c37da513e3 100644 --- a/packages/parsers/src/openapi/3.1/paths/OperationObjectConverter.node.ts +++ b/packages/parsers/src/openapi/3.1/paths/OperationObjectConverter.node.ts @@ -320,7 +320,8 @@ export class OperationObjectConverterNode extends BaseOpenApiV3_1ConverterNode< this.path, this.method, sdkMethodName.sdkMethodName, - this.input.operationId + this.input.operationId, + this.isWebhook ); // TODO: figure out how to merge user specified examples with success response @@ -440,6 +441,9 @@ export class OperationObjectConverterNode extends BaseOpenApiV3_1ConverterNode< path: this.convertPathToPathParts()?.map((part) => part.value.toString()) ?? [], + queryParameters: convertOperationObjectProperties( + this.queryParameters + )?.flat(), headers: convertOperationObjectProperties(this.requestHeaders)?.flat(), payloads: this.requests?.convertToWebhookPayload(), examples: [this.requests?.webhookExample()].filter(isNonNullish), diff --git a/packages/parsers/src/openapi/3.1/paths/WebhooksObjectConverter.node.ts b/packages/parsers/src/openapi/3.1/paths/WebhooksObjectConverter.node.ts index 1426a66dab..5fe5d3f9c5 100644 --- a/packages/parsers/src/openapi/3.1/paths/WebhooksObjectConverter.node.ts +++ b/packages/parsers/src/openapi/3.1/paths/WebhooksObjectConverter.node.ts @@ -9,6 +9,7 @@ import { import { resolveWebhookReference } from "../../utils/3.1/resolveWebhookReference"; import { SecurityRequirementObjectConverterNode } from "../auth/SecurityRequirementObjectConverter.node"; import { XFernBasePathConverterNode } from "../extensions/XFernBasePathConverter.node"; +import { isWebhookDefinition } from "../guards/isWebhookDefinition"; import { PathItemObjectConverterNode } from "./PathItemObjectConverter.node"; import { ServerObjectConverterNode } from "./ServerObjectConverter.node"; @@ -16,7 +17,7 @@ export class WebhooksObjectConverterNode extends BaseOpenApiV3_1ConverterNode< OpenAPIV3_1.Document["webhooks"], FernRegistry.api.latest.ApiDefinition["webhooks"] > { - webhooks: Record | undefined; + webhooks: PathItemObjectConverterNode[] | undefined; constructor( args: BaseOpenApiV3_1ConverterNodeConstructorArgs< @@ -33,37 +34,42 @@ export class WebhooksObjectConverterNode extends BaseOpenApiV3_1ConverterNode< } parse(): void { - this.webhooks = Object.fromEntries( - Object.entries(this.input ?? {}) - .map(([operation, operationItem]) => { - const resolvedOperationItem = resolveWebhookReference( - operationItem, - this.context.document - ); - if (resolvedOperationItem == null) { - return undefined; - } - return [ - operation, - new PathItemObjectConverterNode( - { - input: resolvedOperationItem, - context: this.context, - accessPath: this.accessPath, - pathId: operation, - }, - this.servers, - this.globalAuth, - this.basePath, - true - ), - ]; - }) - .filter(isNonNullish) - ); + this.webhooks = Object.entries(this.input ?? {}) + .map(([operation, operationItem]) => { + const resolvedOperationItem = resolveWebhookReference( + operationItem, + this.context.document + ); + if (resolvedOperationItem == null) { + return undefined; + } + return new PathItemObjectConverterNode( + { + input: resolvedOperationItem, + context: this.context, + accessPath: this.accessPath, + pathId: operation, + }, + this.servers, + this.globalAuth, + this.basePath, + true + ); + }) + .filter(isNonNullish); } convert(): FernRegistry.api.latest.ApiDefinition["webhooks"] | undefined { - return {}; + return this.webhooks?.reduce< + FernRegistry.api.latest.ApiDefinition["webhooks"] + >((acc, webhook) => { + webhook.convert()?.forEach((convertedWebhook) => { + if (isWebhookDefinition(convertedWebhook)) { + acc[FernRegistry.WebhookId(convertedWebhook.id)] = convertedWebhook; + } + }); + + return acc; + }, {}); } } diff --git a/packages/parsers/src/openapi/__test__/__snapshots__/webhooks.json b/packages/parsers/src/openapi/__test__/__snapshots__/webhooks.json index bba3a4597e..79cd32c5b2 100644 --- a/packages/parsers/src/openapi/__test__/__snapshots__/webhooks.json +++ b/packages/parsers/src/openapi/__test__/__snapshots__/webhooks.json @@ -2,7 +2,67 @@ "id": "test-uuid-replacement", "endpoints": {}, "websockets": {}, - "webhooks": {}, + "webhooks": { + "webhook_.GetPet": { + "id": "webhook_.GetPet", + "operationId": "GetPet", + "method": "GET", + "path": [ + "/", + "pet" + ], + "queryParameters": [ + { + "key": "id", + "valueShape": { + "type": "alias", + "value": { + "type": "optional", + "shape": { + "type": "alias", + "value": { + "type": "primitive", + "value": { + "type": "integer" + } + } + } + } + } + } + ], + "examples": [] + }, + "webhook_.CreatePet": { + "id": "webhook_.CreatePet", + "operationId": "CreatePet", + "method": "POST", + "path": [ + "/", + "pet" + ], + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "Pet" + } + } + } + ], + "examples": [ + { + "payload": { + "id": 0, + "name": "name", + "tag": "tag" + } + } + ] + } + }, "types": { "Pet": { "name": "Pet", diff --git a/packages/parsers/src/openapi/__test__/fixtures/webhooks/openapi.yml b/packages/parsers/src/openapi/__test__/fixtures/webhooks/openapi.yml index 32843d95a0..6fca6c2cb5 100644 --- a/packages/parsers/src/openapi/__test__/fixtures/webhooks/openapi.yml +++ b/packages/parsers/src/openapi/__test__/fixtures/webhooks/openapi.yml @@ -4,6 +4,13 @@ info: version: 1.0.0 webhooks: /pet: + get: + operationId: GetPet + parameters: + - name: id + in: query + schema: + type: integer post: operationId: CreatePet requestBody: diff --git a/packages/parsers/src/openapi/utils/getEndpointId.ts b/packages/parsers/src/openapi/utils/getEndpointId.ts index 53cff5cef1..c3bd1e3d64 100644 --- a/packages/parsers/src/openapi/utils/getEndpointId.ts +++ b/packages/parsers/src/openapi/utils/getEndpointId.ts @@ -5,7 +5,8 @@ export function getEndpointId( path: string | undefined, method: string | undefined, sdkMethodName: string | undefined, - operationId: string | undefined + operationId: string | undefined, + isWebhook: boolean | undefined ): string | undefined { if (path == null) { return undefined; @@ -19,5 +20,5 @@ export function getEndpointId( if (endpointName == null) { return undefined; } - return `endpoint_${camelCase(namespace != null ? (typeof namespace === "string" ? namespace : namespace.join("_")) : "")}.${camelCase(sdkMethodName ?? "") || operationId || camelCase(endpointName)}`; + return `${isWebhook ? "webhook_" : "endpoint_"}${camelCase(namespace != null ? (typeof namespace === "string" ? namespace : namespace.join("_")) : "")}.${camelCase(sdkMethodName ?? "") || operationId || camelCase(endpointName)}`; } diff --git a/servers/fdr/src/api/generated/api/resources/api/resources/latest/resources/webhook/types/WebhookDefinition.d.ts b/servers/fdr/src/api/generated/api/resources/api/resources/latest/resources/webhook/types/WebhookDefinition.d.ts index 68e8e34b7a..892eb5a787 100644 --- a/servers/fdr/src/api/generated/api/resources/api/resources/latest/resources/webhook/types/WebhookDefinition.d.ts +++ b/servers/fdr/src/api/generated/api/resources/api/resources/latest/resources/webhook/types/WebhookDefinition.d.ts @@ -9,6 +9,7 @@ export interface WebhookDefinition extends FernRegistry.api.latest.WithDescripti method: FernRegistry.api.latest.WebhookHttpMethod; path: string[]; headers: FernRegistry.api.latest.ObjectProperty[] | undefined; + queryParameters: FernRegistry.api.latest.ObjectProperty[] | undefined; payloads: FernRegistry.api.latest.WebhookPayload[] | undefined; examples: FernRegistry.api.v1.read.ExampleWebhookPayload[] | undefined; }