1- import type { ZodSchema , z } from 'zod' ;
1+ import { ZodError , type ZodIssue , type ZodSchema , type z } from 'zod' ;
22import { ParseError } from '../errors.js' ;
3- import { SnsSchema , SnsSqsNotificationSchema } from '../schemas/sns.js' ;
4- import { SqsSchema } from '../schemas/sqs.js' ;
3+ import { SnsSchema } from '../schemas/sns.js' ;
54import type { ParsedResult } from '../types/index.js' ;
6- import { Envelope , envelopeDiscriminator } from './envelope.js' ;
5+ import { envelopeDiscriminator } from './envelope.js' ;
76
87/**
98 * SNS Envelope to extract array of Records
@@ -23,8 +22,19 @@ export const SnsEnvelope = {
2322 parse < T extends ZodSchema > ( data : unknown , schema : T ) : z . infer < T > [ ] {
2423 const parsedEnvelope = SnsSchema . parse ( data ) ;
2524
26- return parsedEnvelope . Records . map ( ( record ) => {
27- return Envelope . parse ( record . Sns . Message , schema ) ;
25+ return parsedEnvelope . Records . map ( ( record , index ) => {
26+ try {
27+ return schema . parse ( record . Sns . Message ) ;
28+ } catch ( error ) {
29+ throw new ParseError ( `Failed to parse SNS record at index ${ index } ` , {
30+ cause : new ZodError (
31+ ( error as ZodError ) . issues . map ( ( issue ) => ( {
32+ ...issue ,
33+ path : [ 'Records' , index , 'Sns' , 'Message' , ...issue . path ] ,
34+ } ) )
35+ ) ,
36+ } ) ;
37+ }
2838 } ) ;
2939 } ,
3040
@@ -44,112 +54,47 @@ export const SnsEnvelope = {
4454 } ;
4555 }
4656
47- const parsedMessages : z . infer < T > [ ] = [ ] ;
48- for ( const record of parsedEnvelope . data . Records ) {
49- const parsedMessage = Envelope . safeParse ( record . Sns . Message , schema ) ;
50- if ( ! parsedMessage . success ) {
51- return {
52- success : false ,
53- error : new ParseError ( 'Failed to parse SNS message' , {
54- cause : parsedMessage . error ,
55- } ) ,
56- originalEvent : data ,
57- } ;
58- }
59- parsedMessages . push ( parsedMessage . data ) ;
60- }
61-
62- return {
63- success : true ,
64- data : parsedMessages ,
65- } ;
66- } ,
67- } ;
68-
69- /**
70- * SNS plus SQS Envelope to extract array of Records
71- *
72- * Published messages from SNS to SQS has a slightly different payload.
73- * Since SNS payload is marshalled into `Record` key in SQS, we have to:
74- *
75- * 1. Parse SQS schema with incoming data
76- * 2. Unmarshall SNS payload and parse against SNS Notification schema not SNS/SNS Record
77- * 3. Finally, parse provided model against payload extracted
78- *
79- */
80- export const SnsSqsEnvelope = {
81- /**
82- * This is a discriminator to differentiate whether an envelope returns an array or an object
83- * @hidden
84- */
85- [ envelopeDiscriminator ] : 'array' as const ,
86- parse < T extends ZodSchema > ( data : unknown , schema : T ) : z . infer < T > [ ] {
87- const parsedEnvelope = SqsSchema . parse ( data ) ;
88-
89- return parsedEnvelope . Records . map ( ( record ) => {
90- const snsNotification = SnsSqsNotificationSchema . parse (
91- JSON . parse ( record . body )
92- ) ;
57+ const result = parsedEnvelope . data . Records . reduce < {
58+ success : boolean ;
59+ messages : z . infer < T > [ ] ;
60+ errors : { index ?: number ; issues ?: ZodIssue [ ] } ;
61+ } > (
62+ ( acc , message , index ) => {
63+ const parsedMessage = schema . safeParse ( message . Sns . Message ) ;
64+ if ( ! parsedMessage . success ) {
65+ acc . success = false ;
66+ const issues = parsedMessage . error . issues . map ( ( issue ) => ( {
67+ ...issue ,
68+ path : [ 'Records' , index , 'Sns' , 'Message' , ...issue . path ] ,
69+ } ) ) ;
70+ // @ts -expect-error - index is assigned
71+ acc . errors [ index ] = { issues } ;
72+ return acc ;
73+ }
9374
94- return Envelope . parse ( snsNotification . Message , schema ) ;
95- } ) ;
96- } ,
75+ acc . messages . push ( parsedMessage . data ) ;
76+ return acc ;
77+ } ,
78+ { success : true , messages : [ ] , errors : { } }
79+ ) ;
9780
98- safeParse < T extends ZodSchema > (
99- data : unknown ,
100- schema : T
101- ) : ParsedResult < unknown , z . infer < T > [ ] > {
102- const parsedEnvelope = SqsSchema . safeParse ( data ) ;
103- if ( ! parsedEnvelope . success ) {
104- return {
105- success : false ,
106- error : new ParseError ( 'Failed to parse SQS envelope' , {
107- cause : parsedEnvelope . error ,
108- } ) ,
109- originalEvent : data ,
110- } ;
81+ if ( result . success ) {
82+ return { success : true , data : result . messages } ;
11183 }
11284
113- const parsedMessages : z . infer < T > [ ] = [ ] ;
85+ const errorMessage =
86+ Object . keys ( result . errors ) . length > 1
87+ ? `Failed to parse SNS messages at indexes ${ Object . keys ( result . errors ) . join ( ', ' ) } `
88+ : `Failed to parse SNS message at index ${ Object . keys ( result . errors ) [ 0 ] } ` ;
89+ const errorCause = new ZodError (
90+ // @ts -expect-error - issues are assigned because success is false
91+ Object . values ( result . errors ) . flatMap ( ( error ) => error . issues )
92+ ) ;
11493
115- // JSON.parse can throw an error, thus we catch it and return ParsedErrorResult
116- try {
117- for ( const record of parsedEnvelope . data . Records ) {
118- const snsNotification = SnsSqsNotificationSchema . safeParse (
119- JSON . parse ( record . body )
120- ) ;
121- if ( ! snsNotification . success ) {
122- return {
123- success : false ,
124- error : new ParseError ( 'Failed to parse SNS notification' , {
125- cause : snsNotification . error ,
126- } ) ,
127- originalEvent : data ,
128- } ;
129- }
130- const parsedMessage = Envelope . safeParse (
131- snsNotification . data . Message ,
132- schema
133- ) ;
134- if ( ! parsedMessage . success ) {
135- return {
136- success : false ,
137- error : new ParseError ( 'Failed to parse SNS message' , {
138- cause : parsedMessage . error ,
139- } ) ,
140- originalEvent : data ,
141- } ;
142- }
143- parsedMessages . push ( parsedMessage . data ) ;
144- }
145- } catch ( e ) {
146- return {
147- success : false ,
148- error : e as Error ,
149- originalEvent : data ,
150- } ;
151- }
152-
153- return { success : true , data : parsedMessages } ;
94+ return {
95+ success : false ,
96+ error : new ParseError ( errorMessage , { cause : errorCause } ) ,
97+ originalEvent : data ,
98+ } ;
15499 } ,
155100} ;
0 commit comments