diff --git a/packages/parser/src/parser.ts b/packages/parser/src/parser.ts index 43c8ad09c8..dccb6fd8f9 100644 --- a/packages/parser/src/parser.ts +++ b/packages/parser/src/parser.ts @@ -1,6 +1,7 @@ import type { ZodSchema, z } from 'zod'; import { ParseError } from './errors.js'; -import type { Envelope, ParsedResult } from './types/index.js'; +import type { Envelope } from './types/index.js'; +import type { ParseFunction } from './types/parser.js'; /** * Parse the data using the provided schema, envelope and safeParse flag @@ -27,12 +28,12 @@ import type { Envelope, ParsedResult } from './types/index.js'; * @param schema the schema to use * @param safeParse whether to use safeParse or not, if true it will return a ParsedResult with the original event if the parsing fails */ -const parse = ( +const parse: ParseFunction = ( data: z.infer, envelope: E | undefined, schema: T, safeParse?: boolean -): ParsedResult | z.infer => { +) => { if (envelope && safeParse) { return envelope.safeParse(data, schema); } @@ -56,10 +57,7 @@ const parse = ( * @param data the data to parse * @param schema the zod schema to use */ -const safeParseSchema = ( - data: z.infer, - schema: T -): ParsedResult => { +const safeParseSchema = (data: z.infer, schema: T) => { const result = schema.safeParse(data); return result.success diff --git a/packages/parser/src/types/index.ts b/packages/parser/src/types/index.ts index 1bc68a549a..e4f331e88b 100644 --- a/packages/parser/src/types/index.ts +++ b/packages/parser/src/types/index.ts @@ -1,42 +1,43 @@ export type { - ParserOptions, ParsedResult, - ParsedResultSuccess, ParsedResultError, + ParsedResultSuccess, + ParseFunction, + ParserOptions, } from '../types/parser.js'; export type { Envelope } from './envelope.js'; export type { ALBEvent, - APIGatewayProxyEvent, ALBMultiValueHeadersEvent, + APIGatewayProxyEvent, APIGatewayProxyEventV2, - APIGatewayRequestContextV2, APIGatewayRequestAuthorizerV2, - AppSyncResolverEvent, + APIGatewayRequestContextV2, AppSyncBatchResolverEvent, - S3Event, - S3EventNotificationEventBridge, - S3SqsEventNotification, - SnsEvent, - SqsEvent, - DynamoDBStreamEvent, - DynamoDBStreamToKinesisRecordEvent, - CloudWatchLogsEvent, + AppSyncResolverEvent, CloudFormationCustomResourceCreateEvent, CloudFormationCustomResourceDeleteEvent, CloudFormationCustomResourceUpdateEvent, + CloudWatchLogsEvent, + DynamoDBStreamEvent, + DynamoDBStreamToKinesisRecordEvent, EventBridgeEvent, - KafkaSelfManagedEvent, KafkaMskEvent, + KafkaSelfManagedEvent, KinesisDataStreamEvent, KinesisDynamoDBStreamEvent, KinesisFireHoseEvent, KinesisFireHoseSqsEvent, LambdaFunctionUrlEvent, + S3Event, + S3EventNotificationEventBridge, + S3ObjectLambdaEvent, + S3SqsEventNotification, SesEvent, + SnsEvent, SnsSqsNotification, - S3ObjectLambdaEvent, + SqsEvent, VpcLatticeEvent, VpcLatticeEventV2, } from './schema.js'; diff --git a/packages/parser/src/types/parser.ts b/packages/parser/src/types/parser.ts index 16178fa958..91b423e8cc 100644 --- a/packages/parser/src/types/parser.ts +++ b/packages/parser/src/types/parser.ts @@ -34,7 +34,7 @@ type ParsedResultError = { /** * The result of parsing an event using the safeParse, can either be a success or an error */ -type ParsedResult = +type ParsedResult = | ParsedResultSuccess | ParsedResultError; @@ -54,7 +54,7 @@ type ZodInferredSafeParseResult< TSchema extends ZodSchema, TEnvelope extends Envelope, > = undefined extends TEnvelope - ? ParsedResult> + ? ParsedResult, z.infer> : TEnvelope extends ArrayEnvelope ? ParsedResult[]> : ParsedResult>; @@ -70,10 +70,63 @@ type ParserOutput< ? ZodInferredSafeParseResult : ZodInferredResult; +/** + * The parser function that can parse the data using the provided schema and envelope + * we use function overloads to provide the correct return type based on the provided envelope + **/ +type ParseFunction = { + // No envelope cases + ( + data: z.infer, + envelope: undefined, + schema: T, + safeParse?: false + ): z.infer; + + ( + data: z.infer, + envelope: undefined, + schema: T, + safeParse: true + ): ParsedResult>; + + // Generic envelope case + ( + data: unknown, + envelope: E, + schema: T, + safeParse?: false + ): E extends ArrayEnvelope ? z.infer[] : z.infer; + + ( + data: unknown, + envelope: E, + schema: T, + safeParse: true + ): E extends ArrayEnvelope + ? ParsedResult[]> + : ParsedResult>; + + // Generic envelope case with safeParse + ( + data: unknown, + envelope: E, + schema: T, + safeParse?: S + ): S extends true + ? E extends ArrayEnvelope + ? ParsedResult[]> + : ParsedResult> + : E extends ArrayEnvelope + ? z.infer[] + : z.infer; +}; + export type { - ParserOptions, + ParseFunction, ParsedResult, ParsedResultError, ParsedResultSuccess, + ParserOptions, ParserOutput, }; diff --git a/packages/parser/tests/types/envelopes.test.ts b/packages/parser/tests/types/envelopes.test.ts index 72cfbca86b..f211feb5f5 100644 --- a/packages/parser/tests/types/envelopes.test.ts +++ b/packages/parser/tests/types/envelopes.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from 'vitest'; +import { describe, expect, expectTypeOf, it } from 'vitest'; import { z } from 'zod'; import { ApiGatewayEnvelope, @@ -69,10 +69,6 @@ describe('Types ', () => { expect(Array.isArray(result)).toBe(true); expect(result).toEqual([{ name: 'John', age: 30 }]); - // Type assertion to ensure it's specifically User[] - type AssertIsUserArray = T extends z.infer[] - ? true - : false; - type Test = AssertIsUserArray; + expectTypeOf(result).toEqualTypeOf[]>(); }); }); diff --git a/packages/parser/tests/types/parser.test.ts b/packages/parser/tests/types/parser.test.ts new file mode 100644 index 0000000000..292acfb36c --- /dev/null +++ b/packages/parser/tests/types/parser.test.ts @@ -0,0 +1,88 @@ +import { describe } from 'node:test'; +import { expect, expectTypeOf, it } from 'vitest'; +import { z } from 'zod'; +import { EventBridgeEnvelope, SqsEnvelope } from '../../src/envelopes/index.js'; +import { parse } from '../../src/parser.js'; +import type { EventBridgeEvent, SqsEvent } from '../../src/types/schema.js'; +import { getTestEvent } from '../unit/helpers/utils.js'; + +describe('Parser types', () => { + const userSchema = z.object({ + name: z.string(), + age: z.number(), + }); + type User = z.infer; + const input = { name: 'John', age: 30 }; + + const eventBridgeBaseEvent = getTestEvent({ + eventsPath: 'eventbridge', + filename: 'base', + }); + + const sqsBaseEvent = getTestEvent({ + eventsPath: 'sqs', + filename: 'base', + }); + it('infers return type for schema and safeParse', () => { + // Act + const result = parse(input, undefined, userSchema, true); + + // Assert + if (result.success) { + expectTypeOf(result.data).toEqualTypeOf(); + } else { + expectTypeOf(result.originalEvent).toEqualTypeOf(); + } + }); + + it('infers return type for schema', () => { + // Act + const result = parse(input, undefined, userSchema); + + // Assert + expectTypeOf(result).toEqualTypeOf(); + }); + + it('infers return type for schema and envelope', () => { + // Prepare + const event = structuredClone(eventBridgeBaseEvent); + event.detail = input; + + // Act + const result = parse(event, EventBridgeEnvelope, userSchema); + + // Assert + expectTypeOf(result).toEqualTypeOf(); + }); + + it('infert return type for schema, object envelope and safeParse', () => { + // Prepare + const event = structuredClone(eventBridgeBaseEvent); + event.detail = input; + + // Act + const result = parse(event, EventBridgeEnvelope, userSchema, true); + + // Assert + if (result.success) { + expectTypeOf(result.data).toEqualTypeOf(); + expect(result.data).toEqual(input); + } else { + throw new Error('Parsing failed'); + } + }); + + it('infers return type for schema, array envelope and safeParse', () => { + // Prepare + const event = structuredClone(sqsBaseEvent); + event.Records[0].body = JSON.stringify(input); + + // Act + const result = parse(input, SqsEnvelope, userSchema, true); + + // Assert + if (result.success) { + expectTypeOf(result.data).toEqualTypeOf(); + } + }); +});