@@ -12,8 +12,8 @@ Deno.test('Error#buildResponse 500 does not leak error message', async () => {
1212 new globalThis . Error ( 'Connection to ************************ failed' ) ,
1313 null
1414 )
15- const body = ( await res . json ( ) ) as { error : string }
16- assertEquals ( body . error , 'Internal Server Error' )
15+ const body = ( await res . json ( ) ) as { title : string }
16+ assertEquals ( body . title , 'Internal Server Error' )
1717 assertEquals ( JSON . stringify ( body ) . includes ( 'password' ) , false )
1818} )
1919
@@ -40,9 +40,9 @@ Deno.test('Error#buildResponse preserves security headers when errorMiddleware r
4040 )
4141 assertEquals ( res . status , 500 )
4242 assertEquals ( res . headers . get ( 'X-Content-Type-Options' ) , 'nosniff' )
43- assertEquals ( res . headers . get ( 'Content-Type' ) , 'application/json' )
44- const body = ( await res . json ( ) ) as { error : string }
45- assertEquals ( body . error , 'Internal Server Error' )
43+ assertEquals ( res . headers . get ( 'Content-Type' ) , 'application/problem+ json' )
44+ const body = ( await res . json ( ) ) as { title : string }
45+ assertEquals ( body . title , 'Internal Server Error' )
4646} )
4747
4848Deno . test ( 'Error#buildResponse uses generic message for unmapped 418 status' , async ( ) => {
@@ -56,9 +56,9 @@ Deno.test('Error#buildResponse uses generic message for unmapped 418 status', as
5656 new globalThis . Error ( 'secret internal detail' ) ,
5757 null
5858 )
59- const body = ( await res . json ( ) ) as { error : string ; statusCode : number }
60- assertEquals ( body . error , 'Bad Request' )
61- assertEquals ( body . statusCode , 418 )
59+ const body = ( await res . json ( ) ) as { title : string ; status : number }
60+ assertEquals ( body . title , 'Bad Request' )
61+ assertEquals ( body . status , 418 )
6262} )
6363
6464Deno . test ( 'Error#buildResponse uses generic message for unmapped 599 status' , async ( ) => {
@@ -72,8 +72,8 @@ Deno.test('Error#buildResponse uses generic message for unmapped 599 status', as
7272 new globalThis . Error ( 'db password leaked' ) ,
7373 null
7474 )
75- const body = ( await res . json ( ) ) as { error : string }
76- assertEquals ( body . error , 'Internal Server Error' )
75+ const body = ( await res . json ( ) ) as { title : string }
76+ assertEquals ( body . title , 'Internal Server Error' )
7777} )
7878
7979Deno . test ( 'Error#buildResponse with errorMiddleware receives correct error info' , async ( ) => {
@@ -114,8 +114,8 @@ Deno.test('Error#buildResponse with errorMiddleware returning null uses default'
114114 async ( ) => null
115115 )
116116 assertEquals ( res . status , 502 )
117- const body = ( await res . json ( ) ) as { error : string }
118- assertEquals ( body . error , 'Bad Gateway' )
117+ const body = ( await res . json ( ) ) as { title : string }
118+ assertEquals ( body . title , 'Bad Gateway' )
119119} )
120120
121121Deno . test ( 'Error#buildResponse with errorMiddleware returning response uses it' , async ( ) => {
@@ -147,9 +147,9 @@ Deno.test('Error#buildResponse with errorMiddleware returns non-Response falls t
147147 async ( ) => ret as never
148148 )
149149 assertEquals ( res . status , 500 )
150- assertEquals ( res . headers . get ( 'Content-Type' ) , 'application/json' )
151- const body = ( await res . json ( ) ) as { error : string }
152- assertEquals ( body . error , 'Internal Server Error' )
150+ assertEquals ( res . headers . get ( 'Content-Type' ) , 'application/problem+ json' )
151+ const body = ( await res . json ( ) ) as { title : string }
152+ assertEquals ( body . title , 'Internal Server Error' )
153153 }
154154} )
155155
@@ -175,8 +175,8 @@ Deno.test('Error#buildResponse with sync errorMiddleware returning null', async
175175 const ctx = new Core . Context ( request , new URL ( 'http://localhost/' ) , { } )
176176 const res = await Core . Handler . buildResponse ( ctx , 500 , new globalThis . Error ( 'fail' ) , ( ) => null )
177177 assertEquals ( res . status , 500 )
178- const body = ( await res . json ( ) ) as { error : string }
179- assertEquals ( body . error , 'Internal Server Error' )
178+ const body = ( await res . json ( ) ) as { title : string }
179+ assertEquals ( body . title , 'Internal Server Error' )
180180} )
181181
182182Deno . test ( 'Error#buildResponse without errorMiddleware returns JSON when Accept json' , async ( ) => {
@@ -186,11 +186,11 @@ Deno.test('Error#buildResponse without errorMiddleware returns JSON when Accept
186186 const ctx = new Core . Context ( request , new URL ( 'http://localhost/foo' ) , { } )
187187 const res = await Core . Handler . buildResponse ( ctx , 404 , new globalThis . Error ( 'gone' ) , null )
188188 assertEquals ( res . status , 404 )
189- assertEquals ( res . headers . get ( 'Content-Type' ) , 'application/json' )
190- const body = ( await res . json ( ) ) as { error : string ; path : string ; statusCode : number }
191- assertEquals ( body . error , 'Not Found' )
192- assertEquals ( body . path , '/foo' )
193- assertEquals ( body . statusCode , 404 )
189+ assertEquals ( res . headers . get ( 'Content-Type' ) , 'application/problem+ json' )
190+ const body = ( await res . json ( ) ) as { title : string ; instance : string ; status : number }
191+ assertEquals ( body . title , 'Not Found' )
192+ assertEquals ( body . instance , '/foo' )
193+ assertEquals ( body . status , 404 )
194194} )
195195
196196Deno . test ( 'Error#defaultErrorHtml escapes quotes and apostrophes in message' , ( ) => {
@@ -229,10 +229,10 @@ Deno.test('Error#errorResponse falls back to a hardened JSON response when heade
229229 ; ( ctx as unknown as { responseHeaders : Record < string , string > } ) . responseHeaders [ 'Inva lid' ] = 'x'
230230 const res = Core . Handler . errorResponse ( ctx , 500 )
231231 assertEquals ( res . status , 500 )
232- assertEquals ( res . headers . get ( 'Content-Type' ) , 'application/json' )
232+ assertEquals ( res . headers . get ( 'Content-Type' ) , 'application/problem+ json' )
233233 assertEquals ( res . headers . get ( 'X-Content-Type-Options' ) , 'nosniff' )
234- const body = ( await res . json ( ) ) as { error : string }
235- assertEquals ( body . error , 'Internal Server Error' )
234+ const body = ( await res . json ( ) ) as { title : string }
235+ assertEquals ( body . title , 'Internal Server Error' )
236236} )
237237
238238Deno . test ( 'Error#escapeHtml escapes &, <, >, ", \'' , ( ) => {
@@ -306,3 +306,42 @@ Deno.test('Error#extractError wraps a non-error value as 500', () => {
306306 assertEquals ( result . error instanceof globalThis . Error , true )
307307 assertEquals ( result . error . message , 'plain string failure' )
308308} )
309+
310+ Deno . test ( 'Error#handleError omits errors when error has no structured cause' , async ( ) => {
311+ const request = new Request ( 'http://localhost/x' , {
312+ headers : new Headers ( { Accept : 'application/json' } )
313+ } )
314+ const ctx = new Core . Context ( request , new URL ( 'http://localhost/x' ) , { } )
315+ const res = await ctx . handleError ( 500 , new globalThis . Error ( 'boom' ) )
316+ assertEquals ( res . status , 500 )
317+ const body = ( await res . json ( ) ) as { title : string ; errors ?: string [ ] }
318+ assertEquals ( body . title , 'Internal Server Error' )
319+ assertEquals ( 'errors' in body , false )
320+ } )
321+
322+ Deno . test ( 'Error#handleError surfaces validation reasons as problem+json errors' , async ( ) => {
323+ const request = new Request ( 'http://localhost/users' , {
324+ headers : new Headers ( { Accept : 'application/json' } )
325+ } )
326+ const ctx = new Core . Context ( request , new URL ( 'http://localhost/users' ) , { } )
327+ const validationError = Core . Handler . createStatusError ( 422 , 'name must not be empty' )
328+ Object . defineProperty ( validationError , 'cause' , {
329+ value : [ 'name must not be empty' , 'email must contain @' ] ,
330+ enumerable : false
331+ } )
332+ const res = await ctx . handleError ( 422 , validationError )
333+ assertEquals ( res . status , 422 )
334+ assertEquals ( res . headers . get ( 'Content-Type' ) , 'application/problem+json' )
335+ const body = ( await res . json ( ) ) as {
336+ type : string
337+ title : string
338+ status : number
339+ instance : string
340+ errors : string [ ]
341+ }
342+ assertEquals ( body . type , 'about:blank' )
343+ assertEquals ( body . title , 'Unprocessable Entity' )
344+ assertEquals ( body . status , 422 )
345+ assertEquals ( body . instance , '/users' )
346+ assertEquals ( body . errors , [ 'name must not be empty' , 'email must contain @' ] )
347+ } )
0 commit comments