Skip to content

Implement Webhooks #1001

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/digital-ocean-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,8 @@ export interface paths {
};
}

export type webhooks = Record<string, never>;

export interface components {
schemas: never;
responses: never;
Expand All @@ -654,6 +656,7 @@ export interface external {
"description.yml": {

paths: Record<string, never>;
webhooks: Record<string, never>;
components: Record<string, never>;
}
"resources/1-clicks/models/oneClicks_create.yml": {
Expand Down
124,623 changes: 67,150 additions & 57,473 deletions examples/github-api-next.ts

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions examples/github-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7848,6 +7848,8 @@ export interface paths {
};
}

export type webhooks = Record<string, never>;

export interface components {
schemas: {
root: {
Expand Down
2 changes: 2 additions & 0 deletions examples/octokit-ghes-3.6-diff-to-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1165,6 +1165,8 @@ export interface paths {
};
}

export type webhooks = Record<string, never>;

export interface components {
schemas: {
"global-hook": {
Expand Down
2 changes: 2 additions & 0 deletions examples/stripe-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1993,6 +1993,8 @@ export interface paths {
};
}

export type webhooks = Record<string, never>;

export interface components {
schemas: {
/**
Expand Down
5 changes: 5 additions & 0 deletions src/transform/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { GlobalContext, OpenAPI3 } from "../types";
import transformComponentsObject from "./components-object.js";
import transformPathsObject from "./paths-object.js";
import transformWebhooksObject from "./webhooks-object.js";

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

// webhooks
if (schema.webhooks) output.webhooks = transformWebhooksObject(schema.webhooks, ctx);
else output.webhooks = "";

// components
if (schema.components) output.components = transformComponentsObject(schema.components, ctx);
else output.components = "";
Expand Down
23 changes: 23 additions & 0 deletions src/transform/webhooks-object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { GlobalContext, WebhooksObject } from "../types";
import { escStr, getEntries, indent } from "../utils.js";
import transformPathItemObject from "./path-item-object.js";

export default function transformWebhooksObject(webhooksObject: WebhooksObject, ctx: GlobalContext): string {
let { indentLv } = ctx;
const output: string[] = ["{"];
indentLv++;
for (const [name, pathItemObject] of getEntries(webhooksObject, ctx.alphabetize)) {
output.push(
indent(
`${escStr(name)}: ${transformPathItemObject(pathItemObject, {
path: `#/webhooks/${name}`,
ctx: { ...ctx, indentLv },
})};`,
indentLv
)
);
}
indentLv--;
output.push(indent("}", indentLv));
return output.join("\n");
}
6 changes: 6 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ export interface ComponentsObject extends Extensable {
*/
export type PathsObject = { [pathname: string]: PathItemObject };

/**
* [x.x.x] Webhooks Object
* 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.
*/
export type WebhooksObject = { [name: string]: PathItemObject };

/**
* [4.8.9] Path Item Object
* 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.
Expand Down
22 changes: 22 additions & 0 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ describe("openapiTS", () => {
expect(generated).toBe(`${BOILERPLATE}
export type paths = Record<string, never>;

export type webhooks = Record<string, never>;

export interface components {
schemas: {
Base: {
Expand Down Expand Up @@ -95,6 +97,8 @@ export type operations = Record<string, never>;
expect(generated).toBe(`${BOILERPLATE}
export type paths = Record<string, never>;

export type webhooks = Record<string, never>;

export interface components {
schemas: {
Example: {
Expand Down Expand Up @@ -163,6 +167,8 @@ export type operations = Record<string, never>;
expect(generated).toBe(`${BOILERPLATE}
export type paths = Record<string, never>;

export type webhooks = Record<string, never>;

export interface components {
schemas: {
Pet: {
Expand Down Expand Up @@ -222,6 +228,8 @@ export type operations = Record<string, never>;
expect(generated).toBe(`${BOILERPLATE}
export type paths = Record<string, never>;

export type webhooks = Record<string, never>;

export interface components {
schemas: {
ObjRef: {
Expand Down Expand Up @@ -265,6 +273,8 @@ export type operations = Record<string, never>;
expect(generated).toBe(`${BOILERPLATE}
export type paths = Record<string, never>;

export type webhooks = Record<string, never>;

export interface components {
schemas: {
User: {
Expand Down Expand Up @@ -305,6 +315,8 @@ export type operations = Record<string, never>;
expect(generated).toBe(`${BOILERPLATE}
export type paths = Record<string, never>;

export type webhooks = Record<string, never>;

export type components = {
schemas: {
User: {
Expand Down Expand Up @@ -350,6 +362,8 @@ export interface paths {
};
}

export type webhooks = Record<string, never>;

export type components = Record<string, never>;

export type external = Record<string, never>;
Expand All @@ -371,6 +385,8 @@ export interface paths {
};
}

export type webhooks = Record<string, never>;

export type components = Record<string, never>;

export type external = Record<string, never>;
Expand Down Expand Up @@ -400,6 +416,8 @@ export type operations = Record<string, never>;
expect(generated).toBe(`${BOILERPLATE}
export type paths = Record<string, never>;

export type webhooks = Record<string, never>;

export interface components {
schemas: {
/** Format: date-time */
Expand Down Expand Up @@ -430,6 +448,8 @@ export type operations = Record<string, never>;
${inject}
export type paths = Record<string, never>;

export type webhooks = Record<string, never>;

export interface components {
schemas: {
/** Format: date-time */
Expand Down Expand Up @@ -477,6 +497,8 @@ export type operations = Record<string, never>;
expect(generated).toBe(`${BOILERPLATE}${TYPE_HELPERS}
export type paths = Record<string, never>;

export type webhooks = Record<string, never>;

export interface components {
schemas: {
User: OneOf<[{
Expand Down
106 changes: 106 additions & 0 deletions test/webhooks-object.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import type { GlobalContext, WebhooksObject } from "../src/types";
import transformWebhooksObject from "../src/transform/webhooks-object.js";

const options: GlobalContext = {
additionalProperties: false,
alphabetize: false,
defaultNonNullable: false,
discriminators: {},
immutableTypes: false,
indentLv: 0,
operations: {},
pathParamsAsTypes: false,
postTransform: undefined,
silent: true,
supportArrayLength: false,
transform: undefined,
};

describe("Webhooks Object", () => {
test("basic", () => {
const schema: WebhooksObject = {
"user-created": {
post: {
parameters: [
{
name: "signature",
in: "query",
schema: { type: "string" },
required: true,
},
],
requestBody: {
content: {
"application/json": {
schema: {
type: "object",
properties: {
id: { type: "string" },
email: { type: "string" },
name: { type: "string" },
},
required: ["id", "email"],
},
},
},
},
responses: {
200: {
description: "Return a 200 status to indicate that the data was received successfully",
},
},
},
},
};
const generated = transformWebhooksObject(schema, options);
expect(generated).toBe(`{
"user-created": {
post: {
parameters: {
query: {
signature: string;
};
};
requestBody?: {
content: {
"application/json": {
id: string;
email: string;
name?: string;
};
};
};
responses: {
/** @description Return a 200 status to indicate that the data was received successfully */
200: never;
};
};
};
}`);
});

test("$ref", () => {
const schema: WebhooksObject = {
"user-created": {
parameters: [
{ in: "query", name: "signature", schema: { type: "string" }, required: true },
{ $ref: 'components["parameters"]["query"]["utm_source"]' },
{ $ref: 'components["parameters"]["query"]["utm_email"]' },
{ $ref: 'components["parameters"]["query"]["utm_campaign"]' },
{ $ref: 'components["parameters"]["path"]["version"]' },
],
},
};
const generated = transformWebhooksObject(schema, options);
expect(generated).toBe(`{
"user-created": {
parameters: {
query: {
signature: string;
} & (Pick<NonNullable<components["parameters"]["query"]>, "utm_source" | "utm_email" | "utm_campaign">);
path?: Pick<components["parameters"]["path"], "version">;
};
};
}`);
});
});