diff --git a/fern/apis/fdr/definition/api/latest/webhook.yml b/fern/apis/fdr/definition/api/latest/webhook.yml index 90bc8c2c11..09b5333b9f 100644 --- a/fern/apis/fdr/definition/api/latest/webhook.yml +++ b/fern/apis/fdr/definition/api/latest/webhook.yml @@ -19,7 +19,7 @@ types: method: WebhookHttpMethod path: list headers: optional> - payload: optional + payloads: optional> examples: optional> WebhookPayload: diff --git a/packages/fdr-sdk/src/__test__/output/beehiiv/apiDefinitions.json b/packages/fdr-sdk/src/__test__/output/beehiiv/apiDefinitions.json index f96eed5e3c..1aed7f32b7 100644 --- a/packages/fdr-sdk/src/__test__/output/beehiiv/apiDefinitions.json +++ b/packages/fdr-sdk/src/__test__/output/beehiiv/apiDefinitions.json @@ -22603,15 +22603,17 @@ "operationId": "sent", "method": "POST", "path": [], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_post:Post" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_post:Post" + } } } - }, + ], "examples": [ { "payload": { @@ -22652,15 +22654,17 @@ "operationId": "created", "method": "POST", "path": [], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_subscription:Subscription" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_subscription:Subscription" + } } } - }, + ], "examples": [ { "payload": { @@ -22698,15 +22702,17 @@ "operationId": "confirmed", "method": "POST", "path": [], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_subscription:Subscription" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_subscription:Subscription" + } } } - }, + ], "examples": [ { "payload": { @@ -22744,15 +22750,17 @@ "operationId": "deleted", "method": "POST", "path": [], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_subscription:Subscription" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_subscription:Subscription" + } } } - }, + ], "examples": [ { "payload": { @@ -22790,15 +22798,17 @@ "operationId": "upgraded", "method": "POST", "path": [], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_subscription:Subscription" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_subscription:Subscription" + } } } - }, + ], "examples": [ { "payload": { @@ -22836,15 +22846,17 @@ "operationId": "downgraded", "method": "POST", "path": [], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_subscription:Subscription" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_subscription:Subscription" + } } } - }, + ], "examples": [ { "payload": { @@ -22882,15 +22894,17 @@ "operationId": "tier-added", "method": "POST", "path": [], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_subscription:Subscription" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_subscription:Subscription" + } } } - }, + ], "examples": [ { "payload": { @@ -22926,15 +22940,17 @@ "operationId": "tier-deleted", "method": "POST", "path": [], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_subscription:Subscription" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_subscription:Subscription" + } } } - }, + ], "examples": [ { "payload": { @@ -22968,15 +22984,17 @@ "operationId": "submitted", "method": "POST", "path": [], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_survey:SurveyResponse" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_survey:SurveyResponse" + } } } - }, + ], "examples": [ { "payload": { diff --git a/packages/fdr-sdk/src/__test__/output/primer/apiDefinitions.json b/packages/fdr-sdk/src/__test__/output/primer/apiDefinitions.json index cd02e80eda..2355fd2572 100644 --- a/packages/fdr-sdk/src/__test__/output/primer/apiDefinitions.json +++ b/packages/fdr-sdk/src/__test__/output/primer/apiDefinitions.json @@ -4916,15 +4916,17 @@ "description": "A secondary signature that is added when you have rotated your secret within the past 24 hours." } ], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_:PaymentStatusWebhookPayload" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_:PaymentStatusWebhookPayload" + } } } - }, + ], "examples": [ { "payload": { @@ -5071,15 +5073,17 @@ "description": "A secondary signature that is added when you have rotated your secret within the past 24 hours." } ], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_:PaymentRefundWebhookPayload" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_:PaymentRefundWebhookPayload" + } } } - }, + ], "examples": [ { "payload": { @@ -5226,15 +5230,17 @@ "description": "A secondary signature that is added when you have rotated your secret within the past 24 hours." } ], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_:DisputeOpenWebhookPayload" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_:DisputeOpenWebhookPayload" + } } } - }, + ], "examples": [ { "payload": { @@ -5287,15 +5293,17 @@ "description": "A secondary signature that is added when you have rotated your secret within the past 24 hours." } ], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_:DisputeStatusWebhookPayload" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_:DisputeStatusWebhookPayload" + } } } - }, + ], "examples": [ { "payload": { @@ -40580,15 +40588,17 @@ "description": "A secondary signature that is added when you have rotated your secret within the past 24 hours." } ], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_:PaymentStatusWebhookPayload" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_:PaymentStatusWebhookPayload" + } } } - }, + ], "examples": [ { "payload": { @@ -40735,15 +40745,17 @@ "description": "A secondary signature that is added when you have rotated your secret within the past 24 hours." } ], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_:PaymentRefundWebhookPayload" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_:PaymentRefundWebhookPayload" + } } } - }, + ], "examples": [ { "payload": { @@ -40890,15 +40902,17 @@ "description": "A secondary signature that is added when you have rotated your secret within the past 24 hours." } ], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_:DisputeOpenWebhookPayload" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_:DisputeOpenWebhookPayload" + } } } - }, + ], "examples": [ { "payload": { @@ -40951,15 +40965,17 @@ "description": "A secondary signature that is added when you have rotated your secret within the past 24 hours." } ], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_:DisputeStatusWebhookPayload" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_:DisputeStatusWebhookPayload" + } } } - }, + ], "examples": [ { "payload": { diff --git a/packages/fdr-sdk/src/__test__/output/stack-auth/apiDefinitions.json b/packages/fdr-sdk/src/__test__/output/stack-auth/apiDefinitions.json index f2e10c0409..4ee8d8968a 100644 --- a/packages/fdr-sdk/src/__test__/output/stack-auth/apiDefinitions.json +++ b/packages/fdr-sdk/src/__test__/output/stack-auth/apiDefinitions.json @@ -7891,15 +7891,17 @@ "description": "This event is triggered when a user is created.", "method": "POST", "path": [], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_users:UserCreatedPayload" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_users:UserCreatedPayload" + } } } - }, + ], "examples": [ { "payload": { @@ -7953,15 +7955,17 @@ "description": "This event is triggered when a user is updated.", "method": "POST", "path": [], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_users:UserUpdatedPayload" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_users:UserUpdatedPayload" + } } } - }, + ], "examples": [ { "payload": { @@ -8015,15 +8019,17 @@ "description": "This event is triggered when a user is deleted.", "method": "POST", "path": [], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_users:UserDeletedPayload" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_users:UserDeletedPayload" + } } } - }, + ], "examples": [ { "payload": { @@ -8050,15 +8056,17 @@ "description": "This event is triggered when a team is created.", "method": "POST", "path": [], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_teams:TeamCreatedPayload" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_teams:TeamCreatedPayload" + } } } - }, + ], "examples": [ { "payload": { @@ -8092,15 +8100,17 @@ "description": "This event is triggered when a team is updated.", "method": "POST", "path": [], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_teams:TeamUpdatedPayload" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_teams:TeamUpdatedPayload" + } } } - }, + ], "examples": [ { "payload": { @@ -8134,15 +8144,17 @@ "description": "This event is triggered when a team is deleted.", "method": "POST", "path": [], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_teams:TeamDeletedPayload" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_teams:TeamDeletedPayload" + } } } - }, + ], "examples": [ { "payload": { @@ -8164,15 +8176,17 @@ "description": "This event is triggered when a user is added to a team.", "method": "POST", "path": [], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_teams:TeamMembershipCreatedPayload" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_teams:TeamMembershipCreatedPayload" + } } } - }, + ], "examples": [ { "payload": { @@ -8195,15 +8209,17 @@ "description": "This event is triggered when a user is removed from a team.", "method": "POST", "path": [], - "payload": { - "shape": { - "type": "alias", - "value": { - "type": "id", - "id": "type_teams:TeamMembershipDeletedPayload" + "payloads": [ + { + "shape": { + "type": "alias", + "value": { + "type": "id", + "id": "type_teams:TeamMembershipDeletedPayload" + } } } - }, + ], "examples": [ { "payload": { diff --git a/packages/fdr-sdk/src/api-definition/migrators/v1ToV2.ts b/packages/fdr-sdk/src/api-definition/migrators/v1ToV2.ts index 34847804e4..4e8e29a252 100644 --- a/packages/fdr-sdk/src/api-definition/migrators/v1ToV2.ts +++ b/packages/fdr-sdk/src/api-definition/migrators/v1ToV2.ts @@ -247,7 +247,7 @@ export class ApiDefinitionV1ToLatest { method: v1.method, path: v1.path, headers: this.migrateParameters(v1.headers), - payload, + payloads: [payload], examples: v1.examples.map((example) => ({ ...example, payload: sortKeysByShape(example.payload, payload.shape, this.types), diff --git a/packages/fdr-sdk/src/api-definition/transformer.ts b/packages/fdr-sdk/src/api-definition/transformer.ts index f34768a15b..d38fc9e1d4 100644 --- a/packages/fdr-sdk/src/api-definition/transformer.ts +++ b/packages/fdr-sdk/src/api-definition/transformer.ts @@ -478,8 +478,11 @@ export class Transformer { parentKey: string ): Latest.WebhookDefinition => { const payload = - webhook.payload != null - ? this.visitor.WebhookPayload(webhook.payload, `${parentKey}/payload`) + webhook.payloads?.[0] != null + ? this.visitor.WebhookPayload( + webhook.payloads[0], + `${parentKey}/payload` + ) : undefined; const headers = webhook.headers?.map((header) => @@ -487,7 +490,7 @@ export class Transformer { ) ?? []; return { ...webhook, - payload, + payloads: payload != null ? [payload] : undefined, headers: headers.length > 0 ? headers : undefined, }; }; diff --git a/packages/fdr-sdk/src/api-definition/typeid-visitor.ts b/packages/fdr-sdk/src/api-definition/typeid-visitor.ts index aff7378655..ddd996bded 100644 --- a/packages/fdr-sdk/src/api-definition/typeid-visitor.ts +++ b/packages/fdr-sdk/src/api-definition/typeid-visitor.ts @@ -61,8 +61,8 @@ export class ApiTypeIdVisitor { webhook.headers?.forEach((header) => { ApiTypeIdVisitor.visitTypeShape(header.valueShape, visit); }); - if (webhook.payload) { - ApiTypeIdVisitor.visitTypeShape(webhook.payload.shape, visit); + if (webhook.payloads?.[0] != null) { + ApiTypeIdVisitor.visitTypeShape(webhook.payloads[0].shape, visit); } } 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 b2b752bf74..939a3ceda3 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,6 @@ export interface WebhookDefinition method: FernRegistry.api.latest.WebhookHttpMethod; path: string[]; headers: FernRegistry.api.latest.ObjectProperty[] | undefined; - payload: FernRegistry.api.latest.WebhookPayload | 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 304f3e5781..90eb56511b 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 @@ -655,15 +655,9 @@ export class Register { * headers: [{ * "key": "value" * }], - * payload: { - * shape: { - * type: "alias", - * value: { - * "key": "value" - * } - * }, - * description: "string" - * }, + * payloads: [{ + * "key": "value" + * }], * examples: [{ * "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 9d3354927c..0204b450a7 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 @@ -628,15 +628,9 @@ import * as FernRegistry from "../../../../../../../../index"; * headers: [{ * "key": "value" * }], - * payload: { - * shape: { - * type: "alias", - * value: { - * "key": "value" - * } - * }, - * description: "string" - * }, + * payloads: [{ + * "key": "value" + * }], * examples: [{ * "key": "value" * }], diff --git a/packages/fern-docs/search-server/src/algolia/records/archive/generateWebhookRecords.ts b/packages/fern-docs/search-server/src/algolia/records/archive/generateWebhookRecords.ts index 18323d136b..3ad0720f2c 100644 --- a/packages/fern-docs/search-server/src/algolia/records/archive/generateWebhookRecords.ts +++ b/packages/fern-docs/search-server/src/algolia/records/archive/generateWebhookRecords.ts @@ -19,7 +19,7 @@ export function generateWebhookRecord({ }: GenerateWebhookRecordsOptions): Algolia.AlgoliaRecord.WebhookV4 { const description = toDescription([ webhook.description, - webhook.payload?.description, + webhook.payloads?.[0]?.description, ]); const webhookRecord: Algolia.AlgoliaRecord.WebhookV4 = { type: "webhook-v4", @@ -91,18 +91,22 @@ export function generateWebhookFieldRecords({ ); }); - if (webhook.payload) { + if (webhook.payloads?.[0]) { push( - ApiDefinition.collectTypeDefinitionTree(webhook.payload.shape, types, { - path: [ - { - type: "meta", - value: "payload", - displayName: "Payload", - }, - { type: "meta", value: "body", displayName: undefined }, - ], - }) + ApiDefinition.collectTypeDefinitionTree( + webhook.payloads[0].shape, + types, + { + path: [ + { + type: "meta", + value: "payload", + displayName: "Payload", + }, + { type: "meta", value: "body", displayName: undefined }, + ], + } + ) ); } diff --git a/packages/fern-docs/search-server/src/algolia/records/create-api-reference-record-webhook.ts b/packages/fern-docs/search-server/src/algolia/records/create-api-reference-record-webhook.ts index be0e5718c6..5936675082 100644 --- a/packages/fern-docs/search-server/src/algolia/records/create-api-reference-record-webhook.ts +++ b/packages/fern-docs/search-server/src/algolia/records/create-api-reference-record-webhook.ts @@ -23,7 +23,9 @@ export function createApiReferenceRecordWebhook({ const { content: payload_description, code_snippets: payload_description_code_snippets, - } = maybePrepareMdxContent(toDescription(endpoint.payload?.description)); + } = maybePrepareMdxContent( + toDescription(endpoint.payloads?.[0]?.description) + ); const code_snippets = payload_description_code_snippets?.filter( (codeSnippet) => measureBytes(codeSnippet.code) < 2000 diff --git a/packages/fern-docs/ui/src/api-reference/webhooks/WebhookContent.tsx b/packages/fern-docs/ui/src/api-reference/webhooks/WebhookContent.tsx index b0e1513d7b..845144f63f 100644 --- a/packages/fern-docs/ui/src/api-reference/webhooks/WebhookContent.tsx +++ b/packages/fern-docs/ui/src/api-reference/webhooks/WebhookContent.tsx @@ -98,7 +98,7 @@ export const WebhookContent = memo((props) => { )} - {webhook.payload && ( + {webhook.payloads?.[0] && (
((props) => { slug={node.slug} > { isWebhook?: boolean; + constructor(args: BaseOpenApiV3_1ConverterNodeConstructorArgs) { + super(args); + this.safeParse(); + } + parse(): void { this.isWebhook = extendType(this.input)[ X_FERN_WEBHOOK ]; } convert(): boolean | undefined { - return this.isWebhook ? undefined : undefined; + return this.isWebhook; } } diff --git a/packages/parsers/src/openapi/3.1/paths/ExampleObjectConverter.node.ts b/packages/parsers/src/openapi/3.1/paths/ExampleObjectConverter.node.ts index 5c9f1f863e..b6849acff9 100644 --- a/packages/parsers/src/openapi/3.1/paths/ExampleObjectConverter.node.ts +++ b/packages/parsers/src/openapi/3.1/paths/ExampleObjectConverter.node.ts @@ -347,7 +347,7 @@ export class ExampleObjectConverterNode extends BaseOpenApiV3_1ConverterNode< break; default: new UnreachableCaseError(this.requestBody.contentType); - return undefined; + break; } } @@ -391,7 +391,7 @@ export class ExampleObjectConverterNode extends BaseOpenApiV3_1ConverterNode< break; default: new UnreachableCaseError(this.responseBody.contentType); - return undefined; + break; } } 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 e76c7f0bd9..8c40c39c89 100644 --- a/packages/parsers/src/openapi/3.1/paths/OperationObjectConverter.node.ts +++ b/packages/parsers/src/openapi/3.1/paths/OperationObjectConverter.node.ts @@ -7,6 +7,7 @@ import { } from "../../BaseOpenApiV3_1Converter.node"; import { coalesceServers } from "../../utils/3.1/coalesceServers"; import { resolveParameterReference } from "../../utils/3.1/resolveParameterReference"; +import { dedupPayloads } from "../../utils/dedupPayloads"; import { getEndpointId } from "../../utils/getEndpointId"; import { SecurityRequirementObjectConverterNode } from "../auth/SecurityRequirementObjectConverter.node"; import { AvailabilityConverterNode } from "../extensions/AvailabilityConverter.node"; @@ -14,6 +15,7 @@ import { XFernBasePathConverterNode } from "../extensions/XFernBasePathConverter import { XFernEndpointExampleConverterNode } from "../extensions/XFernEndpointExampleConverter.node"; import { XFernGroupNameConverterNode } from "../extensions/XFernGroupNameConverter.node"; import { XFernSdkMethodNameConverterNode } from "../extensions/XFernSdkMethodNameConverter.node"; +import { XFernWebhookConverterNode } from "../extensions/XFernWebhookConverter.node"; import { RedocExampleConverterNode } from "../extensions/examples/RedocExampleConverter.node"; import { isReferenceObject } from "../guards/isReferenceObject"; import { ExampleObjectConverterNode } from "./ExampleObjectConverter.node"; @@ -84,6 +86,14 @@ export class OperationObjectConverterNode extends BaseOpenApiV3_1ConverterNode< this.accessPath ); + this.isWebhook = + new XFernWebhookConverterNode({ + input: this.input, + context: this.context, + accessPath: this.accessPath, + pathId: this.pathId, + }).isWebhook || this.isWebhook; + this.input.parameters?.map((parameter, index) => { if (isReferenceObject(parameter)) { const resolvedParameter = resolveParameterReference( @@ -320,11 +330,29 @@ export class OperationObjectConverterNode extends BaseOpenApiV3_1ConverterNode< return undefined; } + if (this.endpointId == null) { + return undefined; + } + + const { responses, errors } = this.responses?.convert() ?? { + responses: undefined, + errors: undefined, + }; + + const examples = [ + ...(this.xFernExamplesNode?.convert() ?? []), + ...(responses?.flatMap((response) => response.examples) ?? []), + ]; + + if (examples.length === 0) { + const emptyExample = this.emptyExample?.convert(); + if (emptyExample != null) { + examples.push(emptyExample); + } + } + if (this.isWebhook) { - if ( - (this.method !== "POST" && this.method !== "GET") || - this.endpointId == null - ) { + if (this.method !== "POST" && this.method !== "GET") { return undefined; } @@ -336,14 +364,18 @@ export class OperationObjectConverterNode extends BaseOpenApiV3_1ConverterNode< operationId: this.operationId, namespace: this.namespace?.convert(), method: this.method, - // This is a little bit weird, consider changing the shape of fdr path: this.convertPathToPathParts()?.map((part) => part.value.toString()) ?? [], - headers: convertOperationObjectProperties(this.requestHeaders)?.flat(), - // TODO: figure out what this looks like to be able to parse - payload: undefined, - examples: undefined, + headers: dedupPayloads( + convertOperationObjectProperties(this.requestHeaders)?.flat() + ), + payloads: this.requests?.convertToWebhookPayload(), + examples: examples.map((example) => { + return { + payload: example.snippets, + }; + }), }; } @@ -361,27 +393,6 @@ export class OperationObjectConverterNode extends BaseOpenApiV3_1ConverterNode< authIds = Object.keys(auth); } - if (this.endpointId == null) { - return undefined; - } - - const { responses, errors } = this.responses?.convert() ?? { - responses: undefined, - errors: undefined, - }; - - const examples = [ - ...(this.xFernExamplesNode?.convert() ?? []), - ...(responses?.flatMap((response) => response.examples) ?? []), - ]; - - if (examples.length === 0) { - const emptyExample = this.emptyExample?.convert(); - if (emptyExample != null) { - examples.push(emptyExample); - } - } - return { id: FernRegistry.EndpointId(this.endpointId), description: this.description, @@ -394,17 +405,18 @@ export class OperationObjectConverterNode extends BaseOpenApiV3_1ConverterNode< auth: authIds?.map((id) => FernRegistry.api.latest.AuthSchemeId(id)), defaultEnvironment: environments?.[0]?.id, environments, - pathParameters: convertOperationObjectProperties( - this.pathParameters - )?.flat(), - queryParameters: convertOperationObjectProperties( - this.queryParameters - )?.flat(), - requestHeaders: convertOperationObjectProperties( - this.requestHeaders - )?.flat(), - responseHeaders: responses?.[0]?.headers, - // TODO: revisit fdr shape to suport multiple requests + pathParameters: dedupPayloads( + convertOperationObjectProperties(this.pathParameters)?.flat() + ), + queryParameters: dedupPayloads( + convertOperationObjectProperties(this.queryParameters)?.flat() + ), + requestHeaders: dedupPayloads( + convertOperationObjectProperties(this.requestHeaders)?.flat() + ), + responseHeaders: dedupPayloads( + responses?.flatMap((response) => response.headers).filter(isNonNullish) + ), requests: this.requests?.convert(), responses: responses?.map((response) => response.response), errors, diff --git a/packages/parsers/src/openapi/3.1/paths/PathItemObjectConverter.node.ts b/packages/parsers/src/openapi/3.1/paths/PathItemObjectConverter.node.ts index 15fc1ee55e..9b8496abad 100644 --- a/packages/parsers/src/openapi/3.1/paths/PathItemObjectConverter.node.ts +++ b/packages/parsers/src/openapi/3.1/paths/PathItemObjectConverter.node.ts @@ -8,7 +8,6 @@ import { import { coalesceServers } from "../../utils/3.1/coalesceServers"; import { SecurityRequirementObjectConverterNode } from "../auth/SecurityRequirementObjectConverter.node"; import { XFernBasePathConverterNode } from "../extensions/XFernBasePathConverter.node"; -import { XFernWebhookConverterNode } from "../extensions/XFernWebhookConverter.node"; import { OperationObjectConverterNode } from "./OperationObjectConverter.node"; import { ServerObjectConverterNode } from "./ServerObjectConverter.node"; @@ -39,14 +38,6 @@ export class PathItemObjectConverterNode extends BaseOpenApiV3_1ConverterNode< } parse(): void { - const isWebhook = - new XFernWebhookConverterNode({ - input: this.input, - context: this.context, - accessPath: this.accessPath, - pathId: this.pathId, - }).isWebhook || this.isWebhook; - this.description = this.input.description; this.servers = coalesceServers( this.servers, @@ -68,7 +59,7 @@ export class PathItemObjectConverterNode extends BaseOpenApiV3_1ConverterNode< this.pathId, "GET", this.basePath, - isWebhook + this.isWebhook ); } if (this.input.post != null) { @@ -84,7 +75,7 @@ export class PathItemObjectConverterNode extends BaseOpenApiV3_1ConverterNode< this.pathId, "POST", this.basePath, - isWebhook + this.isWebhook ); } if (this.input.put != null) { diff --git a/packages/parsers/src/openapi/3.1/paths/request/RequestBodyObjectConverter.node.ts b/packages/parsers/src/openapi/3.1/paths/request/RequestBodyObjectConverter.node.ts index 16f8b7ae76..69e3a55d87 100644 --- a/packages/parsers/src/openapi/3.1/paths/request/RequestBodyObjectConverter.node.ts +++ b/packages/parsers/src/openapi/3.1/paths/request/RequestBodyObjectConverter.node.ts @@ -75,4 +75,25 @@ export class RequestBodyObjectConverterNode extends BaseOpenApiV3_1ConverterNode }) .filter(isNonNullish); } + + convertToWebhookPayload(): + | FernRegistry.api.latest.WebhookPayload[] + | undefined { + return Object.values(this.requestBodiesByContentType ?? {}) + .flatMap((mediaTypeObject) => { + const maybeBodies = maybeSingleValueToArray(mediaTypeObject.convert()); + + return maybeBodies?.map((body) => { + if (body.type !== "alias" && body.type !== "object") { + return undefined; + } + + return { + description: this.description, + shape: body, + }; + }); + }) + .filter(isNonNullish); + } } diff --git a/packages/parsers/src/openapi/3.1/schemas/__test__/MixedSchemaConverter.node.test.ts b/packages/parsers/src/openapi/3.1/schemas/__test__/MixedSchemaConverter.node.test.ts index 8960b8c440..5f65002fe8 100644 --- a/packages/parsers/src/openapi/3.1/schemas/__test__/MixedSchemaConverter.node.test.ts +++ b/packages/parsers/src/openapi/3.1/schemas/__test__/MixedSchemaConverter.node.test.ts @@ -60,7 +60,6 @@ describe("MixedSchemaConverterNode", () => { }); const result = node.convert()[0]; - console.log(JSON.stringify(result, null, 2)); expect(result?.type).toBe("undiscriminatedUnion"); expect( (result as FernRegistry.api.latest.TypeShape.UndiscriminatedUnion) diff --git a/packages/parsers/src/openapi/utils/dedupPayloads.ts b/packages/parsers/src/openapi/utils/dedupPayloads.ts new file mode 100644 index 0000000000..c2e7240b7e --- /dev/null +++ b/packages/parsers/src/openapi/utils/dedupPayloads.ts @@ -0,0 +1,9 @@ +export function dedupPayloads(payloads: T[] | undefined): T[] | undefined { + const filtered = payloads?.filter( + (payload, index, array) => + index === + array.findIndex((p) => JSON.stringify(p) === JSON.stringify(payload)) + ); + + return filtered != null && filtered.length > 0 ? filtered : undefined; +} 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 ab19de77fe..68e8e34b7a 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,6 @@ export interface WebhookDefinition extends FernRegistry.api.latest.WithDescripti method: FernRegistry.api.latest.WebhookHttpMethod; path: string[]; headers: FernRegistry.api.latest.ObjectProperty[] | undefined; - payload: FernRegistry.api.latest.WebhookPayload | undefined; + payloads: FernRegistry.api.latest.WebhookPayload[] | undefined; examples: FernRegistry.api.v1.read.ExampleWebhookPayload[] | undefined; }