From 4aeca4dd409ee877425a1d7bcc52879678fa0af4 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 8 May 2025 12:27:52 +0200 Subject: [PATCH 1/3] feat(parser): add schemas for AppSync Events --- docs/features/parser.md | 30 ++-- packages/parser/src/schemas/appsync-events.ts | 150 ++++++++++++++++++ packages/parser/src/schemas/index.ts | 9 +- packages/parser/src/types/index.ts | 2 + packages/parser/src/types/schema.ts | 12 +- .../tests/events/appsync-events/base.json | 28 ++++ .../tests/unit/schema/appsync-events.test.ts | 84 ++++++++++ 7 files changed, 300 insertions(+), 15 deletions(-) create mode 100644 packages/parser/src/schemas/appsync-events.ts create mode 100644 packages/parser/tests/events/appsync-events/base.json create mode 100644 packages/parser/tests/unit/schema/appsync-events.test.ts diff --git a/docs/features/parser.md b/docs/features/parser.md index c344a80b2f..aa9dcc2ae6 100644 --- a/docs/features/parser.md +++ b/docs/features/parser.md @@ -64,24 +64,28 @@ Both are also able to parse either an object or JSON string as an input. | **APIGatewayRequestAuthorizerEventSchema** | Lambda Event Source payload for Amazon API Gateway Request Authorizer | | **APIGatewayTokenAuthorizerEventSchema** | Lambda Event Source payload for Amazon API Gateway Token Authorizer | | **APIGatewayProxyEventV2Schema** | Lambda Event Source payload for Amazon API Gateway v2 payload | -| **APIGatewayProxyWebsocketEventSchema** | Lambda Event Source payload for Amazon API Gateway WebSocket events | +| **APIGatewayProxyWebsocketEventSchema** | Lambda Event Source payload for Amazon API Gateway WebSocket events | | **APIGatewayRequestAuthorizerEventV2Schema** | Lambda Event Source payload for Amazon API Gateway v2 Authorizer | +| **AppSyncResolverSchema** | Lambda Event Source payload for AWS AppSync GraphQL API resolver | +| **AppSyncBatchResolverSchema** | Lambda Event Source payload for AWS AppSync GraphQL API batch resolver | +| **AppSyncEventsPublishSchema** | Lambda Event Source payload for AWS AppSync Events API `PUBLISH` operation | +| **AppSyncEventsSubscribeSchema** | Lambda Event Source payload for AWS AppSync Events API `SUBSCRIBE` operation | | **CloudFormationCustomResourceCreateSchema** | Lambda Event Source payload for AWS CloudFormation `CREATE` operation | | **CloudFormationCustomResourceUpdateSchema** | Lambda Event Source payload for AWS CloudFormation `UPDATE` operation | | **CloudFormationCustomResourceDeleteSchema** | Lambda Event Source payload for AWS CloudFormation `DELETE` operation | | **CloudwatchLogsSchema** | Lambda Event Source payload for Amazon CloudWatch Logs | -| **PreSignupTriggerSchema** | Lambda Event Source payload for Amazon Cognito Pre Sign-up trigger | -| **PostConfirmationTriggerSchema** | Lambda Event Source payload for Amazon Cognito Post Confirmation trigger | -| **PreTokenGenerationTriggerSchema** | Lambda Event Source payload for Amazon Cognito Pre Token Generation trigger | -| **CustomMessageTriggerSchema** | Lambda Event Source payload for Amazon Cognito Custom Message trigger | -| **MigrateUserTriggerSchema** | Lambda Event Source payload for Amazon Cognito User Migration trigger | -| **CustomSMSTriggerSchema** | Lambda Event Source payload for Amazon Cognito Custom SMS trigger | -| **CustomEmailTriggerSchema** | Lambda Event Source payload for Amazon Cognito Custom Email trigger | -| **DefineAuthChallengeTriggerSchema** | Lambda Event Source payload for Amazon Cognito Define Auth Challenge trigger | -| **CreateAuthChallengeTriggerSchema** | Lambda Event Source payload for Amazon Cognito Create Auth Challenge trigger | -| **VerifyAuthChallengeResponseTriggerSchema** | Lambda Event Source payload for Amazon Cognito Verify Auth Challenge Response trigger | -| **PreTokenGenerationTriggerSchemaV1** | Lambda Event Source payload for Amazon Cognito Pre Token Generation trigger v1 | -| **PreTokenGenerationTriggerSchemaV2AndV3** | Lambda Event Source payload for Amazon Cognito Pre Token Generation trigger v2 and v3 | +| **PreSignupTriggerSchema** | Lambda Event Source payload for Amazon Cognito Pre Sign-up trigger | +| **PostConfirmationTriggerSchema** | Lambda Event Source payload for Amazon Cognito Post Confirmation trigger | +| **PreTokenGenerationTriggerSchema** | Lambda Event Source payload for Amazon Cognito Pre Token Generation trigger | +| **CustomMessageTriggerSchema** | Lambda Event Source payload for Amazon Cognito Custom Message trigger | +| **MigrateUserTriggerSchema** | Lambda Event Source payload for Amazon Cognito User Migration trigger | +| **CustomSMSTriggerSchema** | Lambda Event Source payload for Amazon Cognito Custom SMS trigger | +| **CustomEmailTriggerSchema** | Lambda Event Source payload for Amazon Cognito Custom Email trigger | +| **DefineAuthChallengeTriggerSchema** | Lambda Event Source payload for Amazon Cognito Define Auth Challenge trigger | +| **CreateAuthChallengeTriggerSchema** | Lambda Event Source payload for Amazon Cognito Create Auth Challenge trigger | +| **VerifyAuthChallengeResponseTriggerSchema** | Lambda Event Source payload for Amazon Cognito Verify Auth Challenge Response trigger | +| **PreTokenGenerationTriggerSchemaV1** | Lambda Event Source payload for Amazon Cognito Pre Token Generation trigger v1 | +| **PreTokenGenerationTriggerSchemaV2AndV3** | Lambda Event Source payload for Amazon Cognito Pre Token Generation trigger v2 and v3 | | **DynamoDBStreamSchema** | Lambda Event Source payload for Amazon DynamoDB Streams | | **EventBridgeSchema** | Lambda Event Source payload for Amazon EventBridge | | **KafkaMskEventSchema** | Lambda Event Source payload for AWS MSK payload | diff --git a/packages/parser/src/schemas/appsync-events.ts b/packages/parser/src/schemas/appsync-events.ts new file mode 100644 index 0000000000..0bd7df1b1f --- /dev/null +++ b/packages/parser/src/schemas/appsync-events.ts @@ -0,0 +1,150 @@ +import { z } from 'zod'; + +/** + * A zod schema for AppSync Events request object. + * + * This schema is used when extending subscribe and publish events. + */ +const AppSyncEventsRequestSchema = z.object({ + headers: z.record(z.string(), z.string()).optional(), + domainName: z.string().nullable(), +}); + +/** + * A zod schema for AppSync Events info object. + * + * This schema is used when extending subscribe and publish events. + */ +const AppSyncEventsInfoSchema = z.object({ + channel: z.object({ + path: z.string(), + segments: z.array(z.string()), + }), + channelNamespace: z.object({ + name: z.string(), + }), + operation: z.union([z.literal('PUBLISH'), z.literal('SUBSCRIBE')]), +}); + +/** + * A zod schema for AppSync Events base events. + * + * This schema is used as a base for both publish and subscribe events. + */ +const AppSyncEventsBaseSchema = z.object({ + identity: z.null(), + result: z.null(), + request: AppSyncEventsRequestSchema, + info: AppSyncEventsInfoSchema, + error: z.null(), + prev: z.null(), + stash: z.object({}), + outErrors: z.array(z.unknown()), + events: z.null(), +}); + +/** + * A zod schema for AppSync Events publish events. + * + * @example + * ```json + * { + * "identity": null, + * "result": null, + * "request": { + * "headers": { + * "header1": "value1", + * }, + * "domainName": "example.com" + * }, + * "info": { + * "channel": { + * "path": "/default/foo", + * "segments": ["default", "foo"] + * }, + * "channelNamespace": { + * "name": "default" + * }, + * "operation": "PUBLISH" + * }, + * "error": null, + * "prev": null, + * "stash": {}, + * "outErrors": [], + * "events": [ + * { + * "payload": { + * "key": "value" + * }, + * "id": "12345" + * }, + * { + * "payload": { + * "key2": "value2" + * }, + * "id": "67890" + * } + * ] + * } + * ``` + */ +const AppSyncEventsPublishSchema = AppSyncEventsBaseSchema.extend({ + info: AppSyncEventsInfoSchema.extend({ + operation: z.literal('PUBLISH'), + }), + events: z + .array( + z.object({ + payload: z.record(z.string(), z.unknown()), + id: z.string(), + }) + ) + .min(1), +}); + +/** + * A zod schema for AppSync Events subscribe events. + * + * @example + * ```json + * { + * "identity": null, + * "result": null, + * "request": { + * "headers": { + * "header1": "value1", + * }, + * "domainName": "example.com" + * }, + * "info": { + * "channel": { + * "path": "/default/foo", + * "segments": ["default", "foo"] + * }, + * "channelNamespace": { + * "name": "default" + * }, + * "operation": "SUBSCRIBE" + * }, + * "error": null, + * "prev": null, + * "stash": {}, + * "outErrors": [], + * "events": null, + * } + * ``` + */ +const AppSyncEventsSubscribeSchema = AppSyncEventsBaseSchema.extend({ + info: AppSyncEventsInfoSchema.extend({ + operation: z.literal('SUBSCRIBE'), + }), + events: z.null(), +}); + +export { + AppSyncEventsBaseSchema, + AppSyncEventsRequestSchema, + AppSyncEventsInfoSchema, + AppSyncEventsPublishSchema, + AppSyncEventsSubscribeSchema, +}; diff --git a/packages/parser/src/schemas/index.ts b/packages/parser/src/schemas/index.ts index ed39e519c5..22854cf653 100644 --- a/packages/parser/src/schemas/index.ts +++ b/packages/parser/src/schemas/index.ts @@ -5,11 +5,18 @@ export { APIGatewayTokenAuthorizerEventSchema, APIGatewayEventRequestContextSchema, } from './api-gateway.js'; -export { APIGatewayProxyWebsocketEventSchema } from './api-gateway-websocket.js' +export { APIGatewayProxyWebsocketEventSchema } from './api-gateway-websocket.js'; export { AppSyncResolverSchema, AppSyncBatchResolverSchema, } from './appsync.js'; +export { + AppSyncEventsBaseSchema, + AppSyncEventsRequestSchema, + AppSyncEventsInfoSchema, + AppSyncEventsPublishSchema, + AppSyncEventsSubscribeSchema, +} from './appsync-events.js'; export { APIGatewayProxyEventV2Schema, APIGatewayRequestAuthorizerEventV2Schema, diff --git a/packages/parser/src/types/index.ts b/packages/parser/src/types/index.ts index 459cc88758..40c2308be8 100644 --- a/packages/parser/src/types/index.ts +++ b/packages/parser/src/types/index.ts @@ -19,6 +19,8 @@ export type { APIGatewayTokenAuthorizerEvent, AppSyncBatchResolverEvent, AppSyncResolverEvent, + AppSyncEventsPublishEvent, + AppSyncEventsSubscribeEvent, CloudFormationCustomResourceCreateEvent, CloudFormationCustomResourceDeleteEvent, CloudFormationCustomResourceUpdateEvent, diff --git a/packages/parser/src/types/schema.ts b/packages/parser/src/types/schema.ts index a638535576..7679abf43b 100644 --- a/packages/parser/src/types/schema.ts +++ b/packages/parser/src/types/schema.ts @@ -11,6 +11,8 @@ import type { AlbMultiValueHeadersSchema, AlbSchema, AppSyncBatchResolverSchema, + AppSyncEventsPublishSchema, + AppSyncEventsSubscribeSchema, AppSyncResolverSchema, CloudFormationCustomResourceCreateSchema, CloudFormationCustomResourceDeleteSchema, @@ -69,7 +71,9 @@ type APIGatewayEventRequestContext = z.infer< type APIGatewayProxyEventV2 = z.infer; -type APIGatewayProxyWebsocketEvent = z.infer; +type APIGatewayProxyWebsocketEvent = z.infer< + typeof APIGatewayProxyWebsocketEventSchema +>; type APIGatewayRequestAuthorizerV2 = z.infer< typeof APIGatewayRequestAuthorizerV2Schema @@ -83,6 +87,10 @@ type AppSyncResolverEvent = z.infer; type AppSyncBatchResolverEvent = z.infer; +type AppSyncEventsPublishEvent = z.infer; + +type AppSyncEventsSubscribeEvent = z.infer; + type CloudWatchLogEvent = z.infer; type CloudWatchLogsDecode = z.infer; @@ -176,6 +184,8 @@ export type { APIGatewayTokenAuthorizerEvent, AppSyncBatchResolverEvent, AppSyncResolverEvent, + AppSyncEventsPublishEvent, + AppSyncEventsSubscribeEvent, CloudFormationCustomResourceCreateEvent, CloudFormationCustomResourceDeleteEvent, CloudFormationCustomResourceUpdateEvent, diff --git a/packages/parser/tests/events/appsync-events/base.json b/packages/parser/tests/events/appsync-events/base.json new file mode 100644 index 0000000000..3c1ac13542 --- /dev/null +++ b/packages/parser/tests/events/appsync-events/base.json @@ -0,0 +1,28 @@ +{ + "identity": null, + "result": null, + "request": { + "headers": { + "key": "value" + }, + "domainName": null + }, + "info": { + "channel": { + "path": "/request/channel", + "segments": [ + "request", + "channel" + ] + }, + "channelNamespace": { + "name": "request" + }, + "operation": "PUBLISH" + }, + "error": null, + "prev": null, + "stash": {}, + "outErrors": [], + "events": null +} \ No newline at end of file diff --git a/packages/parser/tests/unit/schema/appsync-events.test.ts b/packages/parser/tests/unit/schema/appsync-events.test.ts new file mode 100644 index 0000000000..25eb6abc6a --- /dev/null +++ b/packages/parser/tests/unit/schema/appsync-events.test.ts @@ -0,0 +1,84 @@ +import { describe, expect, it } from 'vitest'; +import { + AppSyncEventsPublishSchema, + AppSyncEventsSubscribeSchema, +} from '../../../src/schemas/index.js'; +import type { + AppSyncEventsPublishEvent, + AppSyncEventsSubscribeEvent, +} from '../../../src/types/schema.js'; +import { getTestEvent, omit } from '../helpers/utils.js'; + +describe('Schema: AppSync Events', () => { + const eventsPath = 'appsync-events'; + const baseEvent = getTestEvent({ + eventsPath, + filename: 'base', + }); + + describe('AppSyncEventsPublishSchema', () => { + it('throws when the event is invalid', () => { + // Prepare + const event = omit(['info'], structuredClone(baseEvent)); + + // Act & Assess + expect(() => AppSyncEventsPublishSchema.parse(event)).toThrow(); + }); + + it('parses a publish event', () => { + // Prepare + const event = structuredClone( + baseEvent + ) as unknown as AppSyncEventsPublishEvent; + event.info.operation = 'PUBLISH'; + event.events = [ + { + payload: { + event_1: 'data_1', + }, + id: '5f7dfbd1-b8ff-4c20-924e-23b42db467a0', + }, + { + payload: { + event_2: 'data_2', + }, + id: 'ababdf65-a3e6-4c1d-acd3-87466eab433c', + }, + { + payload: { + event_3: 'data_3', + }, + id: '8bb2983a-0967-45a0-8243-0aeb8c83d80e', + }, + ]; + + // Act + const parsedEvent = AppSyncEventsPublishSchema.parse(event); + + // Assess + expect(parsedEvent).toEqual(event); + }); + }); + + describe('AppSyncEventsSubscribeSchema', () => { + it('throws when the event is invalid', () => { + // Prepare + const event = omit(['info'], structuredClone(baseEvent)); + + // Act & Assess + expect(() => AppSyncEventsSubscribeSchema.parse(event)).toThrow(); + }); + + it('parses a subscribe event', () => { + // Prepare + const event = structuredClone(baseEvent); + event.info.operation = 'SUBSCRIBE'; + + // Act + const parsedEvent = AppSyncEventsSubscribeSchema.parse(event); + + // Assess + expect(parsedEvent).toEqual(event); + }); + }); +}); From b70d3fa3d6f5b48f506054342bdf480eaa7e19f4 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Mon, 12 May 2025 10:19:11 +0200 Subject: [PATCH 2/3] chore: add identity schemas --- packages/parser/src/schemas/appsync-events.ts | 24 +++++++++++++- packages/parser/src/schemas/appsync-shared.ts | 30 +++++++++++++++++ packages/parser/src/schemas/appsync.ts | 32 +++---------------- 3 files changed, 58 insertions(+), 28 deletions(-) create mode 100644 packages/parser/src/schemas/appsync-shared.ts diff --git a/packages/parser/src/schemas/appsync-events.ts b/packages/parser/src/schemas/appsync-events.ts index 0bd7df1b1f..7c90d68a99 100644 --- a/packages/parser/src/schemas/appsync-events.ts +++ b/packages/parser/src/schemas/appsync-events.ts @@ -1,4 +1,16 @@ import { z } from 'zod'; +import { + AppSyncCognitoIdentity, + AppSyncIamIdentity, + AppSyncOidcIdentity, +} from './appsync-shared.js'; + +/** + * A zod schema for the AppSync Events `identity` object when using an AWS Lambda Authorizer. + */ +const AppSyncLambdaAuthIdentity = z.object({ + handlerContext: z.record(z.string(), z.unknown()), +}); /** * A zod schema for AppSync Events request object. @@ -32,7 +44,13 @@ const AppSyncEventsInfoSchema = z.object({ * This schema is used as a base for both publish and subscribe events. */ const AppSyncEventsBaseSchema = z.object({ - identity: z.null(), + identity: z.union([ + z.null(), + AppSyncCognitoIdentity, + AppSyncIamIdentity, + AppSyncLambdaAuthIdentity, + AppSyncOidcIdentity, + ]), result: z.null(), request: AppSyncEventsRequestSchema, info: AppSyncEventsInfoSchema, @@ -143,6 +161,10 @@ const AppSyncEventsSubscribeSchema = AppSyncEventsBaseSchema.extend({ export { AppSyncEventsBaseSchema, + AppSyncCognitoIdentity, + AppSyncIamIdentity, + AppSyncLambdaAuthIdentity, + AppSyncOidcIdentity, AppSyncEventsRequestSchema, AppSyncEventsInfoSchema, AppSyncEventsPublishSchema, diff --git a/packages/parser/src/schemas/appsync-shared.ts b/packages/parser/src/schemas/appsync-shared.ts new file mode 100644 index 0000000000..c04d4916e9 --- /dev/null +++ b/packages/parser/src/schemas/appsync-shared.ts @@ -0,0 +1,30 @@ +import { z } from 'zod'; + +const AppSyncIamIdentity = z.object({ + accountId: z.string(), + cognitoIdentityPoolId: z.string().nullable(), + cognitoIdentityId: z.string().nullable(), + sourceIp: z.array(z.string()), + username: z.string(), + userArn: z.string(), + cognitoIdentityAuthType: z.string().nullable(), + cognitoIdentityAuthProvider: z.string().nullable(), +}); + +const AppSyncCognitoIdentity = z.object({ + sub: z.string(), + issuer: z.string(), + username: z.string(), + claims: z.record(z.string(), z.unknown()), + sourceIp: z.array(z.string().ip()), + defaultAuthStrategy: z.string().nullable(), + groups: z.array(z.string()).nullable(), +}); + +const AppSyncOidcIdentity = z.object({ + claims: z.any(), + issuer: z.string(), + sub: z.string(), +}); + +export { AppSyncCognitoIdentity, AppSyncIamIdentity, AppSyncOidcIdentity }; diff --git a/packages/parser/src/schemas/appsync.ts b/packages/parser/src/schemas/appsync.ts index c3f5daece4..5738a8f1c2 100644 --- a/packages/parser/src/schemas/appsync.ts +++ b/packages/parser/src/schemas/appsync.ts @@ -1,31 +1,9 @@ import { z } from 'zod'; - -const AppSyncIamIdentity = z.object({ - accountId: z.string(), - cognitoIdentityPoolId: z.string().nullable(), - cognitoIdentityId: z.string().nullable(), - sourceIp: z.array(z.string()), - username: z.string(), - userArn: z.string(), - cognitoIdentityAuthType: z.string().nullable(), - cognitoIdentityAuthProvider: z.string().nullable(), -}); - -const AppSyncCognitoIdentity = z.object({ - sub: z.string(), - issuer: z.string(), - username: z.string(), - claims: z.any(), - sourceIp: z.array(z.string()), - defaultAuthStrategy: z.string(), - groups: z.array(z.string()).nullable(), -}); - -const AppSyncOidcIdentity = z.object({ - claims: z.any(), - issuer: z.string(), - sub: z.string(), -}); +import { + AppSyncCognitoIdentity, + AppSyncIamIdentity, + AppSyncOidcIdentity, +} from './appsync-shared.js'; const AppSyncLambdaIdentity = z.object({ resolverContext: z.any(), From 61897c841210612933c69c0f3acab198b08cc456 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Mon, 12 May 2025 10:19:36 +0200 Subject: [PATCH 3/3] chore: export identity schemas --- packages/parser/src/schemas/appsync.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/parser/src/schemas/appsync.ts b/packages/parser/src/schemas/appsync.ts index 5738a8f1c2..610c507d75 100644 --- a/packages/parser/src/schemas/appsync.ts +++ b/packages/parser/src/schemas/appsync.ts @@ -229,4 +229,11 @@ const AppSyncResolverSchema = z.object({ const AppSyncBatchResolverSchema = z.array(AppSyncResolverSchema); -export { AppSyncResolverSchema, AppSyncBatchResolverSchema }; +export { + AppSyncResolverSchema, + AppSyncBatchResolverSchema, + AppSyncCognitoIdentity, + AppSyncIamIdentity, + AppSyncOidcIdentity, + AppSyncLambdaIdentity, +};