Skip to content

Commit 9f1127f

Browse files
authored
Implement Webhooks (openapi-ts#1001)
Fixes openapi-ts#994
1 parent 27e7262 commit 9f1127f

10 files changed

+67321
-57473
lines changed

examples/digital-ocean-api.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,8 @@ export interface paths {
640640
};
641641
}
642642

643+
export type webhooks = Record<string, never>;
644+
643645
export interface components {
644646
schemas: never;
645647
responses: never;
@@ -654,6 +656,7 @@ export interface external {
654656
"description.yml": {
655657

656658
paths: Record<string, never>;
659+
webhooks: Record<string, never>;
657660
components: Record<string, never>;
658661
}
659662
"resources/1-clicks/models/oneClicks_create.yml": {

examples/github-api-next.ts

Lines changed: 67150 additions & 57473 deletions
Large diffs are not rendered by default.

examples/github-api.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7848,6 +7848,8 @@ export interface paths {
78487848
};
78497849
}
78507850

7851+
export type webhooks = Record<string, never>;
7852+
78517853
export interface components {
78527854
schemas: {
78537855
root: {

examples/octokit-ghes-3.6-diff-to-api.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,6 +1165,8 @@ export interface paths {
11651165
};
11661166
}
11671167

1168+
export type webhooks = Record<string, never>;
1169+
11681170
export interface components {
11691171
schemas: {
11701172
"global-hook": {

examples/stripe-api.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1993,6 +1993,8 @@ export interface paths {
19931993
};
19941994
}
19951995

1996+
export type webhooks = Record<string, never>;
1997+
19961998
export interface components {
19971999
schemas: {
19982000
/**

src/transform/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { GlobalContext, OpenAPI3 } from "../types";
22
import transformComponentsObject from "./components-object.js";
33
import transformPathsObject from "./paths-object.js";
4+
import transformWebhooksObject from "./webhooks-object.js";
45

56
/** transform top-level schema */
67
export function transformSchema(schema: OpenAPI3, ctx: GlobalContext): Record<string, string> {
@@ -12,6 +13,10 @@ export function transformSchema(schema: OpenAPI3, ctx: GlobalContext): Record<st
1213
if (schema.paths) output.paths = transformPathsObject(schema.paths, ctx);
1314
else output.paths = "";
1415

16+
// webhooks
17+
if (schema.webhooks) output.webhooks = transformWebhooksObject(schema.webhooks, ctx);
18+
else output.webhooks = "";
19+
1520
// components
1621
if (schema.components) output.components = transformComponentsObject(schema.components, ctx);
1722
else output.components = "";

src/transform/webhooks-object.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { GlobalContext, WebhooksObject } from "../types";
2+
import { escStr, getEntries, indent } from "../utils.js";
3+
import transformPathItemObject from "./path-item-object.js";
4+
5+
export default function transformWebhooksObject(webhooksObject: WebhooksObject, ctx: GlobalContext): string {
6+
let { indentLv } = ctx;
7+
const output: string[] = ["{"];
8+
indentLv++;
9+
for (const [name, pathItemObject] of getEntries(webhooksObject, ctx.alphabetize)) {
10+
output.push(
11+
indent(
12+
`${escStr(name)}: ${transformPathItemObject(pathItemObject, {
13+
path: `#/webhooks/${name}`,
14+
ctx: { ...ctx, indentLv },
15+
})};`,
16+
indentLv
17+
)
18+
);
19+
}
20+
indentLv--;
21+
output.push(indent("}", indentLv));
22+
return output.join("\n");
23+
}

src/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,12 @@ export interface ComponentsObject extends Extensable {
138138
*/
139139
export type PathsObject = { [pathname: string]: PathItemObject };
140140

141+
/**
142+
* [x.x.x] Webhooks Object
143+
* Holds the webhooks definitions, indexed by their names. A webhook is defined by a Path Item Object; the only difference is that the request is initiated by the API provider.
144+
*/
145+
export type WebhooksObject = { [name: string]: PathItemObject };
146+
141147
/**
142148
* [4.8.9] Path Item Object
143149
* Describes the operations available on a single path. A Path Item MAY be empty, due to ACL constraints. The path itself is still exposed to the documentation viewer but they will not know which operations and parameters are available.

test/index.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ describe("openapiTS", () => {
4747
expect(generated).toBe(`${BOILERPLATE}
4848
export type paths = Record<string, never>;
4949
50+
export type webhooks = Record<string, never>;
51+
5052
export interface components {
5153
schemas: {
5254
Base: {
@@ -95,6 +97,8 @@ export type operations = Record<string, never>;
9597
expect(generated).toBe(`${BOILERPLATE}
9698
export type paths = Record<string, never>;
9799
100+
export type webhooks = Record<string, never>;
101+
98102
export interface components {
99103
schemas: {
100104
Example: {
@@ -163,6 +167,8 @@ export type operations = Record<string, never>;
163167
expect(generated).toBe(`${BOILERPLATE}
164168
export type paths = Record<string, never>;
165169
170+
export type webhooks = Record<string, never>;
171+
166172
export interface components {
167173
schemas: {
168174
Pet: {
@@ -222,6 +228,8 @@ export type operations = Record<string, never>;
222228
expect(generated).toBe(`${BOILERPLATE}
223229
export type paths = Record<string, never>;
224230
231+
export type webhooks = Record<string, never>;
232+
225233
export interface components {
226234
schemas: {
227235
ObjRef: {
@@ -265,6 +273,8 @@ export type operations = Record<string, never>;
265273
expect(generated).toBe(`${BOILERPLATE}
266274
export type paths = Record<string, never>;
267275
276+
export type webhooks = Record<string, never>;
277+
268278
export interface components {
269279
schemas: {
270280
User: {
@@ -305,6 +315,8 @@ export type operations = Record<string, never>;
305315
expect(generated).toBe(`${BOILERPLATE}
306316
export type paths = Record<string, never>;
307317
318+
export type webhooks = Record<string, never>;
319+
308320
export type components = {
309321
schemas: {
310322
User: {
@@ -350,6 +362,8 @@ export interface paths {
350362
};
351363
}
352364
365+
export type webhooks = Record<string, never>;
366+
353367
export type components = Record<string, never>;
354368
355369
export type external = Record<string, never>;
@@ -371,6 +385,8 @@ export interface paths {
371385
};
372386
}
373387
388+
export type webhooks = Record<string, never>;
389+
374390
export type components = Record<string, never>;
375391
376392
export type external = Record<string, never>;
@@ -400,6 +416,8 @@ export type operations = Record<string, never>;
400416
expect(generated).toBe(`${BOILERPLATE}
401417
export type paths = Record<string, never>;
402418
419+
export type webhooks = Record<string, never>;
420+
403421
export interface components {
404422
schemas: {
405423
/** Format: date-time */
@@ -430,6 +448,8 @@ export type operations = Record<string, never>;
430448
${inject}
431449
export type paths = Record<string, never>;
432450
451+
export type webhooks = Record<string, never>;
452+
433453
export interface components {
434454
schemas: {
435455
/** Format: date-time */
@@ -477,6 +497,8 @@ export type operations = Record<string, never>;
477497
expect(generated).toBe(`${BOILERPLATE}${TYPE_HELPERS}
478498
export type paths = Record<string, never>;
479499
500+
export type webhooks = Record<string, never>;
501+
480502
export interface components {
481503
schemas: {
482504
User: OneOf<[{

test/webhooks-object.test.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import type { GlobalContext, WebhooksObject } from "../src/types";
2+
import transformWebhooksObject from "../src/transform/webhooks-object.js";
3+
4+
const options: GlobalContext = {
5+
additionalProperties: false,
6+
alphabetize: false,
7+
defaultNonNullable: false,
8+
discriminators: {},
9+
immutableTypes: false,
10+
indentLv: 0,
11+
operations: {},
12+
pathParamsAsTypes: false,
13+
postTransform: undefined,
14+
silent: true,
15+
supportArrayLength: false,
16+
transform: undefined,
17+
};
18+
19+
describe("Webhooks Object", () => {
20+
test("basic", () => {
21+
const schema: WebhooksObject = {
22+
"user-created": {
23+
post: {
24+
parameters: [
25+
{
26+
name: "signature",
27+
in: "query",
28+
schema: { type: "string" },
29+
required: true,
30+
},
31+
],
32+
requestBody: {
33+
content: {
34+
"application/json": {
35+
schema: {
36+
type: "object",
37+
properties: {
38+
id: { type: "string" },
39+
email: { type: "string" },
40+
name: { type: "string" },
41+
},
42+
required: ["id", "email"],
43+
},
44+
},
45+
},
46+
},
47+
responses: {
48+
200: {
49+
description: "Return a 200 status to indicate that the data was received successfully",
50+
},
51+
},
52+
},
53+
},
54+
};
55+
const generated = transformWebhooksObject(schema, options);
56+
expect(generated).toBe(`{
57+
"user-created": {
58+
post: {
59+
parameters: {
60+
query: {
61+
signature: string;
62+
};
63+
};
64+
requestBody?: {
65+
content: {
66+
"application/json": {
67+
id: string;
68+
email: string;
69+
name?: string;
70+
};
71+
};
72+
};
73+
responses: {
74+
/** @description Return a 200 status to indicate that the data was received successfully */
75+
200: never;
76+
};
77+
};
78+
};
79+
}`);
80+
});
81+
82+
test("$ref", () => {
83+
const schema: WebhooksObject = {
84+
"user-created": {
85+
parameters: [
86+
{ in: "query", name: "signature", schema: { type: "string" }, required: true },
87+
{ $ref: 'components["parameters"]["query"]["utm_source"]' },
88+
{ $ref: 'components["parameters"]["query"]["utm_email"]' },
89+
{ $ref: 'components["parameters"]["query"]["utm_campaign"]' },
90+
{ $ref: 'components["parameters"]["path"]["version"]' },
91+
],
92+
},
93+
};
94+
const generated = transformWebhooksObject(schema, options);
95+
expect(generated).toBe(`{
96+
"user-created": {
97+
parameters: {
98+
query: {
99+
signature: string;
100+
} & (Pick<NonNullable<components["parameters"]["query"]>, "utm_source" | "utm_email" | "utm_campaign">);
101+
path?: Pick<components["parameters"]["path"], "version">;
102+
};
103+
};
104+
}`);
105+
});
106+
});

0 commit comments

Comments
 (0)