@@ -19,8 +19,27 @@ import { Deps } from "../config/deps";
1919type SupplierSpec = { supplierId : string ; specId : string } ;
2020type PreparedEvents = LetterRequestPreparedEventV2 | LetterRequestPreparedEvent ;
2121
22+ const SupplierSpecSchema = z . object ( {
23+ supplierId : z . string ( ) . min ( 1 ) ,
24+ specId : z . string ( ) . min ( 1 ) ,
25+ } ) ;
26+
27+ const LetterEventUnionSchema = z . discriminatedUnion ( "type" , [
28+ $LetterRequestPreparedEventV2 ,
29+ $LetterRequestPreparedEvent ,
30+ $LetterEvent ,
31+ ] ) ;
32+
33+ const QueueMessageSchema = z . union ( [
34+ $LetterEvent ,
35+ z . object ( {
36+ letterEvent : LetterEventUnionSchema ,
37+ supplierSpec : SupplierSpecSchema . optional ( ) ,
38+ } ) ,
39+ ] ) ;
40+
2241type UpsertOperation = {
23- name : "Insert" | "Update" ;
42+ name : "Insert" | "Update" | "Unknown" ;
2443 schemas : z . ZodSchema [ ] ;
2544 handler : (
2645 request : unknown ,
@@ -33,7 +52,9 @@ type UpsertOperation = {
3352const TypeEnvelope = z . object ( { type : z . string ( ) . min ( 1 ) } ) ;
3453
3554function getOperationFromType ( type : string ) : UpsertOperation {
36- if ( type . startsWith ( "uk.nhs.notify.letter-rendering.letter-request.prepared" ) )
55+ if (
56+ type . startsWith ( "uk.nhs.notify.letter-rendering.letter-request.prepared" )
57+ ) {
3758 return {
3859 name : "Insert" ,
3960 schemas : [ $LetterRequestPreparedEventV2 , $LetterRequestPreparedEvent ] ,
@@ -55,24 +76,24 @@ function getOperationFromType(type: string): UpsertOperation {
5576 } ) ;
5677 } ,
5778 } ;
58- if ( type . startsWith ( "uk.nhs.notify.supplier-api.letter" ) )
59- return {
60- name : "Update" ,
61- schemas : [ $LetterEvent ] ,
62- handler : async ( request , supplierSpec , deps ) => {
63- const supplierEvent = request as LetterEvent ;
64- const letterToUpdate : UpdateLetter = mapToUpdateLetter ( supplierEvent ) ;
65- await deps . letterRepo . updateLetterStatus ( letterToUpdate ) ;
79+ }
80+ // if it's not an insert type, it must be an update as we've already parsed the message, but we want to have a separate operation for better logging and metrics
81+ return {
82+ name : "Update" ,
83+ schemas : [ $LetterEvent ] ,
84+ handler : async ( request , supplierSpec , deps ) => {
85+ const supplierEvent = request as LetterEvent ;
86+ const letterToUpdate : UpdateLetter = mapToUpdateLetter ( supplierEvent ) ;
87+ await deps . letterRepo . updateLetterStatus ( letterToUpdate ) ;
6688
67- deps . logger . info ( {
68- description : "Updated letter" ,
69- eventId : supplierEvent . id ,
70- letterId : letterToUpdate . id ,
71- supplierId : letterToUpdate . supplierId ,
72- } ) ;
73- } ,
74- } ;
75- throw new Error ( `Unknown operation from type=${ type } ` ) ;
89+ deps . logger . info ( {
90+ description : "Updated letter" ,
91+ eventId : supplierEvent . id ,
92+ letterId : letterToUpdate . id ,
93+ supplierId : letterToUpdate . supplierId ,
94+ } ) ;
95+ } ,
96+ } ;
7697}
7798
7899function mapToInsertLetter (
@@ -111,19 +132,6 @@ function mapToUpdateLetter(upsertRequest: LetterEvent): UpdateLetter {
111132 reasonText : upsertRequest . data . reasonText ,
112133 } ;
113134}
114- function getType ( event : unknown ) {
115- const env = TypeEnvelope . safeParse ( event ) ;
116- if ( ! env . success ) {
117- // Helpful debugging info:
118- const pretty = ( ( ) => {
119- return JSON . stringify ( event , null , 2 ) ;
120- } ) ( ) ;
121- throw new Error (
122- `Missing or invalid envelope.type field. Payload seen:\n${ pretty } ` ,
123- ) ;
124- }
125- return env . data . type ;
126- }
127135
128136async function runUpsert (
129137 operation : UpsertOperation ,
@@ -138,8 +146,6 @@ async function runUpsert(
138146 return ;
139147 }
140148 }
141- // none matched
142- throw new Error ( "No matching schema for received message" ) ;
143149}
144150
145151async function emitMetrics (
@@ -193,7 +199,24 @@ export default function createUpsertLetterHandler(deps: Deps): SQSHandler {
193199 queueMessage,
194200 } ) ;
195201
196- const { letterEvent, supplierSpec } = queueMessage ;
202+ const result = QueueMessageSchema . safeParse ( queueMessage ) ;
203+ if ( ! result . success ) {
204+ throw new Error (
205+ `Message did not match expected schema: ${ JSON . stringify (
206+ result . error . issues ,
207+ ) } `,
208+ ) ;
209+ }
210+ let letterEvent : any ;
211+ let supplierSpec : SupplierSpec | undefined ;
212+
213+ if ( "letterEvent" in result . data ) {
214+ letterEvent = result . data . letterEvent ;
215+ supplierSpec = result . data . supplierSpec ;
216+ } else {
217+ letterEvent = result . data ;
218+ supplierSpec = undefined ;
219+ }
197220
198221 deps . logger . info ( {
199222 description : "Extracted letter event" ,
@@ -207,11 +230,14 @@ export default function createUpsertLetterHandler(deps: Deps): SQSHandler {
207230 ? getSupplierIdFromEvent ( letterEvent )
208231 : supplierSpec . supplierId ;
209232
210- const type = getType ( letterEvent ) ;
233+ const operation = getOperationFromType ( letterEvent . type ) ;
211234
212- const operation = getOperationFromType ( type ) ;
213-
214- await runUpsert ( operation , letterEvent , supplierSpec , deps ) ;
235+ await runUpsert (
236+ operation ,
237+ letterEvent ,
238+ supplierSpec ?? { supplierId : "unknown" , specId : "unknown" } ,
239+ deps ,
240+ ) ;
215241
216242 perSupplierSuccess . set (
217243 supplier ,
0 commit comments