Skip to content

Commit 6965d70

Browse files
Uzlopakwolfy1339
andauthored
feat: add validate-event-name (#1161)
* feat: add validate-event-name * use already existing EmitterWebhookEventName * add ignore option * Update src/event-handler/validate-event-name.ts Co-authored-by: wolfy1339 <[email protected]> --------- Co-authored-by: wolfy1339 <[email protected]>
1 parent 4e83370 commit 6965d70

File tree

5 files changed

+259
-29
lines changed

5 files changed

+259
-29
lines changed

README.md

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
- [createNodeMiddleware()](#createnodemiddleware)
2323
- [Webhook events](#webhook-events)
2424
- [emitterEventNames](#emittereventnames)
25+
- [validateEventName](#validateeventname)
2526
- [TypeScript](#typescript)
2627
- [`EmitterWebhookEventName`](#emitterwebhookeventname)
2728
- [`EmitterWebhookEvent`](#emitterwebhookevent)
@@ -87,18 +88,19 @@ source.onmessage = (event) => {
8788
## API
8889

8990
1. [Constructor](#constructor)
90-
2. [webhooks.sign()](#webhookssign)
91-
3. [webhooks.verify()](#webhooksverify)
92-
4. [webhooks.verifyAndReceive()](#webhooksverifyandreceive)
93-
5. [webhooks.receive()](#webhooksreceive)
94-
6. [webhooks.on()](#webhookson)
95-
7. [webhooks.onAny()](#webhooksonany)
96-
8. [webhooks.onError()](#webhooksonerror)
97-
9. [webhooks.removeListener()](#webhooksremovelistener)
98-
10. [createNodeMiddleware()](#createnodemiddleware)
99-
11. [createWebMiddleware()](#createwebmiddleware)
100-
12. [Webhook events](#webhook-events)
101-
13. [emitterEventNames](#emittereventnames)
91+
1. [webhooks.sign()](#webhookssign)
92+
1. [webhooks.verify()](#webhooksverify)
93+
1. [webhooks.verifyAndReceive()](#webhooksverifyandreceive)
94+
1. [webhooks.receive()](#webhooksreceive)
95+
1. [webhooks.on()](#webhookson)
96+
1. [webhooks.onAny()](#webhooksonany)
97+
1. [webhooks.onError()](#webhooksonerror)
98+
1. [webhooks.removeListener()](#webhooksremovelistener)
99+
1. [createNodeMiddleware()](#createnodemiddleware)
100+
1. [createWebMiddleware()](#createwebmiddleware)
101+
1. [Webhook events](#webhook-events)
102+
1. [emitterEventNames](#emittereventnames)
103+
1. [validateEventName](#validateeventname)
102104

103105
### Constructor
104106

@@ -724,6 +726,34 @@ import { emitterEventNames } from "@octokit/webhooks";
724726
emitterEventNames; // ["check_run", "check_run.completed", ...]
725727
```
726728

729+
### validateEventName
730+
731+
The function `validateEventName` asserts that the provided event name is a valid event name or event/action combination.
732+
It throws an error if the event name is not valid, or '\*' or 'error' is passed.
733+
734+
The second parameter is an optional options object that can be used to customize the behavior of the validation. You can set
735+
a `onUnknownEventName` property to `"warn"` to log a warning instead of throwing an error, and a `log` property to provide a custom logger object, which should have a `"warn"` method. You can also set `onUnknownEventName` to `"ignore"` to disable logging or throwing an error for unknown event names.
736+
737+
```ts
738+
import { validateEventName } from "@octokit/webhooks";
739+
740+
validateEventName("push"); // no error
741+
validateEventName("invalid_event"); // throws an error
742+
validateEventName("*"); // throws an error
743+
validateEventName("error"); // throws an error
744+
745+
validateEventName("invalid_event", { onUnknownEventName: "warn" }); // logs a warning
746+
validateEventName("invalid_event", {
747+
onUnknownEventName: false,
748+
log: {
749+
warn: console.info, // instead of warning we just log it via console.info
750+
},
751+
});
752+
753+
validateEventName("*", { onUnkownEventName: "ignore" }); // throws an error
754+
validateEventName("invalid_event", { onUnkownEventName: "ignore" }); // no error, no warning
755+
```
756+
727757
## TypeScript
728758

729759
The types for the webhook payloads are sourced from [`@octokit/openapi-webhooks-types`](https://github.com/octokit/openapi-webhooks/tree/main/packages/openapi-webhooks-types),

src/event-handler/on.ts

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { emitterEventNames } from "../generated/webhook-names.ts";
21
import type {
32
EmitterWebhookEvent,
43
EmitterWebhookEventName,
54
State,
65
WebhookEventHandlerError,
76
} from "../types.ts";
7+
import { validateEventName } from "./validate-event-name.ts";
88

99
function handleEventHandlers(
1010
state: State,
@@ -29,22 +29,10 @@ export function receiverOn(
2929
return;
3030
}
3131

32-
if (["*", "error"].includes(webhookNameOrNames)) {
33-
const webhookName =
34-
(webhookNameOrNames as string) === "*" ? "any" : webhookNameOrNames;
35-
36-
const message = `Using the "${webhookNameOrNames}" event with the regular Webhooks.on() function is not supported. Please use the Webhooks.on${
37-
webhookName.charAt(0).toUpperCase() + webhookName.slice(1)
38-
}() method instead`;
39-
40-
throw new Error(message);
41-
}
42-
43-
if (!emitterEventNames.includes(webhookNameOrNames)) {
44-
state.log.warn(
45-
`"${webhookNameOrNames}" is not a known webhook name (https://developer.github.com/v3/activity/events/types/)`,
46-
);
47-
}
32+
validateEventName(webhookNameOrNames, {
33+
onUnknownEventName: "warn",
34+
log: state.log,
35+
});
4836

4937
handleEventHandlers(state, webhookNameOrNames, handler);
5038
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import type { Logger } from "../create-logger.ts";
2+
import { emitterEventNames } from "../generated/webhook-names.ts";
3+
import type { EmitterWebhookEventName } from "../types.ts";
4+
5+
type ValidateEventNameOptions =
6+
| {
7+
onUnknownEventName?: undefined | "throw";
8+
}
9+
| {
10+
onUnknownEventName: "ignore";
11+
}
12+
| {
13+
onUnknownEventName: "warn";
14+
log?: Pick<Logger, "warn">;
15+
};
16+
17+
export function validateEventName<
18+
TOptions extends ValidateEventNameOptions = ValidateEventNameOptions,
19+
>(
20+
eventName: EmitterWebhookEventName | (string & Record<never, never>),
21+
options: TOptions = {} as TOptions,
22+
): asserts eventName is TOptions extends { onUnknownEventName: "throw" }
23+
? EmitterWebhookEventName
24+
: Exclude<string, "*" | "error"> {
25+
if (typeof eventName !== "string") {
26+
throw new TypeError("eventName must be of type string");
27+
}
28+
if (eventName === "*") {
29+
throw new TypeError(
30+
`Using the "*" event with the regular Webhooks.on() function is not supported. Please use the Webhooks.onAny() method instead`,
31+
);
32+
}
33+
if (eventName === "error") {
34+
throw new TypeError(
35+
`Using the "error" event with the regular Webhooks.on() function is not supported. Please use the Webhooks.onError() method instead`,
36+
);
37+
}
38+
39+
if (options.onUnknownEventName === "ignore") {
40+
return;
41+
}
42+
43+
if (!emitterEventNames.includes(eventName as EmitterWebhookEventName)) {
44+
if (options.onUnknownEventName !== "warn") {
45+
throw new TypeError(
46+
`"${eventName}" is not a known webhook name (https://developer.github.com/v3/activity/events/types/)`,
47+
);
48+
} else {
49+
(options.log || console).warn(
50+
`"${eventName}" is not a known webhook name (https://developer.github.com/v3/activity/events/types/)`,
51+
);
52+
}
53+
}
54+
}

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
createEventHandler,
44
type EventHandler,
55
} from "./event-handler/index.ts";
6+
import { validateEventName } from "./event-handler/validate-event-name.ts";
67
import { sign, verify } from "@octokit/webhooks-methods";
78
import { verifyAndReceive } from "./verify-and-receive.ts";
89
import type {
@@ -78,6 +79,7 @@ class Webhooks<TTransformed = unknown> {
7879

7980
export {
8081
createEventHandler,
82+
validateEventName,
8183
Webhooks,
8284
type EmitterWebhookEvent,
8385
type EmitterWebhookEventName,
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import { describe, it, assert } from "../testrunner.ts";
2+
import { validateEventName } from "../../src/event-handler/validate-event-name.ts";
3+
4+
describe("validateEventName", () => {
5+
it("validates 'push'", () => {
6+
validateEventName("push");
7+
});
8+
9+
[null, undefined, {}, [], true, false, 1].forEach((value) => {
10+
it(`throws on invalid event name data type - ${JSON.stringify(value)}`, () => {
11+
try {
12+
validateEventName(value as any);
13+
throw new Error("Should have thrown");
14+
} catch (error) {
15+
assert(error instanceof TypeError === true);
16+
assert((error as Error).message === "eventName must be of type string");
17+
}
18+
});
19+
});
20+
21+
it('throws on invalid event name - "error"', () => {
22+
try {
23+
validateEventName("error");
24+
throw new Error("Should have thrown");
25+
} catch (error) {
26+
assert(error instanceof TypeError === true);
27+
assert(
28+
(error as Error).message ===
29+
'Using the "error" event with the regular Webhooks.on() function is not supported. Please use the Webhooks.onError() method instead',
30+
);
31+
}
32+
});
33+
34+
it('throws on invalid event name - "error" with onUnkownEventName set to "warn"', () => {
35+
try {
36+
validateEventName("error", { onUnknownEventName: "warn" });
37+
throw new Error("Should have thrown");
38+
} catch (error) {
39+
assert(error instanceof TypeError === true);
40+
assert(
41+
(error as Error).message ===
42+
'Using the "error" event with the regular Webhooks.on() function is not supported. Please use the Webhooks.onError() method instead',
43+
);
44+
}
45+
});
46+
47+
it('throws on invalid event name - "error" with onUnkownEventName set to "ignore"', () => {
48+
try {
49+
validateEventName("error", { onUnknownEventName: "ignore" });
50+
throw new Error("Should have thrown");
51+
} catch (error) {
52+
assert(error instanceof TypeError === true);
53+
assert(
54+
(error as Error).message ===
55+
'Using the "error" event with the regular Webhooks.on() function is not supported. Please use the Webhooks.onError() method instead',
56+
);
57+
}
58+
});
59+
60+
it('throws on invalid event name - "*"', () => {
61+
try {
62+
validateEventName("*");
63+
throw new Error("Should have thrown");
64+
} catch (error) {
65+
assert(error instanceof TypeError === true);
66+
assert(
67+
(error as Error).message ===
68+
'Using the "*" event with the regular Webhooks.on() function is not supported. Please use the Webhooks.onAny() method instead',
69+
);
70+
}
71+
});
72+
73+
it('throws on invalid event name - "*" with onUnkownEventName set to "warn"', () => {
74+
try {
75+
validateEventName("*", { onUnknownEventName: "warn" });
76+
throw new Error("Should have thrown");
77+
} catch (error) {
78+
assert(error instanceof TypeError === true);
79+
assert(
80+
(error as Error).message ===
81+
'Using the "*" event with the regular Webhooks.on() function is not supported. Please use the Webhooks.onAny() method instead',
82+
);
83+
}
84+
});
85+
86+
it('throws on invalid event name - "*" with onUnkownEventName set to "ignore"', () => {
87+
try {
88+
validateEventName("*", { onUnknownEventName: "ignore" });
89+
throw new Error("Should have thrown");
90+
} catch (error) {
91+
assert(error instanceof TypeError === true);
92+
assert(
93+
(error as Error).message ===
94+
'Using the "*" event with the regular Webhooks.on() function is not supported. Please use the Webhooks.onAny() method instead',
95+
);
96+
}
97+
});
98+
99+
it('throws on invalid event name - "invalid"', () => {
100+
try {
101+
validateEventName("invalid");
102+
throw new Error("Should have thrown");
103+
} catch (error) {
104+
assert(error instanceof TypeError === true);
105+
assert(
106+
(error as Error).message ===
107+
'"invalid" is not a known webhook name (https://developer.github.com/v3/activity/events/types/)',
108+
);
109+
}
110+
});
111+
112+
it('logs on invalid event name - "invalid" and onUnknownEventName is "warn" - console.warn', () => {
113+
const consoleWarn = console.warn;
114+
const logWarnCalls: string[] = [];
115+
console.warn = Array.prototype.push.bind(logWarnCalls);
116+
117+
validateEventName("invalid", { onUnknownEventName: "warn" });
118+
try {
119+
assert(logWarnCalls.length === 1);
120+
assert(
121+
logWarnCalls[0] ===
122+
'"invalid" is not a known webhook name (https://developer.github.com/v3/activity/events/types/)',
123+
);
124+
} catch (error) {
125+
throw error;
126+
} finally {
127+
console.warn = consoleWarn; // restore original console.warn
128+
}
129+
});
130+
131+
it('logs on invalid event name - "invalid" and onUnknownEventName is "warn" - custom logger', () => {
132+
const logWarnCalls: string[] = [];
133+
const log = {
134+
warn: (message: string) => {
135+
logWarnCalls.push(message);
136+
},
137+
};
138+
validateEventName("invalid", { onUnknownEventName: "warn", log });
139+
assert(logWarnCalls.length === 1);
140+
assert(
141+
logWarnCalls[0] ===
142+
'"invalid" is not a known webhook name (https://developer.github.com/v3/activity/events/types/)',
143+
);
144+
});
145+
146+
it('logs nothing on invalid event name - "invalid" and onUnknownEventName is "ignore" - custom logger', () => {
147+
const logWarnCalls: string[] = [];
148+
const log = {
149+
warn: (message: string) => {
150+
logWarnCalls.push(message);
151+
},
152+
};
153+
validateEventName("invalid", { onUnknownEventName: "ignore", log });
154+
assert(logWarnCalls.length === 0);
155+
});
156+
});

0 commit comments

Comments
 (0)