Skip to content

Commit

Permalink
feat: Support GET webhooks OpenApi parser v2 (#2157)
Browse files Browse the repository at this point in the history
  • Loading branch information
RohinBhargava authored Feb 12, 2025
1 parent 71e3b05 commit 545ea13
Show file tree
Hide file tree
Showing 15 changed files with 126 additions and 36 deletions.
1 change: 1 addition & 0 deletions fern/apis/fdr/definition/api/latest/webhook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ types:
method: WebhookHttpMethod
path: list<string>
headers: optional<list<type.ObjectProperty>>
queryParameters: optional<list<type.ObjectProperty>>
payloads: optional<list<WebhookPayload>>
examples: optional<list<v1Read.ExampleWebhookPayload>>

Expand Down
1 change: 1 addition & 0 deletions packages/fdr-sdk/src/api-definition/migrators/v1ToV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => ({
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/parsers/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export class OAuth2SecuritySchemeConverterNode extends BaseOpenApiV3_1ConverterN
this.authorizationUrl,
"POST",
undefined,
undefined,
undefined
);
if (endpointId == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ 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";

export class WebhooksObjectConverterNode extends BaseOpenApiV3_1ConverterNode<
OpenAPIV3_1.Document["webhooks"],
FernRegistry.api.latest.ApiDefinition["webhooks"]
> {
webhooks: Record<string, PathItemObjectConverterNode> | undefined;
webhooks: PathItemObjectConverterNode[] | undefined;

constructor(
args: BaseOpenApiV3_1ConverterNodeConstructorArgs<
Expand All @@ -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;
}, {});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
5 changes: 3 additions & 2 deletions packages/parsers/src/openapi/utils/getEndpointId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)}`;
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 545ea13

Please sign in to comment.