1
- import type { ZodSchema , z } from 'zod' ;
1
+ import { ZodError , type ZodIssue , type ZodSchema , type z } from 'zod' ;
2
2
import { 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' ;
5
4
import type { ParsedResult } from '../types/index.js' ;
6
- import { Envelope , envelopeDiscriminator } from './envelope.js' ;
5
+ import { envelopeDiscriminator } from './envelope.js' ;
7
6
8
7
/**
9
8
* SNS Envelope to extract array of Records
@@ -23,8 +22,19 @@ export const SnsEnvelope = {
23
22
parse < T extends ZodSchema > ( data : unknown , schema : T ) : z . infer < T > [ ] {
24
23
const parsedEnvelope = SnsSchema . parse ( data ) ;
25
24
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
+ }
28
38
} ) ;
29
39
} ,
30
40
@@ -44,112 +54,47 @@ export const SnsEnvelope = {
44
54
} ;
45
55
}
46
56
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
+ }
93
74
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
+ ) ;
97
80
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 } ;
111
83
}
112
84
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
+ ) ;
114
93
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
+ } ;
154
99
} ,
155
100
} ;
0 commit comments