Skip to content

Commit 125af40

Browse files
feat(openapi): add webhook response descriptions to IR (#11222)
* feat(openapi): add webhook response descriptions to IR This PR adds support for webhook response descriptions by: 1. Adding response field to OpenAPI IR Webhook type 2. Adding response/response-stream fields to Fern definition webhook schema 3. Converting OpenAPI webhook responses to Fern definition format 4. Converting Fern definition webhook responses to IR format The descriptions are stored in the body-level docs fields (JsonResponse, FileDownloadResponse, etc.) rather than adding a response-level docs field to HttpResponse, which avoids widespread changes to endpoint handling. Co-Authored-By: Sarah Bawabe <[email protected]> * fix: add response and response-stream fields to webhook validator visitor Co-Authored-By: Sarah Bawabe <[email protected]> * chore: regenerate JSON schema files with webhook response fields Co-Authored-By: Sarah Bawabe <[email protected]> * feat(ir): add docs field to HttpResponse for webhook response descriptions - Add extends: commons.WithDocs to HttpResponse in IR schema - Regenerate IR SDK types for ir-types-latest - Update convertWebhookGroup.ts to populate docs field on webhook responses - Update convertHttpResponse.ts to include docs field on endpoint responses - Update openapi-to-ir WebhookConverter.ts to capture response descriptions - Update openapi-to-ir OperationConverter.ts to include docs field - Update openrpc-to-ir MethodConverter.ts to include docs field - Update test snapshots with new docs field Co-Authored-By: Sarah Bawabe <[email protected]> * fix: add docs field to protoc-gen-fern MethodConverter HttpResponse Co-Authored-By: Sarah Bawabe <[email protected]> * test: update ete-tests snapshots for HttpResponse docs field Co-Authored-By: Sarah Bawabe <[email protected]> * feat(ir): bump IR version to 62.5.0 and update snapshots for HttpResponse docs field Co-Authored-By: Sarah Bawabe <[email protected]> * test: update protoc-gen-fern snapshot for HttpResponse docs field Co-Authored-By: Sarah Bawabe <[email protected]> * test: update grpc-comments-ir snapshot for HttpResponse docs field Co-Authored-By: Sarah Bawabe <[email protected]> * test: update webhook-openapi-responses-fdr snapshot with response descriptions Co-Authored-By: Sarah Bawabe <[email protected]> * fix(fdr): propagate response-level docs to FDR description field Co-Authored-By: Sarah Bawabe <[email protected]> * fix: remove trailing newlines from snapshot files Co-Authored-By: Sarah Bawabe <[email protected]> * test: update v61-to-v60 migration snapshot for HttpResponse docs field Co-Authored-By: Sarah Bawabe <[email protected]> * test: regenerate ir-generator-tests JSON fixtures for HttpResponse docs field Co-Authored-By: Sarah Bawabe <[email protected]> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent b1ceca2 commit 125af40

File tree

360 files changed

+2215
-901
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

360 files changed

+2215
-901
lines changed

fern.schema.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3224,6 +3224,26 @@
32243224
"payload": {
32253225
"$ref": "#/definitions/webhooks.WebhookPayloadSchema"
32263226
},
3227+
"response": {
3228+
"oneOf": [
3229+
{
3230+
"$ref": "#/definitions/service.HttpResponseSchema"
3231+
},
3232+
{
3233+
"type": "null"
3234+
}
3235+
]
3236+
},
3237+
"response-stream": {
3238+
"oneOf": [
3239+
{
3240+
"$ref": "#/definitions/service.HttpResponseStreamSchema"
3241+
},
3242+
{
3243+
"type": "null"
3244+
}
3245+
]
3246+
},
32273247
"examples": {
32283248
"oneOf": [
32293249
{

fern/apis/fern-definition/definition/webhooks.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ types:
1515
method: WebhookMethodSchema
1616
headers: optional<map<string, services.HttpHeaderSchema>>
1717
payload: WebhookPayloadSchema
18+
response: optional<services.HttpResponseSchema>
19+
response-stream: optional<services.HttpResponseStreamSchema>
1820
examples: optional<list<examples.ExampleWebhookCallSchema>>
1921

2022
WebhookMethodSchema:

package-yml.schema.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3244,6 +3244,26 @@
32443244
"payload": {
32453245
"$ref": "#/definitions/webhooks.WebhookPayloadSchema"
32463246
},
3247+
"response": {
3248+
"oneOf": [
3249+
{
3250+
"$ref": "#/definitions/service.HttpResponseSchema"
3251+
},
3252+
{
3253+
"type": "null"
3254+
}
3255+
]
3256+
},
3257+
"response-stream": {
3258+
"oneOf": [
3259+
{
3260+
"$ref": "#/definitions/service.HttpResponseStreamSchema"
3261+
},
3262+
{
3263+
"type": "null"
3264+
}
3265+
]
3266+
},
32473267
"examples": {
32483268
"oneOf": [
32493269
{

packages/cli/api-importers/openapi-to-ir/src/3.1/paths/operations/OperationConverter.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,8 @@ export class OperationConverter extends AbstractOperationConverter {
267267
{
268268
statusCode: 200,
269269
isWildcardStatusCode: undefined,
270-
body: streamResponse.body
270+
body: streamResponse.body,
271+
docs: streamResponse.docs
271272
}
272273
]
273274
}
@@ -356,13 +357,15 @@ export class OperationConverter extends AbstractOperationConverter {
356357
convertedResponseBody.response = {
357358
statusCode: statusCodeNum,
358359
isWildcardStatusCode: isWildcardStatusCode ? true : undefined,
359-
body: converted.responseBody
360+
body: converted.responseBody,
361+
docs: resolvedResponse.description
360362
};
361363

362364
convertedResponseBody.streamResponse = {
363365
statusCode: statusCodeNum,
364366
isWildcardStatusCode: isWildcardStatusCode ? true : undefined,
365-
body: converted.streamResponseBody
367+
body: converted.streamResponseBody,
368+
docs: resolvedResponse.description
366369
};
367370
}
368371

@@ -371,7 +374,8 @@ export class OperationConverter extends AbstractOperationConverter {
371374
{
372375
statusCode: statusCodeNum,
373376
isWildcardStatusCode: isWildcardStatusCode ? true : undefined,
374-
body: converted.responseBody
377+
body: converted.responseBody,
378+
docs: resolvedResponse.description
375379
}
376380
];
377381
}
@@ -436,18 +440,21 @@ export class OperationConverter extends AbstractOperationConverter {
436440
convertedResponseBody.response = {
437441
statusCode: 200,
438442
isWildcardStatusCode: undefined,
439-
body: converted.responseBody
443+
body: converted.responseBody,
444+
docs: undefined
440445
};
441446
convertedResponseBody.streamResponse = {
442447
statusCode: 200,
443448
isWildcardStatusCode: undefined,
444-
body: converted.streamResponseBody
449+
body: converted.streamResponseBody,
450+
docs: undefined
445451
};
446452
convertedResponseBody.v2Responses = [
447453
{
448454
statusCode: 200,
449455
isWildcardStatusCode: undefined,
450-
body: converted.responseBody
456+
body: converted.responseBody,
457+
docs: undefined
451458
}
452459
];
453460
}

packages/cli/api-importers/openapi-to-ir/src/3.1/paths/operations/WebhookConverter.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,15 +134,17 @@ export class WebhookConverter extends AbstractOperationConverter {
134134
}
135135
}
136136

137-
this.context.resolveMaybeReference({
137+
const resolvedResponse = this.context.resolveMaybeReference({
138138
schemaOrReference: response,
139139
breadcrumbs: [...this.breadcrumbs, "responses", statusCode]
140140
});
141+
const docs = resolvedResponse?.description;
141142

142143
responses.push({
143144
statusCode: statusCodeNum,
144145
isWildcardStatusCode: isWildcard ? true : undefined,
145-
body: undefined
146+
body: undefined,
147+
docs
146148
});
147149
}
148150

packages/cli/api-importers/openapi/openapi-ir-parser/src/openapi/v3/converters/operation/convertWebhookOperation.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { FernOpenAPIExtension } from "../../extensions/fernExtensions";
1010
import { OperationContext } from "../contexts";
1111
import { convertParameters } from "../endpoint/convertParameters";
1212
import { convertRequest } from "../endpoint/convertRequest";
13+
import { convertResponse } from "../endpoint/convertResponse";
1314

1415
export function convertWebhookOperation({
1516
context,
@@ -22,6 +23,7 @@ export function convertWebhookOperation({
2223
}): WebhookWithExample[] {
2324
const { document, operation, path, method, baseBreadcrumbs, sdkMethodName } = operationContext;
2425
const payloadBreadcrumbs = [...baseBreadcrumbs, "Payload"];
26+
const responseBreadcrumbs = [...baseBreadcrumbs, "Response"];
2527

2628
const convertedParameters = convertParameters({
2729
parameters: [...operationContext.pathItemParameters, ...operationContext.operationParameters],
@@ -39,6 +41,18 @@ export function convertWebhookOperation({
3941

4042
const operationId = operation.operationId ?? generateWebhookOperationId({ path, method, sdkMethodName });
4143

44+
// Parse webhook responses similar to how endpoints do it
45+
const convertedResponse = operation.responses
46+
? convertResponse({
47+
operationContext,
48+
streamFormat: undefined,
49+
responses: operation.responses,
50+
context,
51+
responseBreadcrumbs,
52+
source
53+
})
54+
: undefined;
55+
4256
if (method !== "POST" && method !== "GET") {
4357
context.logger.error(`Skipping webhook ${method.toUpperCase()} ${path}: Not POST or GET`);
4458
return [];
@@ -79,6 +93,7 @@ export function convertWebhookOperation({
7993
headers: convertedParameters.headers,
8094
generatedPayloadName: getGeneratedTypeName(payloadBreadcrumbs, context.options.preserveSchemaIds),
8195
payload: request.schema,
96+
response: convertedResponse?.value,
8297
description: operation.description,
8398
examples: convertWebhookExamples(request.fullExamples),
8499
source

packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildWebhooks.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { convertFullExample } from "./utils/convertFullExample";
1010
import { convertEndpointSdkNameToFile } from "./utils/convertSdkGroupName";
1111
import { tokenizeString } from "./utils/getEndpointLocation";
1212
import { getEndpointNamespace } from "./utils/getNamespaceFromGroup";
13+
import { getTypeFromTypeReference } from "./utils/getTypeFromTypeReference";
1314

1415
export function buildWebhooks(context: OpenApiIrConverterContext): void {
1516
for (const webhook of context.ir.webhooks) {
@@ -53,6 +54,86 @@ export function buildWebhooks(context: OpenApiIrConverterContext): void {
5354
})
5455
: undefined
5556
};
57+
58+
// Add response if it exists
59+
if (webhook.response != null) {
60+
webhook.response._visit({
61+
json: (jsonResponse) => {
62+
const responseTypeReference = buildTypeReference({
63+
schema: jsonResponse.schema,
64+
context,
65+
fileContainingReference: webhookLocation.file,
66+
namespace: maybeWebhookNamespace,
67+
declarationDepth: 0
68+
});
69+
webhookDefinition.response = {
70+
docs: jsonResponse.description ?? undefined,
71+
type: getTypeFromTypeReference(responseTypeReference)
72+
};
73+
if (jsonResponse.statusCode != null) {
74+
webhookDefinition.response["status-code"] = jsonResponse.statusCode;
75+
}
76+
},
77+
file: (fileResponse) => {
78+
webhookDefinition.response = {
79+
docs: fileResponse.description ?? undefined,
80+
type: "file",
81+
"status-code": fileResponse.statusCode
82+
};
83+
},
84+
bytes: (bytesResponse) => {
85+
webhookDefinition.response = {
86+
docs: bytesResponse.description ?? undefined,
87+
type: "bytes",
88+
"status-code": bytesResponse.statusCode
89+
};
90+
},
91+
text: (textResponse) => {
92+
webhookDefinition.response = {
93+
docs: textResponse.description ?? undefined,
94+
type: "text",
95+
"status-code": textResponse.statusCode
96+
};
97+
},
98+
streamingJson: (jsonResponse) => {
99+
const responseTypeReference = buildTypeReference({
100+
schema: jsonResponse.schema,
101+
context,
102+
fileContainingReference: webhookLocation.file,
103+
namespace: maybeWebhookNamespace,
104+
declarationDepth: 0
105+
});
106+
webhookDefinition["response-stream"] = {
107+
docs: jsonResponse.description ?? undefined,
108+
type: getTypeFromTypeReference(responseTypeReference),
109+
format: "json"
110+
};
111+
},
112+
streamingSse: (jsonResponse) => {
113+
const responseTypeReference = buildTypeReference({
114+
schema: jsonResponse.schema,
115+
context,
116+
fileContainingReference: webhookLocation.file,
117+
namespace: maybeWebhookNamespace,
118+
declarationDepth: 0
119+
});
120+
webhookDefinition["response-stream"] = {
121+
docs: jsonResponse.description ?? undefined,
122+
type: getTypeFromTypeReference(responseTypeReference),
123+
format: "sse"
124+
};
125+
},
126+
streamingText: (textResponse) => {
127+
webhookDefinition["response-stream"] = {
128+
docs: textResponse.description ?? undefined,
129+
type: "text"
130+
};
131+
},
132+
_other: () => {
133+
throw new Error("Unrecognized Response type: " + webhook.response?.type);
134+
}
135+
});
136+
}
56137
context.builder.addWebhook(webhookLocation.file, {
57138
name: webhookLocation.endpointId,
58139
schema: webhookDefinition

packages/cli/api-importers/openapi/openapi-ir/fern/definition/finalIr.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ types:
100100
docs: |
101101
Populated as ${operationId}Payload
102102
payload: Schema
103+
response: optional<Response>
103104
examples: list<WebhookExampleCall>
104105

105106
WebhookExampleCall:

packages/cli/api-importers/openapi/openapi-ir/fern/definition/parseIr.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ types:
8888
docs: |
8989
Populated as ${operationId}Payload
9090
payload: SchemaWithExample
91+
response: optional<ResponseWithExample>
9192
examples: list<finalIr.WebhookExampleCall>
9293

9394
HttpErrorWithExample:

packages/cli/api-importers/openapi/openapi-ir/src/sdk/api/resources/finalIr/types/Webhook.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ export interface Webhook extends FernOpenapiIr.WithDescription, FernOpenapiIr.Wi
1515
/** Populated as ${operationId}Payload */
1616
generatedPayloadName: string;
1717
payload: FernOpenapiIr.Schema;
18+
response: FernOpenapiIr.Response | undefined;
1819
examples: FernOpenapiIr.WebhookExampleCall[];
1920
}

0 commit comments

Comments
 (0)