Skip to content

Commit 663d328

Browse files
authored
feat(parser): add schema support for API Gateway WebSocket events (#3807)
1 parent e798a70 commit 663d328

File tree

6 files changed

+180
-1
lines changed

6 files changed

+180
-1
lines changed

docs/utilities/parser.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ Both are also able to parse either an object or JSON string as an input.
5555

5656
## Built-in schemas
5757

58-
Parser comes with the following built-in schemas:
58+
**Parser** comes with the following built-in schemas:
5959

6060
| Model name | Description |
6161
| -------------------------------------------- | ------------------------------------------------------------------------------------- |
@@ -64,6 +64,7 @@ Parser comes with the following built-in schemas:
6464
| **APIGatewayRequestAuthorizerEventSchema** | Lambda Event Source payload for Amazon API Gateway Request Authorizer |
6565
| **APIGatewayTokenAuthorizerEventSchema** | Lambda Event Source payload for Amazon API Gateway Token Authorizer |
6666
| **APIGatewayProxyEventV2Schema** | Lambda Event Source payload for Amazon API Gateway v2 payload |
67+
| **APIGatewayProxyWebsocketEventSchema** | Lambda Event Source payload for Amazon API Gateway WebSocket events |
6768
| **APIGatewayRequestAuthorizerEventV2Schema** | Lambda Event Source payload for Amazon API Gateway v2 Authorizer |
6869
| **CloudFormationCustomResourceCreateSchema** | Lambda Event Source payload for AWS CloudFormation `CREATE` operation |
6970
| **CloudFormationCustomResourceUpdateSchema** | Lambda Event Source payload for AWS CloudFormation `UPDATE` operation |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { z } from 'zod';
2+
3+
/**
4+
* A zod schema for API Gateway Proxy WebSocket events.
5+
*
6+
* @example
7+
* ```json
8+
* {
9+
* "type": "REQUEST",
10+
* "methodArn": "arn:aws:execute-api:us-east-1:123456789012:abcdef123/default/$connect",
11+
* "headers": {
12+
* "Connection": "upgrade",
13+
* "content-length": "0",
14+
* "HeaderAuth1": "headerValue1",
15+
* "Host": "abcdef123.execute-api.us-east-1.amazonaws.com",
16+
* "Sec-WebSocket-Extensions": "permessage-deflate; client_max_window_bits",
17+
* "Sec-WebSocket-Key": "...",
18+
* "Sec-WebSocket-Version": "13",
19+
* "Upgrade": "websocket",
20+
* "X-Amzn-Trace-Id": "..."
21+
* },
22+
* "multiValueHeaders": {
23+
* "Connection": [ "upgrade" ],
24+
* "content-length": [ "0" ],
25+
* "HeaderAuth1": [ "headerValue1" ],
26+
* "Host": [ "abcdef123.execute-api.us-east-1.amazonaws.com" ],
27+
* "Sec-WebSocket-Extensions": [ "permessage-deflate; client_max_window_bits" ],
28+
* "Sec-WebSocket-Key": [ "..." ],
29+
* "Sec-WebSocket-Version": [ "13" ],
30+
* "Upgrade": [ "websocket" ],
31+
* "X-Amzn-Trace-Id": [ "..." ]
32+
* },
33+
* "queryStringParameters": {
34+
* "QueryString1": "queryValue1"
35+
* },
36+
* "multiValueQueryStringParameters": {
37+
* "QueryString1": [ "queryValue1" ]
38+
* },
39+
* "stageVariables": {},
40+
* "requestContext": {
41+
* "routeKey": "$connect",
42+
* "eventType": "CONNECT",
43+
* "extendedRequestId": "...",
44+
* "requestTime": "19/Jan/2023:21:13:26 +0000",
45+
* "messageDirection": "IN",
46+
* "stage": "default",
47+
* "connectedAt": 1674162806344,
48+
* "requestTimeEpoch": 1674162806345,
49+
* "identity": {
50+
* "sourceIp": "..."
51+
* },
52+
* "requestId": "...",
53+
* "domainName": "abcdef123.execute-api.us-east-1.amazonaws.com",
54+
* "connectionId": "...",
55+
* "apiId": "abcdef123"
56+
* },
57+
* "isBase64Encoded": false,
58+
* "body": null
59+
* }
60+
* ```
61+
*
62+
* @see {@link https://docs.aws.amazon.com/apigateway/latest/developerguide/websocket-api-develop-integrations.html}
63+
*/
64+
export const APIGatewayProxyWebsocketEventSchema = z.object({
65+
type: z.string(),
66+
methodArn: z.string(),
67+
headers: z.record(z.string()),
68+
multiValueHeaders: z.record(z.array(z.string())),
69+
queryStringParameters: z.record(z.string()).nullable().optional(),
70+
multiValueQueryStringParameters: z.record(z.array(z.string())).nullable().optional(),
71+
stageVariables: z.record(z.string()).nullable().optional(),
72+
requestContext: z.object({
73+
routeKey: z.string(),
74+
eventType: z.enum(["CONNECT", "DISCONNECT", "MESSAGE"]),
75+
extendedRequestId: z.string(),
76+
requestTime: z.string(),
77+
messageDirection: z.enum(["IN", "OUT"]),
78+
stage: z.string(),
79+
connectedAt: z.number(),
80+
requestTimeEpoch: z.number(),
81+
identity: z.object({
82+
sourceIp: z.string(),
83+
userAgent: z.string().optional(),
84+
}),
85+
requestId: z.string(),
86+
domainName: z.string(),
87+
connectionId: z.string(),
88+
apiId: z.string(),
89+
}),
90+
isBase64Encoded: z.boolean(),
91+
body: z.string().optional().nullable(),
92+
});

packages/parser/src/schemas/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export {
55
APIGatewayTokenAuthorizerEventSchema,
66
APIGatewayEventRequestContextSchema,
77
} from './api-gateway.js';
8+
export { APIGatewayProxyWebsocketEventSchema } from './api-gateway-websocket.js'
89
export {
910
AppSyncResolverSchema,
1011
AppSyncBatchResolverSchema,

packages/parser/src/types/schema.ts

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {
33
APIGatewayEventRequestContextSchema,
44
APIGatewayProxyEventSchema,
55
APIGatewayProxyEventV2Schema,
6+
APIGatewayProxyWebsocketEventSchema,
67
APIGatewayRequestAuthorizerEventSchema,
78
APIGatewayRequestAuthorizerV2Schema,
89
APIGatewayRequestContextV2Schema,
@@ -68,6 +69,8 @@ type APIGatewayEventRequestContext = z.infer<
6869

6970
type APIGatewayProxyEventV2 = z.infer<typeof APIGatewayProxyEventV2Schema>;
7071

72+
type APIGatewayProxyWebsocketEvent = z.infer<typeof APIGatewayProxyWebsocketEventSchema>;
73+
7174
type APIGatewayRequestAuthorizerV2 = z.infer<
7275
typeof APIGatewayRequestAuthorizerV2Schema
7376
>;
@@ -166,6 +169,7 @@ export type {
166169
APIGatewayEventRequestContext,
167170
APIGatewayProxyEvent,
168171
APIGatewayProxyEventV2,
172+
APIGatewayProxyWebsocketEvent,
169173
APIGatewayRequestAuthorizerEvent,
170174
APIGatewayRequestAuthorizerV2,
171175
APIGatewayRequestContextV2,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"type": "REQUEST",
3+
"methodArn": "arn:aws:execute-api:us-east-1:123456789012:abcdef123/default/$connect",
4+
"headers": {
5+
"Connection": "upgrade",
6+
"content-length": "0",
7+
"Host": "abcdef123.execute-api.us-east-1.amazonaws.com",
8+
"Upgrade": "websocket"
9+
},
10+
"multiValueHeaders": {
11+
"Connection": ["upgrade"],
12+
"content-length": ["0"],
13+
"Host": ["abcdef123.execute-api.us-east-1.amazonaws.com"],
14+
"Upgrade": ["websocket"]
15+
},
16+
"queryStringParameters": {
17+
"QueryString1": "queryValue1"
18+
},
19+
"multiValueQueryStringParameters": {
20+
"QueryString1": ["queryValue1"]
21+
},
22+
"stageVariables": {},
23+
"requestContext": {
24+
"routeKey": "$connect",
25+
"eventType": "CONNECT",
26+
"extendedRequestId": "XYZ123=",
27+
"requestTime": "10/Oct/2024:22:56:18 +0000",
28+
"messageDirection": "IN",
29+
"stage": "default",
30+
"connectedAt": 1674162806344,
31+
"requestTimeEpoch": 1674162806345,
32+
"identity": {
33+
"sourceIp": "192.0.2.1"
34+
},
35+
"requestId": "abc123",
36+
"domainName": "abcdef123.execute-api.us-east-1.amazonaws.com",
37+
"connectionId": "def456",
38+
"apiId": "abcdef123"
39+
},
40+
"isBase64Encoded": false,
41+
"body": null
42+
}
43+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { APIGatewayProxyWebsocketEventSchema } from '../../../src/schemas/api-gateway-websocket.js';
3+
import type { APIGatewayProxyWebsocketEvent } from '../../../src/types/schema.js';
4+
import { getTestEvent } from '../helpers/utils.js';
5+
6+
describe('Schema: APIGatewayProxyWebsocketEvent', () => {
7+
const baseEvent = getTestEvent<APIGatewayProxyWebsocketEvent>({
8+
eventsPath: 'apigw-websocket',
9+
filename: 'connectEvent',
10+
});
11+
12+
it('parses a valid API Gateway WebSocket event', () => {
13+
// Prepare
14+
const event = structuredClone(baseEvent);
15+
16+
// Act
17+
const result = APIGatewayProxyWebsocketEventSchema.parse(event);
18+
19+
// Assess
20+
expect(result).toStrictEqual(event);
21+
});
22+
23+
it('throws if the event is missing required fields', () => {
24+
// Prepare
25+
const invalidEvent = {
26+
type: 'REQUEST',
27+
methodArn: 'arn:aws:execute-api:us-east-1:123456789012:abcdef123/default/$connect',
28+
headers: {},
29+
requestContext: {
30+
routeKey: '$connect',
31+
eventType: 'CONNECT',
32+
},
33+
};
34+
35+
// Act & Assess
36+
expect(() => APIGatewayProxyWebsocketEventSchema.parse(invalidEvent)).toThrow();
37+
});
38+
});

0 commit comments

Comments
 (0)